blob: 3fcd1a943abd00e3f19acf5ca15058bbeff197bd [file] [log] [blame]
// 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';
}