• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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