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