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 static com.google.protobuf.FieldInfo.forField; 34 import static com.google.protobuf.FieldInfo.forFieldWithEnumVerifier; 35 import static com.google.protobuf.FieldInfo.forMapField; 36 import static com.google.protobuf.FieldInfo.forOneofMemberField; 37 import static com.google.protobuf.FieldInfo.forPackedField; 38 import static com.google.protobuf.FieldInfo.forPackedFieldWithEnumVerifier; 39 import static com.google.protobuf.FieldInfo.forProto2OptionalField; 40 import static com.google.protobuf.FieldInfo.forProto2RequiredField; 41 import static com.google.protobuf.FieldInfo.forRepeatedMessageField; 42 43 import com.google.protobuf.Descriptors.Descriptor; 44 import com.google.protobuf.Descriptors.FieldDescriptor; 45 import com.google.protobuf.Descriptors.FieldDescriptor.Type; 46 import com.google.protobuf.Descriptors.OneofDescriptor; 47 import java.lang.reflect.Field; 48 import java.lang.reflect.Method; 49 import java.util.ArrayList; 50 import java.util.Arrays; 51 import java.util.HashMap; 52 import java.util.HashSet; 53 import java.util.List; 54 import java.util.Map; 55 import java.util.Set; 56 import java.util.Stack; 57 import java.util.concurrent.ConcurrentHashMap; 58 59 /** A factory for message info based on protobuf descriptors for a {@link GeneratedMessageV3}. */ 60 @ExperimentalApi 61 final class DescriptorMessageInfoFactory implements MessageInfoFactory { 62 private static final String GET_DEFAULT_INSTANCE_METHOD_NAME = "getDefaultInstance"; 63 private static final DescriptorMessageInfoFactory instance = new DescriptorMessageInfoFactory(); 64 private static final Set<String> specialFieldNames = 65 new HashSet<>(Arrays.asList("cached_size", "serialized_size", "class")); 66 67 // Disallow construction - it's a singleton. DescriptorMessageInfoFactory()68 private DescriptorMessageInfoFactory() {} 69 getInstance()70 public static DescriptorMessageInfoFactory getInstance() { 71 return instance; 72 } 73 74 @Override isSupported(Class<?> messageType)75 public boolean isSupported(Class<?> messageType) { 76 return GeneratedMessageV3.class.isAssignableFrom(messageType); 77 } 78 79 @Override messageInfoFor(Class<?> messageType)80 public MessageInfo messageInfoFor(Class<?> messageType) { 81 if (!GeneratedMessageV3.class.isAssignableFrom(messageType)) { 82 throw new IllegalArgumentException("Unsupported message type: " + messageType.getName()); 83 } 84 85 return convert(messageType, descriptorForType(messageType)); 86 } 87 getDefaultInstance(Class<?> messageType)88 private static Message getDefaultInstance(Class<?> messageType) { 89 try { 90 Method method = messageType.getDeclaredMethod(GET_DEFAULT_INSTANCE_METHOD_NAME); 91 return (Message) method.invoke(null); 92 } catch (Exception e) { 93 throw new IllegalArgumentException( 94 "Unable to get default instance for message class " + messageType.getName(), e); 95 } 96 } 97 descriptorForType(Class<?> messageType)98 private static Descriptor descriptorForType(Class<?> messageType) { 99 return getDefaultInstance(messageType).getDescriptorForType(); 100 } 101 convert(Class<?> messageType, Descriptor messageDescriptor)102 private static MessageInfo convert(Class<?> messageType, Descriptor messageDescriptor) { 103 switch (messageDescriptor.getFile().getSyntax()) { 104 case PROTO2: 105 return convertProto2(messageType, messageDescriptor); 106 case PROTO3: 107 return convertProto3(messageType, messageDescriptor); 108 default: 109 throw new IllegalArgumentException( 110 "Unsupported syntax: " + messageDescriptor.getFile().getSyntax()); 111 } 112 } 113 114 /** 115 * A helper class to determine whether a message type needs to implement {@code isInitialized()}. 116 * 117 * <p>If a message type doesn't have any required fields or extensions (directly and 118 * transitively), it doesn't need to implement isInitialized() and can always return true there. 119 * It's a bit tricky to determine whether a type has transitive required fields because protobuf 120 * allows cycle references within the same .proto file (e.g., message Foo has a Bar field, and 121 * message Bar has a Foo field). For that we use Tarjan's strongly connected components algorithm 122 * to classify messages into strongly connected groups. Messages in the same group are 123 * transitively including each other, so they should either all have transitive required fields 124 * (or extensions), or none have. 125 * 126 * <p>This class is thread-safe. 127 */ 128 static class IsInitializedCheckAnalyzer { 129 130 private final Map<Descriptor, Boolean> resultCache = 131 new ConcurrentHashMap<Descriptor, Boolean>(); 132 133 // The following data members are part of Tarjan's SCC algorithm. See: 134 // https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm 135 private int index = 0; 136 private final Stack<Node> stack = new Stack<Node>(); 137 private final Map<Descriptor, Node> nodeCache = new HashMap<Descriptor, Node>(); 138 needsIsInitializedCheck(Descriptor descriptor)139 public boolean needsIsInitializedCheck(Descriptor descriptor) { 140 Boolean cachedValue = resultCache.get(descriptor); 141 if (cachedValue != null) { 142 return cachedValue; 143 } 144 synchronized (this) { 145 // Double-check the cache because some other thread may have updated it while we 146 // were acquiring the lock. 147 cachedValue = resultCache.get(descriptor); 148 if (cachedValue != null) { 149 return cachedValue; 150 } 151 return dfs(descriptor).component.needsIsInitializedCheck; 152 } 153 } 154 155 private static class Node { 156 final Descriptor descriptor; 157 final int index; 158 int lowLink; 159 StronglyConnectedComponent component; // null if the node is still on stack. 160 Node(Descriptor descriptor, int index)161 Node(Descriptor descriptor, int index) { 162 this.descriptor = descriptor; 163 this.index = index; 164 this.lowLink = index; 165 this.component = null; 166 } 167 } 168 169 private static class StronglyConnectedComponent { 170 final List<Descriptor> messages = new ArrayList<Descriptor>(); 171 boolean needsIsInitializedCheck = false; 172 } 173 dfs(Descriptor descriptor)174 private Node dfs(Descriptor descriptor) { 175 Node result = new Node(descriptor, index++); 176 stack.push(result); 177 nodeCache.put(descriptor, result); 178 179 // Recurse the fields / nodes in graph 180 for (FieldDescriptor field : descriptor.getFields()) { 181 if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { 182 Node child = nodeCache.get(field.getMessageType()); 183 if (child == null) { 184 // Unexplored node 185 child = dfs(field.getMessageType()); 186 result.lowLink = Math.min(result.lowLink, child.lowLink); 187 } else { 188 if (child.component == null) { 189 // Still in the stack so we found a back edge. 190 result.lowLink = Math.min(result.lowLink, child.lowLink); 191 } 192 } 193 } 194 } 195 196 if (result.index == result.lowLink) { 197 // This is the root of a strongly connected component. 198 StronglyConnectedComponent component = new StronglyConnectedComponent(); 199 while (true) { 200 Node node = stack.pop(); 201 node.component = component; 202 component.messages.add(node.descriptor); 203 if (node == result) { 204 break; 205 } 206 } 207 208 analyze(component); 209 } 210 211 return result; 212 } 213 214 // Determine whether messages in this SCC needs isInitialized check. analyze(StronglyConnectedComponent component)215 private void analyze(StronglyConnectedComponent component) { 216 boolean needsIsInitializedCheck = false; 217 loop: 218 for (Descriptor descriptor : component.messages) { 219 if (descriptor.isExtendable()) { 220 needsIsInitializedCheck = true; 221 break; 222 } 223 224 for (FieldDescriptor field : descriptor.getFields()) { 225 if (field.isRequired()) { 226 needsIsInitializedCheck = true; 227 break loop; 228 } 229 230 if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { 231 // Since we are analyzing the graph bottom-up, all referenced fields should either be 232 // in this same component or in a different already-analyzed component. 233 Node node = nodeCache.get(field.getMessageType()); 234 if (node.component != component) { 235 if (node.component.needsIsInitializedCheck) { 236 needsIsInitializedCheck = true; 237 break loop; 238 } 239 } 240 } 241 } 242 } 243 244 component.needsIsInitializedCheck = needsIsInitializedCheck; 245 246 for (Descriptor descriptor : component.messages) { 247 resultCache.put(descriptor, component.needsIsInitializedCheck); 248 } 249 } 250 } 251 252 private static IsInitializedCheckAnalyzer isInitializedCheckAnalyzer = 253 new IsInitializedCheckAnalyzer(); 254 needsIsInitializedCheck(Descriptor descriptor)255 private static boolean needsIsInitializedCheck(Descriptor descriptor) { 256 return isInitializedCheckAnalyzer.needsIsInitializedCheck(descriptor); 257 } 258 convertProto2( Class<?> messageType, Descriptor messageDescriptor)259 private static StructuralMessageInfo convertProto2( 260 Class<?> messageType, Descriptor messageDescriptor) { 261 List<FieldDescriptor> fieldDescriptors = messageDescriptor.getFields(); 262 StructuralMessageInfo.Builder builder = 263 StructuralMessageInfo.newBuilder(fieldDescriptors.size()); 264 builder.withDefaultInstance(getDefaultInstance(messageType)); 265 builder.withSyntax(ProtoSyntax.PROTO2); 266 builder.withMessageSetWireFormat(messageDescriptor.getOptions().getMessageSetWireFormat()); 267 268 OneofState oneofState = new OneofState(); 269 int bitFieldIndex = 0; 270 int presenceMask = 1; 271 Field bitField = null; 272 273 // Fields in the descriptor are ordered by the index position in which they appear in the 274 // proto file. This is the same order used to determine the presence mask used in the 275 // bitFields. So to determine the appropriate presence mask to be used for a field, we simply 276 // need to shift the presence mask whenever a presence-checked field is encountered. 277 for (int i = 0; i < fieldDescriptors.size(); ++i) { 278 final FieldDescriptor fd = fieldDescriptors.get(i); 279 boolean enforceUtf8 = fd.getFile().getOptions().getJavaStringCheckUtf8(); 280 Internal.EnumVerifier enumVerifier = null; 281 if (fd.getJavaType() == Descriptors.FieldDescriptor.JavaType.ENUM) { 282 enumVerifier = 283 new Internal.EnumVerifier() { 284 @Override 285 public boolean isInRange(int number) { 286 return fd.getEnumType().findValueByNumber(number) != null; 287 } 288 }; 289 } 290 if (fd.getContainingOneof() != null) { 291 // Build a oneof member field. 292 builder.withField(buildOneofMember(messageType, fd, oneofState, enforceUtf8, enumVerifier)); 293 } else { 294 Field field = field(messageType, fd); 295 int number = fd.getNumber(); 296 FieldType type = getFieldType(fd); 297 298 if (fd.isMapField()) { 299 // Map field points to an auto-generated message entry type with the definition: 300 // message MapEntry { 301 // K key = 1; 302 // V value = 2; 303 // } 304 final FieldDescriptor valueField = fd.getMessageType().findFieldByNumber(2); 305 if (valueField.getJavaType() == Descriptors.FieldDescriptor.JavaType.ENUM) { 306 enumVerifier = 307 new Internal.EnumVerifier() { 308 @Override 309 public boolean isInRange(int number) { 310 return valueField.getEnumType().findValueByNumber(number) != null; 311 } 312 }; 313 } 314 builder.withField( 315 forMapField( 316 field, 317 number, 318 SchemaUtil.getMapDefaultEntry(messageType, fd.getName()), 319 enumVerifier)); 320 continue; 321 } 322 323 if (fd.isRepeated()) { 324 // Repeated fields are not presence-checked. 325 if (enumVerifier != null) { 326 if (fd.isPacked()) { 327 builder.withField( 328 forPackedFieldWithEnumVerifier( 329 field, number, type, enumVerifier, cachedSizeField(messageType, fd))); 330 } else { 331 builder.withField(forFieldWithEnumVerifier(field, number, type, enumVerifier)); 332 } 333 } else if (fd.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { 334 builder.withField( 335 forRepeatedMessageField( 336 field, number, type, getTypeForRepeatedMessageField(messageType, fd))); 337 } else { 338 if (fd.isPacked()) { 339 builder.withField( 340 forPackedField(field, number, type, cachedSizeField(messageType, fd))); 341 } else { 342 builder.withField(forField(field, number, type, enforceUtf8)); 343 } 344 } 345 continue; 346 } 347 348 if (bitField == null) { 349 // Lazy-create the next bitfield since we know it must exist. 350 bitField = bitField(messageType, bitFieldIndex); 351 } 352 353 // It's a presence-checked field. 354 if (fd.isRequired()) { 355 builder.withField( 356 forProto2RequiredField( 357 field, number, type, bitField, presenceMask, enforceUtf8, enumVerifier)); 358 } else { 359 builder.withField( 360 forProto2OptionalField( 361 field, number, type, bitField, presenceMask, enforceUtf8, enumVerifier)); 362 } 363 } 364 365 // Update the presence mask for the next iteration. If the shift clears out the mask, we will 366 // go to the next bitField. 367 presenceMask <<= 1; 368 if (presenceMask == 0) { 369 bitField = null; 370 presenceMask = 1; 371 bitFieldIndex++; 372 } 373 } 374 375 List<Integer> fieldsToCheckIsInitialized = new ArrayList<Integer>(); 376 for (int i = 0; i < fieldDescriptors.size(); ++i) { 377 FieldDescriptor fd = fieldDescriptors.get(i); 378 if (fd.isRequired() 379 || (fd.getJavaType() == FieldDescriptor.JavaType.MESSAGE 380 && needsIsInitializedCheck(fd.getMessageType()))) { 381 fieldsToCheckIsInitialized.add(fd.getNumber()); 382 } 383 } 384 int[] numbers = new int[fieldsToCheckIsInitialized.size()]; 385 for (int i = 0; i < fieldsToCheckIsInitialized.size(); i++) { 386 numbers[i] = fieldsToCheckIsInitialized.get(i); 387 } 388 builder.withCheckInitialized(numbers); 389 390 return builder.build(); 391 } 392 convertProto3( Class<?> messageType, Descriptor messageDescriptor)393 private static StructuralMessageInfo convertProto3( 394 Class<?> messageType, Descriptor messageDescriptor) { 395 List<FieldDescriptor> fieldDescriptors = messageDescriptor.getFields(); 396 StructuralMessageInfo.Builder builder = 397 StructuralMessageInfo.newBuilder(fieldDescriptors.size()); 398 builder.withDefaultInstance(getDefaultInstance(messageType)); 399 builder.withSyntax(ProtoSyntax.PROTO3); 400 401 OneofState oneofState = new OneofState(); 402 boolean enforceUtf8 = true; 403 for (int i = 0; i < fieldDescriptors.size(); ++i) { 404 FieldDescriptor fd = fieldDescriptors.get(i); 405 if (fd.getContainingOneof() != null) { 406 // Build a oneof member field. 407 builder.withField(buildOneofMember(messageType, fd, oneofState, enforceUtf8, null)); 408 continue; 409 } 410 if (fd.isMapField()) { 411 builder.withField( 412 forMapField( 413 field(messageType, fd), 414 fd.getNumber(), 415 SchemaUtil.getMapDefaultEntry(messageType, fd.getName()), 416 null)); 417 continue; 418 } 419 if (fd.isRepeated() && fd.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { 420 builder.withField( 421 forRepeatedMessageField( 422 field(messageType, fd), 423 fd.getNumber(), 424 getFieldType(fd), 425 getTypeForRepeatedMessageField(messageType, fd))); 426 continue; 427 } 428 if (fd.isPacked()) { 429 builder.withField( 430 forPackedField( 431 field(messageType, fd), 432 fd.getNumber(), 433 getFieldType(fd), 434 cachedSizeField(messageType, fd))); 435 } else { 436 builder.withField( 437 forField(field(messageType, fd), fd.getNumber(), getFieldType(fd), enforceUtf8)); 438 } 439 } 440 441 return builder.build(); 442 } 443 444 /** Builds info for a oneof member field. */ buildOneofMember( Class<?> messageType, FieldDescriptor fd, OneofState oneofState, boolean enforceUtf8, Internal.EnumVerifier enumVerifier)445 private static FieldInfo buildOneofMember( 446 Class<?> messageType, 447 FieldDescriptor fd, 448 OneofState oneofState, 449 boolean enforceUtf8, 450 Internal.EnumVerifier enumVerifier) { 451 OneofInfo oneof = oneofState.getOneof(messageType, fd.getContainingOneof()); 452 FieldType type = getFieldType(fd); 453 Class<?> oneofStoredType = getOneofStoredType(messageType, fd, type); 454 return forOneofMemberField( 455 fd.getNumber(), type, oneof, oneofStoredType, enforceUtf8, enumVerifier); 456 } 457 getOneofStoredType( Class<?> messageType, FieldDescriptor fd, FieldType type)458 private static Class<?> getOneofStoredType( 459 Class<?> messageType, FieldDescriptor fd, FieldType type) { 460 switch (type.getJavaType()) { 461 case BOOLEAN: 462 return Boolean.class; 463 case BYTE_STRING: 464 return ByteString.class; 465 case DOUBLE: 466 return Double.class; 467 case FLOAT: 468 return Float.class; 469 case ENUM: 470 case INT: 471 return Integer.class; 472 case LONG: 473 return Long.class; 474 case STRING: 475 return String.class; 476 case MESSAGE: 477 return getOneofStoredTypeForMessage(messageType, fd); 478 default: 479 throw new IllegalArgumentException("Invalid type for oneof: " + type); 480 } 481 } 482 getFieldType(FieldDescriptor fd)483 private static FieldType getFieldType(FieldDescriptor fd) { 484 switch (fd.getType()) { 485 case BOOL: 486 if (!fd.isRepeated()) { 487 return FieldType.BOOL; 488 } 489 return fd.isPacked() ? FieldType.BOOL_LIST_PACKED : FieldType.BOOL_LIST; 490 case BYTES: 491 return fd.isRepeated() ? FieldType.BYTES_LIST : FieldType.BYTES; 492 case DOUBLE: 493 if (!fd.isRepeated()) { 494 return FieldType.DOUBLE; 495 } 496 return fd.isPacked() ? FieldType.DOUBLE_LIST_PACKED : FieldType.DOUBLE_LIST; 497 case ENUM: 498 if (!fd.isRepeated()) { 499 return FieldType.ENUM; 500 } 501 return fd.isPacked() ? FieldType.ENUM_LIST_PACKED : FieldType.ENUM_LIST; 502 case FIXED32: 503 if (!fd.isRepeated()) { 504 return FieldType.FIXED32; 505 } 506 return fd.isPacked() ? FieldType.FIXED32_LIST_PACKED : FieldType.FIXED32_LIST; 507 case FIXED64: 508 if (!fd.isRepeated()) { 509 return FieldType.FIXED64; 510 } 511 return fd.isPacked() ? FieldType.FIXED64_LIST_PACKED : FieldType.FIXED64_LIST; 512 case FLOAT: 513 if (!fd.isRepeated()) { 514 return FieldType.FLOAT; 515 } 516 return fd.isPacked() ? FieldType.FLOAT_LIST_PACKED : FieldType.FLOAT_LIST; 517 case GROUP: 518 return fd.isRepeated() ? FieldType.GROUP_LIST : FieldType.GROUP; 519 case INT32: 520 if (!fd.isRepeated()) { 521 return FieldType.INT32; 522 } 523 return fd.isPacked() ? FieldType.INT32_LIST_PACKED : FieldType.INT32_LIST; 524 case INT64: 525 if (!fd.isRepeated()) { 526 return FieldType.INT64; 527 } 528 return fd.isPacked() ? FieldType.INT64_LIST_PACKED : FieldType.INT64_LIST; 529 case MESSAGE: 530 if (fd.isMapField()) { 531 return FieldType.MAP; 532 } 533 return fd.isRepeated() ? FieldType.MESSAGE_LIST : FieldType.MESSAGE; 534 case SFIXED32: 535 if (!fd.isRepeated()) { 536 return FieldType.SFIXED32; 537 } 538 return fd.isPacked() ? FieldType.SFIXED32_LIST_PACKED : FieldType.SFIXED32_LIST; 539 case SFIXED64: 540 if (!fd.isRepeated()) { 541 return FieldType.SFIXED64; 542 } 543 return fd.isPacked() ? FieldType.SFIXED64_LIST_PACKED : FieldType.SFIXED64_LIST; 544 case SINT32: 545 if (!fd.isRepeated()) { 546 return FieldType.SINT32; 547 } 548 return fd.isPacked() ? FieldType.SINT32_LIST_PACKED : FieldType.SINT32_LIST; 549 case SINT64: 550 if (!fd.isRepeated()) { 551 return FieldType.SINT64; 552 } 553 return fd.isPacked() ? FieldType.SINT64_LIST_PACKED : FieldType.SINT64_LIST; 554 case STRING: 555 return fd.isRepeated() ? FieldType.STRING_LIST : FieldType.STRING; 556 case UINT32: 557 if (!fd.isRepeated()) { 558 return FieldType.UINT32; 559 } 560 return fd.isPacked() ? FieldType.UINT32_LIST_PACKED : FieldType.UINT32_LIST; 561 case UINT64: 562 if (!fd.isRepeated()) { 563 return FieldType.UINT64; 564 } 565 return fd.isPacked() ? FieldType.UINT64_LIST_PACKED : FieldType.UINT64_LIST; 566 default: 567 throw new IllegalArgumentException("Unsupported field type: " + fd.getType()); 568 } 569 } 570 bitField(Class<?> messageType, int index)571 private static Field bitField(Class<?> messageType, int index) { 572 return field(messageType, "bitField" + index + "_"); 573 } 574 field(Class<?> messageType, FieldDescriptor fd)575 private static Field field(Class<?> messageType, FieldDescriptor fd) { 576 return field(messageType, getFieldName(fd)); 577 } 578 cachedSizeField(Class<?> messageType, FieldDescriptor fd)579 private static Field cachedSizeField(Class<?> messageType, FieldDescriptor fd) { 580 return field(messageType, getCachedSizeFieldName(fd)); 581 } 582 field(Class<?> messageType, String fieldName)583 private static Field field(Class<?> messageType, String fieldName) { 584 try { 585 return messageType.getDeclaredField(fieldName); 586 } catch (Exception e) { 587 throw new IllegalArgumentException( 588 "Unable to find field " + fieldName + " in message class " + messageType.getName()); 589 } 590 } 591 getFieldName(FieldDescriptor fd)592 static String getFieldName(FieldDescriptor fd) { 593 String name = (fd.getType() == FieldDescriptor.Type.GROUP) 594 ? fd.getMessageType().getName() 595 : fd.getName(); 596 String suffix = specialFieldNames.contains(name) ? "__" : "_"; 597 return snakeCaseToCamelCase(name) + suffix; 598 } 599 getCachedSizeFieldName(FieldDescriptor fd)600 private static String getCachedSizeFieldName(FieldDescriptor fd) { 601 return snakeCaseToCamelCase(fd.getName()) + "MemoizedSerializedSize"; 602 } 603 604 /** 605 * This method must match exactly with the corresponding function in protocol compiler. See: 606 * https://github.com/google/protobuf/blob/v3.0.0/src/google/protobuf/compiler/java/java_helpers.cc#L153 607 */ snakeCaseToCamelCase(String snakeCase)608 private static String snakeCaseToCamelCase(String snakeCase) { 609 StringBuilder sb = new StringBuilder(snakeCase.length() + 1); 610 boolean capNext = false; 611 for (int ctr = 0; ctr < snakeCase.length(); ctr++) { 612 char next = snakeCase.charAt(ctr); 613 if (next == '_') { 614 capNext = true; 615 } else if (Character.isDigit(next)) { 616 sb.append(next); 617 capNext = true; 618 } else if (capNext) { 619 sb.append(Character.toUpperCase(next)); 620 capNext = false; 621 } else if (ctr == 0) { 622 sb.append(Character.toLowerCase(next)); 623 } else { 624 sb.append(next); 625 } 626 } 627 return sb.toString(); 628 } 629 630 /** 631 * Inspects the message to identify the stored type for a message field that is part of a oneof. 632 */ getOneofStoredTypeForMessage(Class<?> messageType, FieldDescriptor fd)633 private static Class<?> getOneofStoredTypeForMessage(Class<?> messageType, FieldDescriptor fd) { 634 try { 635 String name = fd.getType() == Type.GROUP ? fd.getMessageType().getName() : fd.getName(); 636 Method getter = messageType.getDeclaredMethod(getterForField(name)); 637 return getter.getReturnType(); 638 } catch (Exception e) { 639 throw new RuntimeException(e); 640 } 641 } 642 643 /** Inspects the message to identify the message type of a repeated message field. */ getTypeForRepeatedMessageField(Class<?> messageType, FieldDescriptor fd)644 private static Class<?> getTypeForRepeatedMessageField(Class<?> messageType, FieldDescriptor fd) { 645 try { 646 String name = fd.getType() == Type.GROUP ? fd.getMessageType().getName() : fd.getName(); 647 Method getter = messageType.getDeclaredMethod(getterForField(name), int.class); 648 return getter.getReturnType(); 649 } catch (Exception e) { 650 throw new RuntimeException(e); 651 } 652 } 653 654 /** Constructs the name of the get method for the given field in the proto. */ getterForField(String snakeCase)655 private static String getterForField(String snakeCase) { 656 String camelCase = snakeCaseToCamelCase(snakeCase); 657 StringBuilder builder = new StringBuilder("get"); 658 // Capitalize the first character in the field name. 659 builder.append(Character.toUpperCase(camelCase.charAt(0))); 660 builder.append(camelCase.substring(1, camelCase.length())); 661 return builder.toString(); 662 } 663 664 private static final class OneofState { 665 private OneofInfo[] oneofs = new OneofInfo[2]; 666 getOneof(Class<?> messageType, OneofDescriptor desc)667 OneofInfo getOneof(Class<?> messageType, OneofDescriptor desc) { 668 int index = desc.getIndex(); 669 if (index >= oneofs.length) { 670 // Grow the array. 671 oneofs = Arrays.copyOf(oneofs, index * 2); 672 } 673 OneofInfo info = oneofs[index]; 674 if (info == null) { 675 info = newInfo(messageType, desc); 676 oneofs[index] = info; 677 } 678 return info; 679 } 680 newInfo(Class<?> messageType, OneofDescriptor desc)681 private static OneofInfo newInfo(Class<?> messageType, OneofDescriptor desc) { 682 String camelCase = snakeCaseToCamelCase(desc.getName()); 683 String valueFieldName = camelCase + "_"; 684 String caseFieldName = camelCase + "Case_"; 685 686 return new OneofInfo( 687 desc.getIndex(), field(messageType, caseFieldName), field(messageType, valueFieldName)); 688 } 689 } 690 } 691