1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2008 Google Inc.  All rights reserved.
3 // https://developers.google.com/protocol-buffers/
4 //
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions are
7 // met:
8 //
9 //     * Redistributions of source code must retain the above copyright
10 // notice, this list of conditions and the following disclaimer.
11 //     * Redistributions in binary form must reproduce the above
12 // copyright notice, this list of conditions and the following disclaimer
13 // in the documentation and/or other materials provided with the
14 // distribution.
15 //     * Neither the name of Google Inc. nor the names of its
16 // contributors may be used to endorse or promote products derived from
17 // this software without specific prior written permission.
18 //
19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 
31 package com.google.protobuf;
32 
33 import com.google.protobuf.Descriptors.EnumValueDescriptor;
34 import com.google.protobuf.Descriptors.FieldDescriptor;
35 import com.google.protobuf.Descriptors.OneofDescriptor;
36 import com.google.protobuf.Internal.EnumLite;
37 
38 import java.io.IOException;
39 import java.io.InputStream;
40 import java.util.Arrays;
41 import java.util.Collections;
42 import java.util.HashMap;
43 import java.util.Iterator;
44 import java.util.List;
45 import java.util.Map;
46 
47 /**
48  * A partial implementation of the {@link Message} interface which implements
49  * as many methods of that interface as possible in terms of other methods.
50  *
51  * @author kenton@google.com Kenton Varda
52  */
53 public abstract class AbstractMessage
54     // TODO(dweis): Update GeneratedMessage to parameterize with MessageType and BuilderType.
55     extends AbstractMessageLite
56     implements Message {
57 
58   @Override
isInitialized()59   public boolean isInitialized() {
60     return MessageReflection.isInitialized(this);
61   }
62 
63   /**
64    * Interface for the parent of a Builder that allows the builder to
65    * communicate invalidations back to the parent for use when using nested
66    * builders.
67    */
68   protected interface BuilderParent {
69 
70     /**
71      * A builder becomes dirty whenever a field is modified -- including fields
72      * in nested builders -- and becomes clean when build() is called.  Thus,
73      * when a builder becomes dirty, all its parents become dirty as well, and
74      * when it becomes clean, all its children become clean.  The dirtiness
75      * state is used to invalidate certain cached values.
76      * <br>
77      * To this end, a builder calls markDirty() on its parent whenever it
78      * transitions from clean to dirty.  The parent must propagate this call to
79      * its own parent, unless it was already dirty, in which case the
80      * grandparent must necessarily already be dirty as well.  The parent can
81      * only transition back to "clean" after calling build() on all children.
82      */
markDirty()83     void markDirty();
84   }
85 
86   /** Create a nested builder. */
newBuilderForType(BuilderParent parent)87   protected Message.Builder newBuilderForType(BuilderParent parent) {
88     throw new UnsupportedOperationException("Nested builder is not supported for this type.");
89   }
90 
91 
92   @Override
findInitializationErrors()93   public List<String> findInitializationErrors() {
94     return MessageReflection.findMissingFields(this);
95   }
96 
97   @Override
getInitializationErrorString()98   public String getInitializationErrorString() {
99     return MessageReflection.delimitWithCommas(findInitializationErrors());
100   }
101 
102   /** TODO(jieluo): Clear it when all subclasses have implemented this method. */
103   @Override
hasOneof(OneofDescriptor oneof)104   public boolean hasOneof(OneofDescriptor oneof) {
105     throw new UnsupportedOperationException("hasOneof() is not implemented.");
106   }
107 
108   /** TODO(jieluo): Clear it when all subclasses have implemented this method. */
109   @Override
getOneofFieldDescriptor(OneofDescriptor oneof)110   public FieldDescriptor getOneofFieldDescriptor(OneofDescriptor oneof) {
111     throw new UnsupportedOperationException(
112         "getOneofFieldDescriptor() is not implemented.");
113   }
114 
115   @Override
toString()116   public final String toString() {
117     return TextFormat.printToString(this);
118   }
119 
120   @Override
writeTo(final CodedOutputStream output)121   public void writeTo(final CodedOutputStream output) throws IOException {
122     MessageReflection.writeMessageTo(this, getAllFields(), output, false);
123   }
124 
125   protected int memoizedSize = -1;
126 
127   @Override
getSerializedSize()128   public int getSerializedSize() {
129     int size = memoizedSize;
130     if (size != -1) {
131       return size;
132     }
133 
134     memoizedSize = MessageReflection.getSerializedSize(this, getAllFields());
135     return memoizedSize;
136   }
137 
138   @Override
equals(final Object other)139   public boolean equals(final Object other) {
140     if (other == this) {
141       return true;
142     }
143     if (!(other instanceof Message)) {
144       return false;
145     }
146     final Message otherMessage = (Message) other;
147     if (getDescriptorForType() != otherMessage.getDescriptorForType()) {
148       return false;
149     }
150     return compareFields(getAllFields(), otherMessage.getAllFields()) &&
151         getUnknownFields().equals(otherMessage.getUnknownFields());
152   }
153 
154   @Override
hashCode()155   public int hashCode() {
156     int hash = memoizedHashCode;
157     if (hash == 0) {
158       hash = 41;
159       hash = (19 * hash) + getDescriptorForType().hashCode();
160       hash = hashFields(hash, getAllFields());
161       hash = (29 * hash) + getUnknownFields().hashCode();
162       memoizedHashCode = hash;
163     }
164     return hash;
165   }
166 
toByteString(Object value)167   private static ByteString toByteString(Object value) {
168     if (value instanceof byte[]) {
169       return ByteString.copyFrom((byte[]) value);
170     } else {
171       return (ByteString) value;
172     }
173   }
174 
175   /**
176    * Compares two bytes fields. The parameters must be either a byte array or a
177    * ByteString object. They can be of different type though.
178    */
compareBytes(Object a, Object b)179   private static boolean compareBytes(Object a, Object b) {
180     if (a instanceof byte[] && b instanceof byte[]) {
181       return Arrays.equals((byte[])a, (byte[])b);
182     }
183     return toByteString(a).equals(toByteString(b));
184   }
185 
186   /**
187    * Converts a list of MapEntry messages into a Map used for equals() and
188    * hashCode().
189    */
190   @SuppressWarnings({"rawtypes", "unchecked"})
convertMapEntryListToMap(List list)191   private static Map convertMapEntryListToMap(List list) {
192     if (list.isEmpty()) {
193       return Collections.emptyMap();
194     }
195     Map result = new HashMap();
196     Iterator iterator = list.iterator();
197     Message entry = (Message) iterator.next();
198     Descriptors.Descriptor descriptor = entry.getDescriptorForType();
199     Descriptors.FieldDescriptor key = descriptor.findFieldByName("key");
200     Descriptors.FieldDescriptor value = descriptor.findFieldByName("value");
201     Object fieldValue = entry.getField(value);
202     if (fieldValue instanceof EnumValueDescriptor) {
203       fieldValue = ((EnumValueDescriptor) fieldValue).getNumber();
204     }
205     result.put(entry.getField(key), fieldValue);
206     while (iterator.hasNext()) {
207       entry = (Message) iterator.next();
208       fieldValue = entry.getField(value);
209       if (fieldValue instanceof EnumValueDescriptor) {
210         fieldValue = ((EnumValueDescriptor) fieldValue).getNumber();
211       }
212       result.put(entry.getField(key), fieldValue);
213     }
214     return result;
215   }
216 
217   /**
218    * Compares two map fields. The parameters must be a list of MapEntry
219    * messages.
220    */
221   @SuppressWarnings({"rawtypes", "unchecked"})
compareMapField(Object a, Object b)222   private static boolean compareMapField(Object a, Object b) {
223     Map ma = convertMapEntryListToMap((List) a);
224     Map mb = convertMapEntryListToMap((List) b);
225     return MapFieldLite.equals(ma, mb);
226   }
227 
228   /**
229    * Compares two set of fields.
230    * This method is used to implement {@link AbstractMessage#equals(Object)}
231    * and {@link AbstractMutableMessage#equals(Object)}. It takes special care
232    * of bytes fields because immutable messages and mutable messages use
233    * different Java type to reprensent a bytes field and this method should be
234    * able to compare immutable messages, mutable messages and also an immutable
235    * message to a mutable message.
236    */
compareFields(Map<FieldDescriptor, Object> a, Map<FieldDescriptor, Object> b)237   static boolean compareFields(Map<FieldDescriptor, Object> a,
238       Map<FieldDescriptor, Object> b) {
239     if (a.size() != b.size()) {
240       return false;
241     }
242     for (FieldDescriptor descriptor : a.keySet()) {
243       if (!b.containsKey(descriptor)) {
244         return false;
245       }
246       Object value1 = a.get(descriptor);
247       Object value2 = b.get(descriptor);
248       if (descriptor.getType() == FieldDescriptor.Type.BYTES) {
249         if (descriptor.isRepeated()) {
250           List list1 = (List) value1;
251           List list2 = (List) value2;
252           if (list1.size() != list2.size()) {
253             return false;
254           }
255           for (int i = 0; i < list1.size(); i++) {
256             if (!compareBytes(list1.get(i), list2.get(i))) {
257               return false;
258             }
259           }
260         } else {
261           // Compares a singular bytes field.
262           if (!compareBytes(value1, value2)) {
263             return false;
264           }
265         }
266       } else if (descriptor.isMapField()) {
267         if (!compareMapField(value1, value2)) {
268           return false;
269         }
270       } else {
271         // Compare non-bytes fields.
272         if (!value1.equals(value2)) {
273           return false;
274         }
275       }
276     }
277     return true;
278   }
279 
280   /**
281    * Calculates the hash code of a map field. {@code value} must be a list of
282    * MapEntry messages.
283    */
284   @SuppressWarnings("unchecked")
hashMapField(Object value)285   private static int hashMapField(Object value) {
286     return MapFieldLite.calculateHashCodeForMap(convertMapEntryListToMap((List) value));
287   }
288 
289   /** Get a hash code for given fields and values, using the given seed. */
290   @SuppressWarnings("unchecked")
hashFields(int hash, Map<FieldDescriptor, Object> map)291   protected static int hashFields(int hash, Map<FieldDescriptor, Object> map) {
292     for (Map.Entry<FieldDescriptor, Object> entry : map.entrySet()) {
293       FieldDescriptor field = entry.getKey();
294       Object value = entry.getValue();
295       hash = (37 * hash) + field.getNumber();
296       if (field.isMapField()) {
297         hash = (53 * hash) + hashMapField(value);
298       } else if (field.getType() != FieldDescriptor.Type.ENUM){
299         hash = (53 * hash) + value.hashCode();
300       } else if (field.isRepeated()) {
301         List<? extends EnumLite> list = (List<? extends EnumLite>) value;
302         hash = (53 * hash) + Internal.hashEnumList(list);
303       } else {
304         hash = (53 * hash) + Internal.hashEnum((EnumLite) value);
305       }
306     }
307     return hash;
308   }
309 
310   /**
311    * Package private helper method for AbstractParser to create
312    * UninitializedMessageException with missing field information.
313    */
314   @Override
newUninitializedMessageException()315   UninitializedMessageException newUninitializedMessageException() {
316     return Builder.newUninitializedMessageException(this);
317   }
318 
319   // =================================================================
320 
321   /**
322    * A partial implementation of the {@link Message.Builder} interface which
323    * implements as many methods of that interface as possible in terms of
324    * other methods.
325    */
326   @SuppressWarnings("unchecked")
327   public static abstract class Builder<BuilderType extends Builder<BuilderType>>
328       extends AbstractMessageLite.Builder
329       implements Message.Builder {
330     // The compiler produces an error if this is not declared explicitly.
331     @Override
clone()332     public abstract BuilderType clone();
333 
334     /** TODO(jieluo): Clear it when all subclasses have implemented this method. */
335     @Override
hasOneof(OneofDescriptor oneof)336     public boolean hasOneof(OneofDescriptor oneof) {
337       throw new UnsupportedOperationException("hasOneof() is not implemented.");
338     }
339 
340     /** TODO(jieluo): Clear it when all subclasses have implemented this method. */
341     @Override
getOneofFieldDescriptor(OneofDescriptor oneof)342     public FieldDescriptor getOneofFieldDescriptor(OneofDescriptor oneof) {
343       throw new UnsupportedOperationException(
344           "getOneofFieldDescriptor() is not implemented.");
345     }
346 
347     /** TODO(jieluo): Clear it when all subclasses have implemented this method. */
348     @Override
clearOneof(OneofDescriptor oneof)349     public BuilderType clearOneof(OneofDescriptor oneof) {
350       throw new UnsupportedOperationException("clearOneof() is not implemented.");
351     }
352 
353     @Override
clear()354     public BuilderType clear() {
355       for (final Map.Entry<FieldDescriptor, Object> entry :
356            getAllFields().entrySet()) {
357         clearField(entry.getKey());
358       }
359       return (BuilderType) this;
360     }
361 
362     @Override
findInitializationErrors()363     public List<String> findInitializationErrors() {
364       return MessageReflection.findMissingFields(this);
365     }
366 
367     @Override
getInitializationErrorString()368     public String getInitializationErrorString() {
369       return MessageReflection.delimitWithCommas(findInitializationErrors());
370     }
371 
372     @Override
internalMergeFrom(AbstractMessageLite other)373     protected BuilderType internalMergeFrom(AbstractMessageLite other) {
374       return mergeFrom((Message) other);
375     }
376 
377     @Override
mergeFrom(final Message other)378     public BuilderType mergeFrom(final Message other) {
379       if (other.getDescriptorForType() != getDescriptorForType()) {
380         throw new IllegalArgumentException(
381           "mergeFrom(Message) can only merge messages of the same type.");
382       }
383 
384       // Note:  We don't attempt to verify that other's fields have valid
385       //   types.  Doing so would be a losing battle.  We'd have to verify
386       //   all sub-messages as well, and we'd have to make copies of all of
387       //   them to insure that they don't change after verification (since
388       //   the Message interface itself cannot enforce immutability of
389       //   implementations).
390       // TODO(kenton):  Provide a function somewhere called makeDeepCopy()
391       //   which allows people to make secure deep copies of messages.
392 
393       for (final Map.Entry<FieldDescriptor, Object> entry :
394            other.getAllFields().entrySet()) {
395         final FieldDescriptor field = entry.getKey();
396         if (field.isRepeated()) {
397           for (final Object element : (List)entry.getValue()) {
398             addRepeatedField(field, element);
399           }
400         } else if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
401           final Message existingValue = (Message)getField(field);
402           if (existingValue == existingValue.getDefaultInstanceForType()) {
403             setField(field, entry.getValue());
404           } else {
405             setField(field,
406               existingValue.newBuilderForType()
407                 .mergeFrom(existingValue)
408                 .mergeFrom((Message)entry.getValue())
409                 .build());
410           }
411         } else {
412           setField(field, entry.getValue());
413         }
414       }
415 
416       mergeUnknownFields(other.getUnknownFields());
417 
418       return (BuilderType) this;
419     }
420 
421     @Override
mergeFrom(final CodedInputStream input)422     public BuilderType mergeFrom(final CodedInputStream input)
423                                  throws IOException {
424       return mergeFrom(input, ExtensionRegistry.getEmptyRegistry());
425     }
426 
427     @Override
mergeFrom( final CodedInputStream input, final ExtensionRegistryLite extensionRegistry)428     public BuilderType mergeFrom(
429         final CodedInputStream input,
430         final ExtensionRegistryLite extensionRegistry)
431         throws IOException {
432       final UnknownFieldSet.Builder unknownFields =
433         UnknownFieldSet.newBuilder(getUnknownFields());
434       while (true) {
435         final int tag = input.readTag();
436         if (tag == 0) {
437           break;
438         }
439 
440         MessageReflection.BuilderAdapter builderAdapter =
441             new MessageReflection.BuilderAdapter(this);
442         if (!MessageReflection.mergeFieldFrom(input, unknownFields,
443                                               extensionRegistry,
444                                               getDescriptorForType(),
445                                               builderAdapter,
446                                               tag)) {
447           // end group tag
448           break;
449         }
450       }
451       setUnknownFields(unknownFields.build());
452       return (BuilderType) this;
453     }
454 
455     @Override
mergeUnknownFields(final UnknownFieldSet unknownFields)456     public BuilderType mergeUnknownFields(final UnknownFieldSet unknownFields) {
457       setUnknownFields(
458         UnknownFieldSet.newBuilder(getUnknownFields())
459                        .mergeFrom(unknownFields)
460                        .build());
461       return (BuilderType) this;
462     }
463 
464     @Override
getFieldBuilder(final FieldDescriptor field)465     public Message.Builder getFieldBuilder(final FieldDescriptor field) {
466       throw new UnsupportedOperationException(
467           "getFieldBuilder() called on an unsupported message type.");
468     }
469 
470     @Override
getRepeatedFieldBuilder(final FieldDescriptor field, int index)471     public Message.Builder getRepeatedFieldBuilder(final FieldDescriptor field, int index) {
472       throw new UnsupportedOperationException(
473           "getRepeatedFieldBuilder() called on an unsupported message type.");
474     }
475 
476     @Override
toString()477     public String toString() {
478       return TextFormat.printToString(this);
479     }
480 
481     /**
482      * Construct an UninitializedMessageException reporting missing fields in
483      * the given message.
484      */
485     protected static UninitializedMessageException
newUninitializedMessageException(Message message)486         newUninitializedMessageException(Message message) {
487       return new UninitializedMessageException(
488           MessageReflection.findMissingFields(message));
489     }
490 
491     /**
492      * Used to support nested builders and called to mark this builder as clean.
493      * Clean builders will propagate the {@link BuilderParent#markDirty()} event
494      * to their parent builders, while dirty builders will not, as their parents
495      * should be dirty already.
496      *
497      * NOTE: Implementations that don't support nested builders don't need to
498      * override this method.
499      */
markClean()500     void markClean() {
501       throw new IllegalStateException("Should be overridden by subclasses.");
502     }
503 
504     /**
505      * Used to support nested builders and called when this nested builder is
506      * no longer used by its parent builder and should release the reference
507      * to its parent builder.
508      *
509      * NOTE: Implementations that don't support nested builders don't need to
510      * override this method.
511      */
dispose()512     void dispose() {
513       throw new IllegalStateException("Should be overridden by subclasses.");
514     }
515 
516     // ===============================================================
517     // The following definitions seem to be required in order to make javac
518     // not produce weird errors like:
519     //
520     // java/com/google/protobuf/DynamicMessage.java:203: types
521     //   com.google.protobuf.AbstractMessage.Builder<
522     //     com.google.protobuf.DynamicMessage.Builder> and
523     //   com.google.protobuf.AbstractMessage.Builder<
524     //     com.google.protobuf.DynamicMessage.Builder> are incompatible; both
525     //   define mergeFrom(com.google.protobuf.ByteString), but with unrelated
526     //   return types.
527     //
528     // Strangely, these lines are only needed if javac is invoked separately
529     // on AbstractMessage.java and AbstractMessageLite.java.  If javac is
530     // invoked on both simultaneously, it works.  (Or maybe the important
531     // point is whether or not DynamicMessage.java is compiled together with
532     // AbstractMessageLite.java -- not sure.)  I suspect this is a compiler
533     // bug.
534 
535     @Override
mergeFrom(final ByteString data)536     public BuilderType mergeFrom(final ByteString data)
537         throws InvalidProtocolBufferException {
538       return (BuilderType) super.mergeFrom(data);
539     }
540 
541     @Override
mergeFrom( final ByteString data, final ExtensionRegistryLite extensionRegistry)542     public BuilderType mergeFrom(
543         final ByteString data,
544         final ExtensionRegistryLite extensionRegistry)
545         throws InvalidProtocolBufferException {
546       return (BuilderType) super.mergeFrom(data, extensionRegistry);
547     }
548 
549     @Override
mergeFrom(final byte[] data)550     public BuilderType mergeFrom(final byte[] data)
551         throws InvalidProtocolBufferException {
552       return (BuilderType) super.mergeFrom(data);
553     }
554 
555     @Override
mergeFrom( final byte[] data, final int off, final int len)556     public BuilderType mergeFrom(
557         final byte[] data, final int off, final int len)
558         throws InvalidProtocolBufferException {
559       return (BuilderType) super.mergeFrom(data, off, len);
560     }
561 
562     @Override
mergeFrom( final byte[] data, final ExtensionRegistryLite extensionRegistry)563     public BuilderType mergeFrom(
564         final byte[] data,
565         final ExtensionRegistryLite extensionRegistry)
566         throws InvalidProtocolBufferException {
567       return (BuilderType) super.mergeFrom(data, extensionRegistry);
568     }
569 
570     @Override
mergeFrom( final byte[] data, final int off, final int len, final ExtensionRegistryLite extensionRegistry)571     public BuilderType mergeFrom(
572         final byte[] data, final int off, final int len,
573         final ExtensionRegistryLite extensionRegistry)
574         throws InvalidProtocolBufferException {
575       return (BuilderType) super.mergeFrom(data, off, len, extensionRegistry);
576     }
577 
578     @Override
mergeFrom(final InputStream input)579     public BuilderType mergeFrom(final InputStream input)
580         throws IOException {
581       return (BuilderType) super.mergeFrom(input);
582     }
583 
584     @Override
mergeFrom( final InputStream input, final ExtensionRegistryLite extensionRegistry)585     public BuilderType mergeFrom(
586         final InputStream input,
587         final ExtensionRegistryLite extensionRegistry)
588         throws IOException {
589       return (BuilderType) super.mergeFrom(input, extensionRegistry);
590     }
591 
592     @Override
mergeDelimitedFrom(final InputStream input)593     public boolean mergeDelimitedFrom(final InputStream input)
594         throws IOException {
595       return super.mergeDelimitedFrom(input);
596     }
597 
598     @Override
mergeDelimitedFrom( final InputStream input, final ExtensionRegistryLite extensionRegistry)599     public boolean mergeDelimitedFrom(
600         final InputStream input,
601         final ExtensionRegistryLite extensionRegistry)
602         throws IOException {
603       return super.mergeDelimitedFrom(input, extensionRegistry);
604     }
605   }
606 
607   /**
608    * @deprecated from v3.0.0-beta-3+, for compatibility with v2.5.0 and v2.6.1
609    * generated code.
610    */
611   @Deprecated
hashLong(long n)612   protected static int hashLong(long n) {
613     return (int) (n ^ (n >>> 32));
614   }
615   //
616   /**
617    * @deprecated from v3.0.0-beta-3+, for compatibility with v2.5.0 and v2.6.1
618    * generated code.
619    */
620   @Deprecated
hashBoolean(boolean b)621   protected static int hashBoolean(boolean b) {
622     return b ? 1231 : 1237;
623   }
624   //
625   /**
626    * @deprecated from v3.0.0-beta-3+, for compatibility with v2.5.0 and v2.6.1
627    * generated code.
628    */
629   @Deprecated
hashEnum(EnumLite e)630   protected static int hashEnum(EnumLite e) {
631     return e.getNumber();
632   }
633   //
634   /**
635    * @deprecated from v3.0.0-beta-3+, for compatibility with v2.5.0 and v2.6.1
636    * generated code.
637    */
638   @Deprecated
hashEnumList(List<? extends EnumLite> list)639   protected static int hashEnumList(List<? extends EnumLite> list) {
640     int hash = 1;
641     for (EnumLite e : list) {
642       hash = 31 * hash + hashEnum(e);
643     }
644     return hash;
645   }
646 }
647