blob: 75d8e0c879bea90f26738eafabaf0ee07f686d3e [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.
part of protoc;
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.
/// Populated by [resolve].
final _deps = <String, MessageGenerator>{};
/// The message types needed transitively by this service.
///
/// The key is the fully qualified name.
/// Populated by [resolve].
final _transitiveDeps = <String, MessageGenerator>{};
/// Maps each undefined type to a string describing its location.
///
/// Populated by [resolve].
final _undefinedDeps = <String, String>{};
ServiceGenerator(this._descriptor, this.fileGen);
String get classname {
if (_descriptor.name.endsWith("Service")) {
return _descriptor.name + "Base"; // avoid: ServiceServiceBase
} else {
return _descriptor.name + "ServiceBase";
}
}
/// 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.
MessageGenerator mg = ctx.getFieldType(fqname);
if (mg == null) {
_undefinedDeps[fqname] = location;
return;
}
_addDepsRecursively(mg, 0);
}
void _addDepsRecursively(MessageGenerator mg, int depth) {
if (_transitiveDeps.containsKey(mg.fqname)) {
// Already added, but perhaps at a different depth.
if (depth == 0) _deps[mg.fqname] = mg;
return;
}
mg.checkResolved();
if (depth == 0) _deps[mg.fqname] = mg;
_transitiveDeps[mg.fqname] = mg;
for (var field in mg._fieldList) {
if (field.baseType.isGroup || field.baseType.isMessage) {
_addDepsRecursively(field.baseType.generator, 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.
///
/// Throws an exception if it can't be resolved.
String _getDartClassName(String fqname) {
var mg = _deps[fqname];
if (mg == null) {
var location = _undefinedDeps[fqname];
throw 'FAILURE: Unknown type reference (${fqname}) for ${location}';
}
if (fileGen.package == mg.fileGen.package || mg.fileGen.package == "") {
// It's either the same file, or another file with the same package.
// (In the second case, we import it without using "as".)
return mg.classname;
}
return mg.packageImportPrefix + "." + mg.classname;
}
List<MethodDescriptorProto> get _methodDescriptors => _descriptor.method;
String _methodName(String name) =>
name.substring(0, 1).toLowerCase() + name.substring(1);
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 (MethodDescriptorProto m in _methodDescriptors) {
_generateStub(out, m);
}
out.println();
}
void _generateRequestMethod(IndentingWriter out) {
out.addBlock('GeneratedMessage createRequest(String method) {', '}', () {
out.addBlock("switch (method) {", "}", () {
for (MethodDescriptorProto m in _methodDescriptors) {
var inputClass = _getDartClassName(m.inputType);
out.println("case '${m.name}': return new $inputClass();");
}
out.println("default: "
"throw new ArgumentError('Unknown method: \$method');");
});
});
out.println();
}
void _generateDispatchMethod(out) {
out.addBlock(
'Future<GeneratedMessage> handleCall(ServerContext ctx, '
'String method, GeneratedMessage request) {',
'}', () {
out.addBlock("switch (method) {", "}", () {
for (MethodDescriptorProto m in _methodDescriptors) {
var methodName = _methodName(m.name);
out.println(
"case '${m.name}': return this.$methodName(ctx, request);");
}
out.println("default: "
"throw new 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("Map<String, dynamic> get \$json => $jsonConstant;");
out.println("Map<String, Map<String, dynamic>> get \$messageJson =>"
" $messageJsonConstant;");
});
out.println();
}
String get jsonConstant => "${_descriptor.name}\$json";
String get messageJsonConstant => "${_descriptor.name}\$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 $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.addBlock("const $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();
}
}
}