blob: 7a7d32b680583d2ca948aa1334a5cca80bf4ee01 [file] [log] [blame]
diff --git a/lib/message_generator.dart b/../../dart-protoc-plugin/lib/message_generator.dart
index 5c9c072..65ca39c 100644
--- a/lib/message_generator.dart
+++ b/../../dart-protoc-plugin/lib/message_generator.dart
@@ -4,6 +4,19 @@
part of protoc;
+class OneofEnumGenerator {
+ static void generate(
+ IndentingWriter out, String classname, List<ProtobufField> fields) {
+ out.addBlock('enum ${classname} {', '}\n', () {
+ for (ProtobufField field in fields) {
+ final name = field.memberNames.fieldName;
+ out.println('$name, ');
+ }
+ out.println('unset');
+ });
+ }
+}
+
class MessageGenerator extends ProtobufContainer {
/// Returns the mixin for this message, or null if none.
///
@@ -56,37 +69,46 @@ class MessageGenerator extends ProtobufContainer {
final List<EnumGenerator> _enumGenerators = <EnumGenerator>[];
final List<MessageGenerator> _messageGenerators = <MessageGenerator>[];
final List<ExtensionGenerator> _extensionGenerators = <ExtensionGenerator>[];
+ // Stores the list of fields belonging to each oneof declaration identified
+ // by the index in the containing types's oneof_decl list.
+ final List<List<ProtobufField>> _oneofFields;
+ List<OneofNames> _oneofNames;
// populated by resolve()
List<ProtobufField> _fieldList;
+ Set<String> _usedTopLevelNames;
+
MessageGenerator(
DescriptorProto descriptor,
ProtobufContainer parent,
Map<String, PbMixin> declaredMixins,
PbMixin defaultMixin,
- Set<String> usedNames)
+ this._usedTopLevelNames)
: _descriptor = descriptor,
_parent = parent,
- classname = messageOrEnumClassName(descriptor.name, usedNames,
+ classname = messageOrEnumClassName(descriptor.name, _usedTopLevelNames,
parent: parent?.classname ?? ''),
assert(parent != null),
fullName = parent.fullName == ''
? descriptor.name
: '${parent.fullName}.${descriptor.name}',
mixin = _getMixin(descriptor, parent.fileGen.descriptor, declaredMixins,
- defaultMixin) {
+ defaultMixin),
+ _oneofFields =
+ List.generate(descriptor.oneofDecl.length, (int index) => []) {
for (EnumDescriptorProto e in _descriptor.enumType) {
- _enumGenerators.add(new EnumGenerator(e, this, usedNames));
+ _enumGenerators.add(new EnumGenerator(e, this, _usedTopLevelNames));
}
for (DescriptorProto n in _descriptor.nestedType) {
_messageGenerators.add(new MessageGenerator(
- n, this, declaredMixins, defaultMixin, usedNames));
+ n, this, declaredMixins, defaultMixin, _usedTopLevelNames));
}
for (FieldDescriptorProto x in _descriptor.extension) {
- _extensionGenerators.add(new ExtensionGenerator(x, this, usedNames));
+ _extensionGenerators
+ .add(new ExtensionGenerator(x, this, _usedTopLevelNames));
}
}
@@ -138,12 +160,19 @@ class MessageGenerator extends ProtobufContainer {
if (_fieldList != null) throw new StateError("message already resolved");
var reserved = mixin?.findReservedNames() ?? const <String>[];
- var fields = messageFieldNames(_descriptor, reserved: reserved);
+ MemberNames members = messageMemberNames(
+ _descriptor, classname, _usedTopLevelNames,
+ reserved: reserved);
_fieldList = <ProtobufField>[];
- for (MemberNames names in fields.values) {
- _fieldList.add(new ProtobufField.message(names, this, ctx));
+ for (FieldNames names in members.fieldNames) {
+ ProtobufField field = new ProtobufField.message(names, this, ctx);
+ if (field.descriptor.hasOneofIndex()) {
+ _oneofFields[field.descriptor.oneofIndex].add(field);
+ }
+ _fieldList.add(field);
}
+ _oneofNames = members.oneofNames;
for (var m in _messageGenerators) {
m.resolve(ctx);
@@ -223,6 +252,11 @@ class MessageGenerator extends ProtobufContainer {
m.generate(out);
}
+ for (OneofNames oneof in _oneofNames) {
+ OneofEnumGenerator.generate(
+ out, oneof.oneofEnumName, _oneofFields[oneof.index]);
+ }
+
var mixinClause = '';
if (mixin != null) {
var mixinNames = mixin.findMixinsToApply().map((m) => m.name);
@@ -235,6 +269,17 @@ class MessageGenerator extends ProtobufContainer {
out.addBlock(
'class ${classname} extends $_protobufImportPrefix.GeneratedMessage${mixinClause} {',
'}', () {
+ for (OneofNames oneof in _oneofNames) {
+ out.addBlock(
+ 'static const Map<int, ${oneof.oneofEnumName}> ${oneof.byTagMapName} = {',
+ '};', () {
+ for (ProtobufField field in _oneofFields[oneof.index]) {
+ out.println(
+ '${field.number} : ${oneof.oneofEnumName}.${field.memberNames.fieldName},');
+ }
+ out.println('0 : ${oneof.oneofEnumName}.unset');
+ });
+ }
out.addBlock(
'static final $_protobufImportPrefix.BuilderInfo _i = '
'new $_protobufImportPrefix.BuilderInfo(\'${messageName}\'$packageClause)',
@@ -244,6 +289,12 @@ class MessageGenerator extends ProtobufContainer {
out.println(field.generateBuilderInfoCall(fileGen, dartFieldName));
}
+ for (int oneof = 0; oneof < _oneofFields.length; oneof++) {
+ List<int> tags =
+ _oneofFields[oneof].map((ProtobufField f) => f.number).toList();
+ out.println("..oo($oneof, ${tags})");
+ }
+
if (_descriptor.extensionRange.length > 0) {
out.println('..hasExtensions = true');
}
@@ -375,12 +426,23 @@ class MessageGenerator extends ProtobufContainer {
}
void generateFieldsAccessorsMutators(IndentingWriter out) {
+ _oneofNames
+ .forEach((OneofNames oneof) => generateoneOfAccessors(out, oneof));
+
for (ProtobufField field in _fieldList) {
out.println();
generateFieldAccessorsMutators(field, out);
}
}
+ void generateoneOfAccessors(IndentingWriter out, OneofNames oneof) {
+ out.println();
+ out.println("${oneof.oneofEnumName} ${oneof.whichOneofMethodName}() "
+ "=> ${oneof.byTagMapName}[\$_whichOneof(${oneof.index})];");
+ out.println('void ${oneof.clearMethodName}() '
+ '=> clearField(\$_whichOneof(${oneof.index}));');
+ }
+
void generateFieldAccessorsMutators(
ProtobufField field, IndentingWriter out) {
var fieldTypeString = field.getDartType(fileGen);
diff --git a/lib/names.dart b/../../dart-protoc-plugin/lib/names.dart
index a974bec..251f9f0 100644
--- a/lib/names.dart
+++ b/../../dart-protoc-plugin/lib/names.dart
@@ -10,8 +10,14 @@ import 'package:protoc_plugin/src/descriptor.pb.dart';
/// to check its type and range.
const checkItem = '\$checkItem';
-/// The Dart member names in a GeneratedMessage subclass for one protobuf field.
class MemberNames {
+ List<FieldNames> fieldNames;
+ List<OneofNames> oneofNames;
+ MemberNames(this.fieldNames, this.oneofNames);
+}
+
+/// The Dart member names in a GeneratedMessage subclass for one protobuf field.
+class FieldNames {
/// The descriptor of the field these member names apply to.
final FieldDescriptorProto descriptor;
@@ -32,10 +38,33 @@ class MemberNames {
/// `null` for repeated fields.
final String clearMethodName;
- MemberNames(this.descriptor, this.index, this.fieldName,
+ FieldNames(this.descriptor, this.index, this.fieldName,
{this.hasMethodName, this.clearMethodName});
}
+// The Dart names associated with a oneof declaration.
+class OneofNames {
+ final OneofDescriptorProto descriptor;
+
+ // Index in the containing type's oneof_decl list.
+ final int index;
+
+ // Identifier for the generated whichX() method, without braces.
+ final String whichOneofMethodName;
+
+ // Identifier for the generated clearX() method, without braces.
+ final String clearMethodName;
+
+ // Identifier for the generated enum definition.
+ final String oneofEnumName;
+
+ // Identifier for the _XByTag map.
+ final String byTagMapName;
+
+ OneofNames(this.descriptor, this.index, this.clearMethodName,
+ this.whichOneofMethodName, this.oneofEnumName, this.byTagMapName);
+}
+
/// Move any initial underscores in [input] to the end.
///
/// According to the spec identifiers cannot start with _, but it seems to be
@@ -143,6 +172,13 @@ Iterable<String> defaultSuffixes() sync* {
}
}
+String oneofEnumClassName(
+ String descriptorName, Set<String> usedNames, String parent) {
+ descriptorName = '${parent}_${underscoresToCamelCase(descriptorName)}';
+ return disambiguateName(
+ avoidInitialUnderscore(descriptorName), usedNames, defaultSuffixes());
+}
+
/// Chooses the name of the Dart class to generate for a proto message or enum.
///
/// For a nested message or enum, [parent] should be provided
@@ -170,17 +206,18 @@ Iterable<String> enumSuffixes() sync* {
}
}
-/// Chooses the GeneratedMessage member names for each field.
+/// Chooses the GeneratedMessage member names for each field and names
+/// associated with each oneof declaration.
///
/// Additional names to avoid can be supplied using [reserved].
/// (This should only be used for mixins.)
///
-/// Returns a map from the field name in the .proto file to its
-/// corresponding MemberNames.
+/// Returns [MemberNames] which holds a list with [FieldNames] and a list with [OneofNames].
///
/// Throws [DartNameOptionException] if a field has this option and
/// it's set to an invalid name.
-Map<String, MemberNames> messageFieldNames(DescriptorProto descriptor,
+MemberNames messageMemberNames(DescriptorProto descriptor,
+ String parentClassName, Set<String> usedTopLevelNames,
{Iterable<String> reserved = const []}) {
var sorted = new List<FieldDescriptorProto>.from(descriptor.field)
..sort((FieldDescriptorProto a, FieldDescriptorProto b) {
@@ -200,10 +237,10 @@ Map<String, MemberNames> messageFieldNames(DescriptorProto descriptor,
..addAll(reservedMemberNames)
..addAll(reserved);
- var memberNames = <String, MemberNames>{};
+ List<FieldNames> fieldNames = <FieldNames>[];
- void takeNames(MemberNames chosen) {
- memberNames[chosen.descriptor.name] = chosen;
+ void takeFieldNames(FieldNames chosen) {
+ fieldNames.add(chosen);
existingNames.add(chosen.fieldName);
if (chosen.hasMethodName != null) {
@@ -219,7 +256,7 @@ Map<String, MemberNames> messageFieldNames(DescriptorProto descriptor,
// Explicitly setting a name that's already taken is a build error.
for (var field in sorted) {
if (_nameOption(field).isNotEmpty) {
- takeNames(_memberNamesFromOption(
+ takeFieldNames(_memberNamesFromOption(
descriptor, field, indexes[field.name], existingNames));
}
}
@@ -229,23 +266,55 @@ Map<String, MemberNames> messageFieldNames(DescriptorProto descriptor,
for (var field in sorted) {
if (_nameOption(field).isEmpty) {
var index = indexes[field.name];
- takeNames(_unusedMemberNames(field, index, existingNames));
+ takeFieldNames(_unusedMemberNames(field, index, existingNames));
}
}
- // Return a map with entries in sorted order.
- var result = <String, MemberNames>{};
- for (var field in sorted) {
- result[field.name] = memberNames[field.name];
+ List<OneofNames> oneofNames = <OneofNames>[];
+
+ void takeOneofNames(OneofNames chosen) {
+ oneofNames.add(chosen);
+
+ if (chosen.whichOneofMethodName != null) {
+ existingNames.add(chosen.whichOneofMethodName);
+ }
+ if (chosen.clearMethodName != null) {
+ existingNames.add(chosen.clearMethodName);
+ }
+ if (chosen.byTagMapName != null) {
+ existingNames.add(chosen.byTagMapName);
+ }
+ }
+
+ List<String> oneofNameVariants(String name) {
+ return [_defaultWhichMethodName(name), _defaultClearMethodName(name)];
}
- return result;
+
+ for (int i = 0; i < descriptor.oneofDecl.length; i++) {
+ OneofDescriptorProto oneof = descriptor.oneofDecl[i];
+
+ String oneofName = disambiguateName(
+ underscoresToCamelCase(oneof.name), existingNames, defaultSuffixes(),
+ generateVariants: oneofNameVariants);
+
+ String oneofEnumName =
+ oneofEnumClassName(oneof.name, usedTopLevelNames, parentClassName);
+
+ String enumMapName = disambiguateName(
+ '_${oneofEnumName}ByTag', existingNames, defaultSuffixes());
+
+ takeOneofNames(OneofNames(oneof, i, _defaultClearMethodName(oneofName),
+ _defaultWhichMethodName(oneofName), oneofEnumName, enumMapName));
+ }
+
+ return MemberNames(fieldNames, oneofNames);
}
/// Chooses the member names for a field that has the 'dart_name' option.
///
/// If the explicitly-set Dart name is already taken, throw an exception.
/// (Fails the build.)
-MemberNames _memberNamesFromOption(DescriptorProto message,
+FieldNames _memberNamesFromOption(DescriptorProto message,
FieldDescriptorProto field, int index, Set<String> existingNames) {
// TODO(skybrian): provide more context in errors (filename).
var where = "${message.name}.${field.name}";
@@ -268,7 +337,7 @@ MemberNames _memberNamesFromOption(DescriptorProto message,
checkAvailable(name);
if (_isRepeated(field)) {
- return new MemberNames(field, index, name);
+ return new FieldNames(field, index, name);
}
String hasMethod = "has${_capitalize(name)}";
@@ -277,7 +346,7 @@ MemberNames _memberNamesFromOption(DescriptorProto message,
String clearMethod = "clear${_capitalize(name)}";
checkAvailable(clearMethod);
- return new MemberNames(field, index, name,
+ return new FieldNames(field, index, name,
hasMethodName: hasMethod, clearMethodName: clearMethod);
}
@@ -289,10 +358,10 @@ Iterable<String> _memberNamesSuffix(int number) sync* {
}
}
-MemberNames _unusedMemberNames(
+FieldNames _unusedMemberNames(
FieldDescriptorProto field, int index, Set<String> existingNames) {
if (_isRepeated(field)) {
- return new MemberNames(
+ return new FieldNames(
field,
index,
disambiguateName(_defaultFieldName(_fieldMethodSuffix(field)),
@@ -310,7 +379,7 @@ MemberNames _unusedMemberNames(
String name = disambiguateName(_fieldMethodSuffix(field), existingNames,
_memberNamesSuffix(field.number),
generateVariants: generateNameVariants);
- return new MemberNames(field, index, _defaultFieldName(name),
+ return new FieldNames(field, index, _defaultFieldName(name),
hasMethodName: _defaultHasMethodName(name),
clearMethodName: _defaultClearMethodName(name));
}
@@ -327,6 +396,9 @@ String _defaultHasMethodName(String fieldMethodSuffix) =>
String _defaultClearMethodName(String fieldMethodSuffix) =>
'clear$fieldMethodSuffix';
+String _defaultWhichMethodName(String oneofMethodSuffix) =>
+ 'which$oneofMethodSuffix';
+
/// The suffix to use for this field in Dart method names.
/// (It should be camelcase and begin with an uppercase letter.)
String _fieldMethodSuffix(FieldDescriptorProto field) {
@@ -334,7 +406,7 @@ String _fieldMethodSuffix(FieldDescriptorProto field) {
if (name.isNotEmpty) return _capitalize(name);
if (field.type != FieldDescriptorProto_Type.TYPE_GROUP) {
- return _underscoresToCamelCase(field.name);
+ return underscoresToCamelCase(field.name);
}
// For groups, use capitalization of 'typeName' rather than 'name'.
@@ -343,10 +415,10 @@ String _fieldMethodSuffix(FieldDescriptorProto field) {
if (index != -1) {
name = name.substring(index + 1);
}
- return _underscoresToCamelCase(name);
+ return underscoresToCamelCase(name);
}
-String _underscoresToCamelCase(s) => s.split('_').map(_capitalize).join('');
+String underscoresToCamelCase(s) => s.split('_').map(_capitalize).join('');
String _capitalize(s) =>
s.isEmpty ? s : '${s[0].toUpperCase()}${s.substring(1)}';
diff --git a/lib/protobuf_field.dart b/../../dart-protoc-plugin/lib/protobuf_field.dart
index 83eca16..3482161 100644
--- a/lib/protobuf_field.dart
+++ b/../../dart-protoc-plugin/lib/protobuf_field.dart
@@ -20,20 +20,20 @@ class ProtobufField {
final FieldDescriptorProto descriptor;
/// Dart names within a GeneratedMessage or `null` for an extension.
- final MemberNames memberNames;
+ final FieldNames memberNames;
final String fullName;
final BaseType baseType;
ProtobufField.message(
- MemberNames names, ProtobufContainer parent, GenerationContext ctx)
+ FieldNames names, ProtobufContainer parent, GenerationContext ctx)
: this._(names.descriptor, names, parent, ctx);
ProtobufField.extension(FieldDescriptorProto descriptor,
ProtobufContainer parent, GenerationContext ctx)
: this._(descriptor, null, parent, ctx);
- ProtobufField._(FieldDescriptorProto descriptor, MemberNames dartNames,
+ ProtobufField._(FieldDescriptorProto descriptor, FieldNames dartNames,
ProtobufContainer parent, GenerationContext ctx)
: this.descriptor = descriptor,
this.memberNames = dartNames,