| Index: lib/message_generator.dart | 
| diff --git a/lib/message_generator.dart b/lib/message_generator.dart | 
| index 1deaa6f2587d37dd9a03cb3268c000502b51a3a5..6dc1b2556c95dac24ad215076c481a5e6289d721 100644 | 
| --- a/lib/message_generator.dart | 
| +++ b/lib/message_generator.dart | 
| @@ -31,27 +31,23 @@ class MessageGenerator extends ProtobufContainer { | 
| 'extensionsAreInitialized', 'mergeFromMessage', 'mergeUnknownFields', | 
| '==', 'info_', 'GeneratedMessage', 'Object']; | 
|  | 
| -  // List of names that can't be used in a subclass that implements Map. | 
| -  static final List<String> reservedNamesForMap = | 
| -    ['addAll', 'containsKey', 'containsValue', 'forEach', 'putIfAbsent', | 
| -     'remove', 'isEmpty', 'isNotEmpty', 'keys', 'length', 'values']; | 
| - | 
| -  // This should match the extension in dart_options.proto. | 
| -  static const int implementMapOption = 95333044; | 
| - | 
| -  // Returns true if the implement_map option is turned on for the message. | 
| -  static bool _shouldImplementMap(DescriptorProto desc, bool defaultValue) { | 
| +  // Returns the mixin for this message, or null if none. | 
| +  static PbMixin _getMixin(DescriptorProto desc, PbMixin defaultValue) { | 
| if (!desc.hasOptions()) return defaultValue; | 
| +    if (!desc.options.hasExtension(Dart_options.mixin)) return defaultValue; | 
|  | 
| -    var val = desc.options.unknownFields.getField(implementMapOption); | 
| -    if (val == null || val.length != 1) return defaultValue; | 
| - | 
| -    return val.values[0] == 1; | 
| +    String name = desc.options.getExtension(Dart_options.mixin); | 
| +    if (name.isEmpty) return null; // don't use a mixin (override any default) | 
| +    var mixin = findMixin(name); | 
| +    if (mixin == null) { | 
| +      throw("unknown mixin class: ${name}"); | 
| +    } | 
| +    return mixin; | 
| } | 
|  | 
| final String classname; | 
| final String fqname; | 
| -  final bool implementsMap; | 
| +  final PbMixin mixin; | 
|  | 
| final ProtobufContainer _parent; | 
| final GenerationContext _context; | 
| @@ -64,7 +60,7 @@ class MessageGenerator extends ProtobufContainer { | 
|  | 
| MessageGenerator( | 
| DescriptorProto descriptor, ProtobufContainer parent, this._context, | 
| -      bool implementMapByDefault) | 
| +      PbMixin defaultMixin) | 
| : _descriptor = descriptor, | 
| _parent = parent, | 
| classname = (parent.classname == '') ? | 
| @@ -72,7 +68,7 @@ class MessageGenerator extends ProtobufContainer { | 
| fqname = (parent == null || parent.fqname == null) ? descriptor.name : | 
| (parent.fqname == '.' ? | 
| '.${descriptor.name}' : '${parent.fqname}.${descriptor.name}'), | 
| -        implementsMap = _shouldImplementMap(descriptor, implementMapByDefault) { | 
| +        mixin = _getMixin(descriptor, defaultMixin) { | 
| _context.register(this); | 
|  | 
| for (EnumDescriptorProto e in _descriptor.enumType) { | 
| @@ -81,7 +77,7 @@ class MessageGenerator extends ProtobufContainer { | 
|  | 
| for (DescriptorProto n in _descriptor.nestedType) { | 
| _messageGenerators.add( | 
| -          new MessageGenerator(n, this, _context, implementMapByDefault)); | 
| +          new MessageGenerator(n, this, _context, defaultMixin)); | 
| } | 
|  | 
| for (FieldDescriptorProto x in _descriptor.extension) { | 
| @@ -91,14 +87,14 @@ class MessageGenerator extends ProtobufContainer { | 
|  | 
| String get package => _parent.package; | 
|  | 
| -  bool get needsMapMixinImport { | 
| -    if (implementsMap) return true; | 
| - | 
| +  /// Adds all mixins used in this message and any submessages. | 
| +  void addMixinsTo(Set<PbMixin> output) { | 
| +    if (mixin != null) { | 
| +      output.addAll(mixin.findMixinsToApply()); | 
| +    } | 
| for (var m in _messageGenerators) { | 
| -      if (m.implementsMap) return true; | 
| +      m.addMixinsTo(output); | 
| } | 
| - | 
| -    return false; | 
| } | 
|  | 
| void initializeFields() { | 
| @@ -116,8 +112,8 @@ class MessageGenerator extends ProtobufContainer { | 
| _methodNames.addAll(reservedWords); | 
| _methodNames.addAll(reservedNames); | 
|  | 
| -    if (implementsMap) { | 
| -      _methodNames.addAll(reservedNamesForMap); | 
| +    if (mixin != null) { | 
| +      _methodNames.addAll(mixin.findReservedNames()); | 
| } | 
|  | 
| for (EnumGenerator e in _enumGenerators) { | 
| @@ -128,12 +124,13 @@ class MessageGenerator extends ProtobufContainer { | 
| m.generate(out); | 
| } | 
|  | 
| -    var implClause = ""; | 
| -    if (implementsMap) { | 
| -      implClause = " with MapMixin"; | 
| +    var mixinClause = ''; | 
| +    if (mixin != null) { | 
| +      var mixinNames = mixin.findMixinsToApply().map((m) => m.name); | 
| +      mixinClause = ' with ${mixinNames.join(", ")}'; | 
| } | 
|  | 
| -    out.addBlock('class ${classname} extends GeneratedMessage${implClause} {', | 
| +    out.addBlock('class ${classname} extends GeneratedMessage${mixinClause} {', | 
| '}', () | 
| { | 
| out.addBlock( | 
| @@ -223,76 +220,6 @@ class MessageGenerator extends ProtobufContainer { | 
| out.println('static PbList<${classname}>${SP}createRepeated()${SP}=>' | 
| '${SP}new PbList<${classname}>();'); | 
|  | 
| - | 
| -      if (implementsMap) { | 
| -        // clear() is inherited from GeneratedMessage. | 
| -        // Other map operations are implemented by MapMixin. | 
| -        out.println(''' | 
| -@override | 
| -operator [] (key) { | 
| -  if (key is !String) return null; | 
| -  if (!key.contains(".")) { | 
| -    var tag = getTagNumber(key); | 
| -    if (tag == null) return null; | 
| -    return getField(tag); | 
| -  } | 
| - | 
| -  var keys = key.split('.'); | 
| -  var item = this; | 
| -  for (var key in keys) { | 
| -    if (item is !GeneratedMessage) return null; | 
| -    var tag = item.getTagNumber(key); | 
| -    if (tag == null) return null; | 
| -    item = item.getField(tag); | 
| -  } | 
| - | 
| -  return item; | 
| -} | 
| - | 
| -@override | 
| -operator []= (String key, val) { | 
| -  if (!key.contains(".")) { | 
| -    var tag = _mustGetTagNumber(this, key); | 
| -    setField(tag, val); | 
| -    return; | 
| -  } | 
| - | 
| -  var keys = key.split('.'); | 
| -  var lastKey = keys.removeLast(); | 
| -  var item = this; | 
| -  for (var key in keys) { | 
| -    var tag = _mustGetTagNumber(item, key); | 
| -    item = item.getField(tag); | 
| -    if (item is !GeneratedMessage) { | 
| -      throw new ArgumentError( | 
| -          "field '\${key}' in \${info._messageName} isn't a GeneratedMessage:"); | 
| -    } | 
| -  } | 
| -  var tag = _mustGetTagNumber(item, lastKey); | 
| -  item.setField(tag, val); | 
| -} | 
| - | 
| -_mustGetTagNumber(GeneratedMessage msg, String key) { | 
| -  var tag = msg.getTagNumber(key); | 
| -  if (tag == null) { | 
| -    throw new ArgumentError( | 
| -        "field '\${key}' not found in \${msg.info_.messageName}"); | 
| -  } | 
| -  return tag; | 
| -} | 
| - | 
| -@override | 
| -get keys => info_.byName.keys; | 
| - | 
| -@override | 
| -get length => info_.byName.length; | 
| - | 
| -remove(key) { | 
| -  throw new UnsupportedError("remove() not supported by \${info_.messageName}"); | 
| -} | 
| -'''); | 
| -      } | 
| - | 
| generateFieldsAccessorsMutators(out); | 
| }); | 
| out.println(); | 
|  |