| // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| // @dart=2.11 |
| |
| part of '../protoc.dart'; |
| |
| class ServiceGenerator { |
| final ServiceDescriptorProto _descriptor; |
| |
| /// The generator of the .pb.dart file that will contain this service. |
| final FileGenerator fileGen; |
| |
| /// The message types needed directly by this service. |
| /// |
| /// The key is the fully qualified name with a leading '.'. |
| /// Populated by [resolve]. |
| final _deps = <String, MessageGenerator>{}; |
| |
| /// The message types needed transitively by this service. |
| /// |
| /// The key is the fully qualified name with a leading '.'. |
| /// Populated by [resolve]. |
| final _transitiveDeps = <String, MessageGenerator>{}; |
| |
| /// Maps each undefined type to a string describing its location. |
| /// |
| /// Populated by [resolve]. |
| final _undefinedDeps = <String, String>{}; |
| |
| final String classname; |
| |
| static String serviceBaseName(String originalName) { |
| if (originalName.endsWith('Service')) { |
| return originalName + 'Base'; // avoid: ServiceServiceBase |
| } else { |
| return originalName + 'ServiceBase'; |
| } |
| } |
| |
| ServiceGenerator(this._descriptor, this.fileGen, Set<String> usedNames) |
| : classname = disambiguateName( |
| serviceBaseName(avoidInitialUnderscore(_descriptor.name)), |
| usedNames, |
| defaultSuffixes()); |
| |
| /// Finds all message types used by this service. |
| /// |
| /// Puts the types found in [_deps] and [_transitiveDeps]. |
| /// If a type name can't be resolved, puts it in [_undefinedDeps]. |
| /// Precondition: messages have been registered and resolved. |
| void resolve(GenerationContext ctx) { |
| for (var m in _methodDescriptors) { |
| _addDependency(ctx, m.inputType, 'input type of ${m.name}'); |
| _addDependency(ctx, m.outputType, 'output type of ${m.name}'); |
| } |
| _resolveMoreTypes(ctx); |
| } |
| |
| /// Hook for a subclass to register any additional types it uses. |
| void _resolveMoreTypes(GenerationContext ctx) {} |
| |
| /// Adds a dependency on the given message type. |
| /// |
| /// If the type name can't be resolved, adds it to [_undefinedDeps]. |
| /// If it can, recursively adds the types of its fields as well. |
| void _addDependency(GenerationContext ctx, String fqname, String location) { |
| if (_deps.containsKey(fqname)) return; // Already added. |
| |
| final mg = ctx.getFieldType(fqname) as MessageGenerator; |
| if (mg == null) { |
| _undefinedDeps[fqname] = location; |
| return; |
| } |
| _addDepsRecursively(mg, 0); |
| } |
| |
| void _addDepsRecursively(MessageGenerator mg, int depth) { |
| if (_transitiveDeps.containsKey(mg.dottedName)) { |
| // Already added, but perhaps at a different depth. |
| if (depth == 0) _deps[mg.dottedName] = mg; |
| return; |
| } |
| mg.checkResolved(); |
| if (depth == 0) _deps[mg.dottedName] = mg; |
| _transitiveDeps[mg.dottedName] = mg; |
| for (var field in mg._fieldList) { |
| if (field.baseType.isGroup || field.baseType.isMessage) { |
| _addDepsRecursively( |
| field.baseType.generator as MessageGenerator, depth + 1); |
| } |
| } |
| } |
| |
| /// Adds dependencies of [generate] to [imports]. |
| /// |
| /// For each .pb.dart file that the generated code needs to import, |
| /// add its generator. |
| void addImportsTo(Set<FileGenerator> imports) { |
| for (var mg in _deps.values) { |
| imports.add(mg.fileGen); |
| } |
| } |
| |
| /// Adds dependencies of [generateConstants] to [imports]. |
| /// |
| /// For each .pbjson.dart file that the generated code needs to import, |
| /// add its generator. |
| void addConstantImportsTo(Set<FileGenerator> imports) { |
| for (var mg in _transitiveDeps.values) { |
| imports.add(mg.fileGen); |
| } |
| } |
| |
| /// Returns the Dart class name to use for a message type or throws an |
| /// exception if it can't be resolved. |
| /// |
| /// When generating the main file (if [forMainFile] is true), all imports |
| /// should be prefixed unless the target file is the main file (the client |
| /// generator calls this method). Otherwise, prefix everything. |
| String _getDartClassName(String fqname, {bool forMainFile = false}) { |
| var mg = _deps[fqname]; |
| if (mg == null) { |
| var location = _undefinedDeps[fqname]; |
| throw 'FAILURE: Unknown type reference ($fqname) for $location'; |
| } |
| if (forMainFile && fileGen.protoFileUri == mg.fileGen.protoFileUri) { |
| // If it's the same file, we import it without using "as". |
| return mg.classname; |
| } |
| return mg.fileImportPrefix + '.' + mg.classname; |
| } |
| |
| List<MethodDescriptorProto> get _methodDescriptors => _descriptor.method; |
| |
| String _methodName(String name) => lowerCaseFirstLetter(name); |
| |
| String get _parentClass => _generatedService; |
| |
| void _generateStub(IndentingWriter out, MethodDescriptorProto m) { |
| var methodName = _methodName(m.name); |
| var inputClass = _getDartClassName(m.inputType); |
| var outputClass = _getDartClassName(m.outputType); |
| |
| out.println('$_future<$outputClass> $methodName(' |
| '$_serverContext ctx, $inputClass request);'); |
| } |
| |
| void _generateStubs(IndentingWriter out) { |
| for (var m in _methodDescriptors) { |
| _generateStub(out, m); |
| } |
| out.println(); |
| } |
| |
| void _generateRequestMethod(IndentingWriter out) { |
| out.addBlock( |
| '$_generatedMessage createRequest($coreImportPrefix.String method) {', |
| '}', () { |
| out.addBlock('switch (method) {', '}', () { |
| for (var m in _methodDescriptors) { |
| var inputClass = _getDartClassName(m.inputType); |
| out.println("case '${m.name}': return $inputClass();"); |
| } |
| out.println('default: ' |
| "throw $coreImportPrefix.ArgumentError('Unknown method: \$method');"); |
| }); |
| }); |
| out.println(); |
| } |
| |
| void _generateDispatchMethod(out) { |
| out.addBlock( |
| '$_future<$_generatedMessage> handleCall($_serverContext ctx, ' |
| '$coreImportPrefix.String method, $_generatedMessage request) {', |
| '}', () { |
| out.addBlock('switch (method) {', '}', () { |
| for (var m in _methodDescriptors) { |
| var methodName = _methodName(m.name); |
| var inputClass = _getDartClassName(m.inputType); |
| out.println("case '${m.name}': return this.$methodName" |
| '(ctx, request as $inputClass);'); |
| } |
| out.println('default: ' |
| "throw $coreImportPrefix.ArgumentError('Unknown method: \$method');"); |
| }); |
| }); |
| out.println(); |
| } |
| |
| /// Hook for generating members added in subclasses. |
| void _generateMoreClassMembers(IndentingWriter out) {} |
| |
| void generate(IndentingWriter out) { |
| out.addBlock( |
| 'abstract class $classname extends ' |
| '$_parentClass {', |
| '}', () { |
| _generateStubs(out); |
| _generateRequestMethod(out); |
| _generateDispatchMethod(out); |
| _generateMoreClassMembers(out); |
| out.println( |
| '$coreImportPrefix.Map<$coreImportPrefix.String, $coreImportPrefix.dynamic> get \$json => $jsonConstant;'); |
| out.println( |
| '$coreImportPrefix.Map<$coreImportPrefix.String, $coreImportPrefix.Map<$coreImportPrefix.String,' |
| ' $coreImportPrefix.dynamic>> get \$messageJson => $messageJsonConstant;'); |
| }); |
| out.println(); |
| } |
| |
| String get jsonConstant => '$classname\$json'; |
| String get messageJsonConstant => '$classname\$messageJson'; |
| |
| /// Writes Dart constants for the service and message descriptors. |
| /// |
| /// The map includes an entry for every message type that might need |
| /// to be read or written (assuming the type name resolved). |
| void generateConstants(IndentingWriter out) { |
| out.print('const $coreImportPrefix.Map<$coreImportPrefix.String,' |
| ' $coreImportPrefix.dynamic> $jsonConstant = '); |
| writeJsonConst(out, _descriptor.writeToJsonMap()); |
| out.println(';'); |
| out.println(); |
| |
| var typeConstants = <String, String>{}; |
| for (var key in _transitiveDeps.keys) { |
| typeConstants[key] = _transitiveDeps[key].getJsonConstant(fileGen); |
| } |
| |
| out.println('@$coreImportPrefix.Deprecated' |
| '(\'Use $binaryDescriptorName instead\')'); |
| out.addBlock( |
| 'const $coreImportPrefix.Map<$coreImportPrefix.String,' |
| ' $coreImportPrefix.Map<$coreImportPrefix.String,' |
| ' $coreImportPrefix.dynamic>> $messageJsonConstant = const {', |
| '};', () { |
| for (var key in typeConstants.keys) { |
| var typeConst = typeConstants[key]; |
| out.println("'$key': $typeConst,"); |
| } |
| }); |
| out.println(); |
| |
| if (_undefinedDeps.isNotEmpty) { |
| for (var name in _undefinedDeps.keys) { |
| var location = _undefinedDeps[name]; |
| out.println("// can't resolve ($name) used by $location"); |
| } |
| out.println(); |
| } |
| } |
| |
| String get binaryDescriptorName { |
| var prefix = lowerCaseFirstLetter(classname); |
| if (prefix.endsWith('Base')) { |
| prefix = prefix.substring(0, prefix.length - 4); |
| } |
| return '${prefix}Descriptor'; |
| } |
| |
| static final String _future = '$asyncImportPrefix.Future'; |
| static final String _generatedMessage = |
| '$protobufImportPrefix.GeneratedMessage'; |
| static final String _serverContext = '$protobufImportPrefix.ServerContext'; |
| static final String _generatedService = |
| '$protobufImportPrefix.GeneratedService'; |
| } |