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 : getUnknownFieldSetBuilder(); 430 MessageReflection.mergeMessageFrom(this, unknownFields, input, extensionRegistry); 431 if (unknownFields != null) { 432 setUnknownFieldSetBuilder(unknownFields); 433 } 434 return (BuilderType) this; 435 } 436 getUnknownFieldSetBuilder()437 protected UnknownFieldSet.Builder getUnknownFieldSetBuilder() { 438 return UnknownFieldSet.newBuilder(getUnknownFields()); 439 } 440 setUnknownFieldSetBuilder(final UnknownFieldSet.Builder builder)441 protected void setUnknownFieldSetBuilder(final UnknownFieldSet.Builder builder) { 442 setUnknownFields(builder.build()); 443 } 444 445 @Override mergeUnknownFields(final UnknownFieldSet unknownFields)446 public BuilderType mergeUnknownFields(final UnknownFieldSet unknownFields) { 447 setUnknownFields( 448 UnknownFieldSet.newBuilder(getUnknownFields()).mergeFrom(unknownFields).build()); 449 return (BuilderType) this; 450 } 451 452 @Override getFieldBuilder(final FieldDescriptor field)453 public Message.Builder getFieldBuilder(final FieldDescriptor field) { 454 throw new UnsupportedOperationException( 455 "getFieldBuilder() called on an unsupported message type."); 456 } 457 458 @Override getRepeatedFieldBuilder(final FieldDescriptor field, int index)459 public Message.Builder getRepeatedFieldBuilder(final FieldDescriptor field, int index) { 460 throw new UnsupportedOperationException( 461 "getRepeatedFieldBuilder() called on an unsupported message type."); 462 } 463 464 @Override toString()465 public String toString() { 466 return TextFormat.printer().printToString(this); 467 } 468 469 /** Construct an UninitializedMessageException reporting missing fields in the given message. */ newUninitializedMessageException( Message message)470 protected static UninitializedMessageException newUninitializedMessageException( 471 Message message) { 472 return new UninitializedMessageException(MessageReflection.findMissingFields(message)); 473 } 474 475 /** 476 * Used to support nested builders and called to mark this builder as clean. Clean builders will 477 * propagate the {@link BuilderParent#markDirty()} event to their parent builders, while dirty 478 * builders will not, as their parents should be dirty already. 479 * 480 * <p>NOTE: Implementations that don't support nested builders don't need to override this 481 * method. 482 */ markClean()483 void markClean() { 484 throw new IllegalStateException("Should be overridden by subclasses."); 485 } 486 487 /** 488 * Used to support nested builders and called when this nested builder is no longer used by its 489 * parent builder and should release the reference to its parent builder. 490 * 491 * <p>NOTE: Implementations that don't support nested builders don't need to override this 492 * method. 493 */ dispose()494 void dispose() { 495 throw new IllegalStateException("Should be overridden by subclasses."); 496 } 497 498 // =============================================================== 499 // The following definitions seem to be required in order to make javac 500 // not produce weird errors like: 501 // 502 // java/com/google/protobuf/DynamicMessage.java:203: types 503 // com.google.protobuf.AbstractMessage.Builder< 504 // com.google.protobuf.DynamicMessage.Builder> and 505 // com.google.protobuf.AbstractMessage.Builder< 506 // com.google.protobuf.DynamicMessage.Builder> are incompatible; both 507 // define mergeFrom(com.google.protobuf.ByteString), but with unrelated 508 // return types. 509 // 510 // Strangely, these lines are only needed if javac is invoked separately 511 // on AbstractMessage.java and AbstractMessageLite.java. If javac is 512 // invoked on both simultaneously, it works. (Or maybe the important 513 // point is whether or not DynamicMessage.java is compiled together with 514 // AbstractMessageLite.java -- not sure.) I suspect this is a compiler 515 // bug. 516 517 @Override mergeFrom(final ByteString data)518 public BuilderType mergeFrom(final ByteString data) throws InvalidProtocolBufferException { 519 return (BuilderType) super.mergeFrom(data); 520 } 521 522 @Override mergeFrom( final ByteString data, final ExtensionRegistryLite extensionRegistry)523 public BuilderType mergeFrom( 524 final ByteString data, final ExtensionRegistryLite extensionRegistry) 525 throws InvalidProtocolBufferException { 526 return (BuilderType) super.mergeFrom(data, extensionRegistry); 527 } 528 529 @Override mergeFrom(final byte[] data)530 public BuilderType mergeFrom(final byte[] data) throws InvalidProtocolBufferException { 531 return (BuilderType) super.mergeFrom(data); 532 } 533 534 @Override mergeFrom(final byte[] data, final int off, final int len)535 public BuilderType mergeFrom(final byte[] data, final int off, final int len) 536 throws InvalidProtocolBufferException { 537 return (BuilderType) super.mergeFrom(data, off, len); 538 } 539 540 @Override mergeFrom(final byte[] data, final ExtensionRegistryLite extensionRegistry)541 public BuilderType mergeFrom(final byte[] data, final ExtensionRegistryLite extensionRegistry) 542 throws InvalidProtocolBufferException { 543 return (BuilderType) super.mergeFrom(data, extensionRegistry); 544 } 545 546 @Override mergeFrom( final byte[] data, final int off, final int len, final ExtensionRegistryLite extensionRegistry)547 public BuilderType mergeFrom( 548 final byte[] data, 549 final int off, 550 final int len, 551 final ExtensionRegistryLite extensionRegistry) 552 throws InvalidProtocolBufferException { 553 return (BuilderType) super.mergeFrom(data, off, len, extensionRegistry); 554 } 555 556 @Override mergeFrom(final InputStream input)557 public BuilderType mergeFrom(final InputStream input) throws IOException { 558 return (BuilderType) super.mergeFrom(input); 559 } 560 561 @Override mergeFrom( final InputStream input, final ExtensionRegistryLite extensionRegistry)562 public BuilderType mergeFrom( 563 final InputStream input, final ExtensionRegistryLite extensionRegistry) throws IOException { 564 return (BuilderType) super.mergeFrom(input, extensionRegistry); 565 } 566 567 @Override mergeDelimitedFrom(final InputStream input)568 public boolean mergeDelimitedFrom(final InputStream input) throws IOException { 569 return super.mergeDelimitedFrom(input); 570 } 571 572 @Override mergeDelimitedFrom( final InputStream input, final ExtensionRegistryLite extensionRegistry)573 public boolean mergeDelimitedFrom( 574 final InputStream input, final ExtensionRegistryLite extensionRegistry) throws IOException { 575 return super.mergeDelimitedFrom(input, extensionRegistry); 576 } 577 } 578 579 /** 580 * @deprecated from v3.0.0-beta-3+, for compatibility with v2.5.0 and v2.6.1 581 * generated code. 582 */ 583 @Deprecated hashLong(long n)584 protected static int hashLong(long n) { 585 return (int) (n ^ (n >>> 32)); 586 } 587 // 588 /** 589 * @deprecated from v3.0.0-beta-3+, for compatibility with v2.5.0 and v2.6.1 590 * generated code. 591 */ 592 @Deprecated hashBoolean(boolean b)593 protected static int hashBoolean(boolean b) { 594 return b ? 1231 : 1237; 595 } 596 // 597 /** 598 * @deprecated from v3.0.0-beta-3+, for compatibility with v2.5.0 and v2.6.1 599 * generated code. 600 */ 601 @Deprecated hashEnum(EnumLite e)602 protected static int hashEnum(EnumLite e) { 603 return e.getNumber(); 604 } 605 // 606 /** 607 * @deprecated from v3.0.0-beta-3+, for compatibility with v2.5.0 and v2.6.1 608 * generated code. 609 */ 610 @Deprecated hashEnumList(List<? extends EnumLite> list)611 protected static int hashEnumList(List<? extends EnumLite> list) { 612 int hash = 1; 613 for (EnumLite e : list) { 614 hash = 31 * hash + hashEnum(e); 615 } 616 return hash; 617 } 618 } 619