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