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