content: // Protocol Buffers - Google's data interchange format
  // Copyright 2008 Google Inc.  All rights reserved.
  // https://developers.google.com/protocol-buffers/
  //
  // Redistribution and use in source and binary forms, with or without
  // modification, are permitted provided that the following conditions are
  // met:
  //
  //     * Redistributions of source code must retain the above copyright
  // notice, this list of conditions and the following disclaimer.
  //     * Redistributions in binary form must reproduce the above
  // copyright notice, this list of conditions and the following disclaimer
  // in the documentation and/or other materials provided with the
  // distribution.
  //     * Neither the name of Google Inc. nor the names of its
  // contributors may be used to endorse or promote products derived from
  // this software without specific prior written permission.
  //
  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  
  package com.google.protobuf;
  
  import static com.google.protobuf.FieldInfo.forField;
  import static com.google.protobuf.FieldInfo.forFieldWithEnumVerifier;
  import static com.google.protobuf.FieldInfo.forMapField;
  import static com.google.protobuf.FieldInfo.forOneofMemberField;
  import static com.google.protobuf.FieldInfo.forPackedField;
  import static com.google.protobuf.FieldInfo.forPackedFieldWithEnumVerifier;
  import static com.google.protobuf.FieldInfo.forProto2OptionalField;
  import static com.google.protobuf.FieldInfo.forProto2RequiredField;
  import static com.google.protobuf.FieldInfo.forRepeatedMessageField;
  
  import com.google.protobuf.Descriptors.Descriptor;
  import com.google.protobuf.Descriptors.FieldDescriptor;
  import com.google.protobuf.Descriptors.FieldDescriptor.Type;
  import com.google.protobuf.Descriptors.OneofDescriptor;
  import java.lang.reflect.Field;
  import java.lang.reflect.Method;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import java.util.Stack;
  import java.util.concurrent.ConcurrentHashMap;
  
  /** A factory for message info based on protobuf descriptors for a {@link GeneratedMessageV3}. */
  @ExperimentalApi
  final class DescriptorMessageInfoFactory implements MessageInfoFactory {
    private static final String GET_DEFAULT_INSTANCE_METHOD_NAME = "getDefaultInstance";
    private static final DescriptorMessageInfoFactory instance = new DescriptorMessageInfoFactory();
    private static final Set<String> specialFieldNames =
        new HashSet<>(Arrays.asList("cached_size", "serialized_size", "class"));
  
    // Disallow construction - it's a singleton.
    private DescriptorMessageInfoFactory() {}
  
    public static DescriptorMessageInfoFactory getInstance() {
      return instance;
    }
  
    @Override
    public boolean isSupported(Class<?> messageType) {
      return GeneratedMessageV3.class.isAssignableFrom(messageType);
    }
  
    @Override
    public MessageInfo messageInfoFor(Class<?> messageType) {
      if (!GeneratedMessageV3.class.isAssignableFrom(messageType)) {
        throw new IllegalArgumentException("Unsupported message type: " + messageType.getName());
      }
  
      return convert(messageType, descriptorForType(messageType));
    }
  
    private static Message getDefaultInstance(Class<?> messageType) {
      try {
        Method method = messageType.getDeclaredMethod(GET_DEFAULT_INSTANCE_METHOD_NAME);
        return (Message) method.invoke(null);
      } catch (Exception e) {
        throw new IllegalArgumentException(
            "Unable to get default instance for message class " + messageType.getName(), e);
      }
    }
  
    private static Descriptor descriptorForType(Class<?> messageType) {
      return getDefaultInstance(messageType).getDescriptorForType();
    }
  
    private static MessageInfo convert(Class<?> messageType, Descriptor messageDescriptor) {
      switch (messageDescriptor.getFile().getSyntax()) {
        case PROTO2:
          return convertProto2(messageType, messageDescriptor);
        case PROTO3:
          return convertProto3(messageType, messageDescriptor);
        default:
          throw new IllegalArgumentException(
              "Unsupported syntax: " + messageDescriptor.getFile().getSyntax());
      }
    }
  
    /**
     * A helper class to determine whether a message type needs to implement {@code isInitialized()}.
     *
     * <p>If a message type doesn't have any required fields or extensions (directly and
     * transitively), it doesn't need to implement isInitialized() and can always return true there.
     * It's a bit tricky to determine whether a type has transitive required fields because protobuf
     * allows cycle references within the same .proto file (e.g., message Foo has a Bar field, and
     * message Bar has a Foo field). For that we use Tarjan's strongly connected components algorithm
     * to classify messages into strongly connected groups. Messages in the same group are
     * transitively including each other, so they should either all have transitive required fields
     * (or extensions), or none have.
     *
     * <p>This class is thread-safe.
     */
    static class IsInitializedCheckAnalyzer {
  
      private final Map<Descriptor, Boolean> resultCache =
          new ConcurrentHashMap<Descriptor, Boolean>();
  
      // The following data members are part of Tarjan's SCC algorithm. See:
      //   https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
      private int index = 0;
      private final Stack<Node> stack = new Stack<Node>();
      private final Map<Descriptor, Node> nodeCache = new HashMap<Descriptor, Node>();
  
      public boolean needsIsInitializedCheck(Descriptor descriptor) {
        Boolean cachedValue = resultCache.get(descriptor);
        if (cachedValue != null) {
          return cachedValue;
        }
        synchronized (this) {
          // Double-check the cache because some other thread may have updated it while we
          // were acquiring the lock.
          cachedValue = resultCache.get(descriptor);
          if (cachedValue != null) {
            return cachedValue;
          }
          return dfs(descriptor).component.needsIsInitializedCheck;
        }
      }
  
      private static class Node {
        final Descriptor descriptor;
        final int index;
        int lowLink;
        StronglyConnectedComponent component; // null if the node is still on stack.
  
        Node(Descriptor descriptor, int index) {
          this.descriptor = descriptor;
          this.index = index;
          this.lowLink = index;
          this.component = null;
        }
      }
  
      private static class StronglyConnectedComponent {
        final List<Descriptor> messages = new ArrayList<Descriptor>();
        boolean needsIsInitializedCheck = false;
      }
  
      private Node dfs(Descriptor descriptor) {
        Node result = new Node(descriptor, index++);
        stack.push(result);
        nodeCache.put(descriptor, result);
  
        // Recurse the fields / nodes in graph
        for (FieldDescriptor field : descriptor.getFields()) {
          if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
            Node child = nodeCache.get(field.getMessageType());
            if (child == null) {
              // Unexplored node
              child = dfs(field.getMessageType());
              result.lowLink = Math.min(result.lowLink, child.lowLink);
            } else {
              if (child.component == null) {
                // Still in the stack so we found a back edge.
                result.lowLink = Math.min(result.lowLink, child.lowLink);
              }
            }
          }
        }
  
        if (result.index == result.lowLink) {
          // This is the root of a strongly connected component.
          StronglyConnectedComponent component = new StronglyConnectedComponent();
          while (true) {
            Node node = stack.pop();
            node.component = component;
            component.messages.add(node.descriptor);
            if (node == result) {
              break;
            }
          }
  
          analyze(component);
        }
  
        return result;
      }
  
      // Determine whether messages in this SCC needs isInitialized check.
      private void analyze(StronglyConnectedComponent component) {
        boolean needsIsInitializedCheck = false;
        loop:
        for (Descriptor descriptor : component.messages) {
          if (descriptor.isExtendable()) {
            needsIsInitializedCheck = true;
            break;
          }
  
          for (FieldDescriptor field : descriptor.getFields()) {
            if (field.isRequired()) {
              needsIsInitializedCheck = true;
              break loop;
            }
  
            if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
              // Since we are analyzing the graph bottom-up, all referenced fields should either be
              // in this same component or in a different already-analyzed component.
              Node node = nodeCache.get(field.getMessageType());
              if (node.component != component) {
                if (node.component.needsIsInitializedCheck) {
                  needsIsInitializedCheck = true;
                  break loop;
                }
              }
            }
          }
        }
  
        component.needsIsInitializedCheck = needsIsInitializedCheck;
  
        for (Descriptor descriptor : component.messages) {
          resultCache.put(descriptor, component.needsIsInitializedCheck);
        }
      }
    }
  
    private static IsInitializedCheckAnalyzer isInitializedCheckAnalyzer =
        new IsInitializedCheckAnalyzer();
  
    private static boolean needsIsInitializedCheck(Descriptor descriptor) {
      return isInitializedCheckAnalyzer.needsIsInitializedCheck(descriptor);
    }
  
    private static StructuralMessageInfo convertProto2(
        Class<?> messageType, Descriptor messageDescriptor) {
      List<FieldDescriptor> fieldDescriptors = messageDescriptor.getFields();
      StructuralMessageInfo.Builder builder =
          StructuralMessageInfo.newBuilder(fieldDescriptors.size());
      builder.withDefaultInstance(getDefaultInstance(messageType));
      builder.withSyntax(ProtoSyntax.PROTO2);
      builder.withMessageSetWireFormat(messageDescriptor.getOptions().getMessageSetWireFormat());
  
      OneofState oneofState = new OneofState();
      int bitFieldIndex = 0;
      int presenceMask = 1;
      Field bitField = null;
  
      // Fields in the descriptor are ordered by the index position in which they appear in the
      // proto file. This is the same order used to determine the presence mask used in the
      // bitFields. So to determine the appropriate presence mask to be used for a field, we simply
      // need to shift the presence mask whenever a presence-checked field is encountered.
      for (int i = 0; i < fieldDescriptors.size(); ++i) {
        final FieldDescriptor fd = fieldDescriptors.get(i);
        boolean enforceUtf8 = fd.getFile().getOptions().getJavaStringCheckUtf8();
        Internal.EnumVerifier enumVerifier = null;
        if (fd.getJavaType() == Descriptors.FieldDescriptor.JavaType.ENUM) {
          enumVerifier =
              new Internal.EnumVerifier() {
                @Override
                public boolean isInRange(int number) {
                  return fd.getEnumType().findValueByNumber(number) != null;
                }
              };
        }
        if (fd.getContainingOneof() != null) {
          // Build a oneof member field.
          builder.withField(buildOneofMember(messageType, fd, oneofState, enforceUtf8, enumVerifier));
        } else {
          Field field = field(messageType, fd);
          int number = fd.getNumber();
          FieldType type = getFieldType(fd);
  
          if (fd.isMapField()) {
            // Map field points to an auto-generated message entry type with the definition:
            //   message MapEntry {
            //     K key = 1;
            //     V value = 2;
            //   }
            final FieldDescriptor valueField = fd.getMessageType().findFieldByNumber(2);
            if (valueField.getJavaType() == Descriptors.FieldDescriptor.JavaType.ENUM) {
              enumVerifier =
                  new Internal.EnumVerifier() {
                    @Override
                    public boolean isInRange(int number) {
                      return valueField.getEnumType().findValueByNumber(number) != null;
                    }
                  };
            }
            builder.withField(
                forMapField(
                    field,
                    number,
                    SchemaUtil.getMapDefaultEntry(messageType, fd.getName()),
                    enumVerifier));
            continue;
          }
  
          if (fd.isRepeated()) {
            // Repeated fields are not presence-checked.
            if (enumVerifier != null) {
              if (fd.isPacked()) {
                builder.withField(
                    forPackedFieldWithEnumVerifier(
                        field, number, type, enumVerifier, cachedSizeField(messageType, fd)));
              } else {
                builder.withField(forFieldWithEnumVerifier(field, number, type, enumVerifier));
              }
            } else if (fd.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
              builder.withField(
                  forRepeatedMessageField(
                      field, number, type, getTypeForRepeatedMessageField(messageType, fd)));
            } else {
              if (fd.isPacked()) {
                builder.withField(
                    forPackedField(field, number, type, cachedSizeField(messageType, fd)));
              } else {
                builder.withField(forField(field, number, type, enforceUtf8));
              }
            }
            continue;
          }
  
          if (bitField == null) {
            // Lazy-create the next bitfield since we know it must exist.
            bitField = bitField(messageType, bitFieldIndex);
          }
  
          // It's a presence-checked field.
          if (fd.isRequired()) {
            builder.withField(
                forProto2RequiredField(
                    field, number, type, bitField, presenceMask, enforceUtf8, enumVerifier));
          } else {
            builder.withField(
                forProto2OptionalField(
                    field, number, type, bitField, presenceMask, enforceUtf8, enumVerifier));
          }
        }
  
        // Update the presence mask for the next iteration. If the shift clears out the mask, we will
        // go to the next bitField.
        presenceMask <<= 1;
        if (presenceMask == 0) {
          bitField = null;
          presenceMask = 1;
          bitFieldIndex++;
        }
      }
  
      List<Integer> fieldsToCheckIsInitialized = new ArrayList<Integer>();
      for (int i = 0; i < fieldDescriptors.size(); ++i) {
        FieldDescriptor fd = fieldDescriptors.get(i);
        if (fd.isRequired()
            || (fd.getJavaType() == FieldDescriptor.JavaType.MESSAGE
                && needsIsInitializedCheck(fd.getMessageType()))) {
          fieldsToCheckIsInitialized.add(fd.getNumber());
        }
      }
      int[] numbers = new int[fieldsToCheckIsInitialized.size()];
      for (int i = 0; i < fieldsToCheckIsInitialized.size(); i++) {
        numbers[i] = fieldsToCheckIsInitialized.get(i);
      }
      builder.withCheckInitialized(numbers);
  
      return builder.build();
    }
  
    private static StructuralMessageInfo convertProto3(
        Class<?> messageType, Descriptor messageDescriptor) {
      List<FieldDescriptor> fieldDescriptors = messageDescriptor.getFields();
      StructuralMessageInfo.Builder builder =
          StructuralMessageInfo.newBuilder(fieldDescriptors.size());
      builder.withDefaultInstance(getDefaultInstance(messageType));
      builder.withSyntax(ProtoSyntax.PROTO3);
  
      OneofState oneofState = new OneofState();
      boolean enforceUtf8 = true;
      for (int i = 0; i < fieldDescriptors.size(); ++i) {
        FieldDescriptor fd = fieldDescriptors.get(i);
        if (fd.getContainingOneof() != null) {
          // Build a oneof member field.
          builder.withField(buildOneofMember(messageType, fd, oneofState, enforceUtf8, null));
          continue;
        }
        if (fd.isMapField()) {
          builder.withField(
              forMapField(
                  field(messageType, fd),
                  fd.getNumber(),
                  SchemaUtil.getMapDefaultEntry(messageType, fd.getName()),
                  null));
          continue;
        }
        if (fd.isRepeated() && fd.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
          builder.withField(
              forRepeatedMessageField(
                  field(messageType, fd),
                  fd.getNumber(),
                  getFieldType(fd),
                  getTypeForRepeatedMessageField(messageType, fd)));
          continue;
        }
        if (fd.isPacked()) {
          builder.withField(
              forPackedField(
                  field(messageType, fd),
                  fd.getNumber(),
                  getFieldType(fd),
                  cachedSizeField(messageType, fd)));
        } else {
          builder.withField(
              forField(field(messageType, fd), fd.getNumber(), getFieldType(fd), enforceUtf8));
        }
      }
  
      return builder.build();
    }
  
    /** Builds info for a oneof member field. */
    private static FieldInfo buildOneofMember(
        Class<?> messageType,
        FieldDescriptor fd,
        OneofState oneofState,
        boolean enforceUtf8,
        Internal.EnumVerifier enumVerifier) {
      OneofInfo oneof = oneofState.getOneof(messageType, fd.getContainingOneof());
      FieldType type = getFieldType(fd);
      Class<?> oneofStoredType = getOneofStoredType(messageType, fd, type);
      return forOneofMemberField(
          fd.getNumber(), type, oneof, oneofStoredType, enforceUtf8, enumVerifier);
    }
  
    private static Class<?> getOneofStoredType(
        Class<?> messageType, FieldDescriptor fd, FieldType type) {
      switch (type.getJavaType()) {
        case BOOLEAN:
          return Boolean.class;
        case BYTE_STRING:
          return ByteString.class;
        case DOUBLE:
          return Double.class;
        case FLOAT:
          return Float.class;
        case ENUM:
        case INT:
          return Integer.class;
        case LONG:
          return Long.class;
        case STRING:
          return String.class;
        case MESSAGE:
          return getOneofStoredTypeForMessage(messageType, fd);
        default:
          throw new IllegalArgumentException("Invalid type for oneof: " + type);
      }
    }
  
    private static FieldType getFieldType(FieldDescriptor fd) {
      switch (fd.getType()) {
        case BOOL:
          if (!fd.isRepeated()) {
            return FieldType.BOOL;
          }
          return fd.isPacked() ? FieldType.BOOL_LIST_PACKED : FieldType.BOOL_LIST;
        case BYTES:
          return fd.isRepeated() ? FieldType.BYTES_LIST : FieldType.BYTES;
        case DOUBLE:
          if (!fd.isRepeated()) {
            return FieldType.DOUBLE;
          }
          return fd.isPacked() ? FieldType.DOUBLE_LIST_PACKED : FieldType.DOUBLE_LIST;
        case ENUM:
          if (!fd.isRepeated()) {
            return FieldType.ENUM;
          }
          return fd.isPacked() ? FieldType.ENUM_LIST_PACKED : FieldType.ENUM_LIST;
        case FIXED32:
          if (!fd.isRepeated()) {
            return FieldType.FIXED32;
          }
          return fd.isPacked() ? FieldType.FIXED32_LIST_PACKED : FieldType.FIXED32_LIST;
        case FIXED64:
          if (!fd.isRepeated()) {
            return FieldType.FIXED64;
          }
          return fd.isPacked() ? FieldType.FIXED64_LIST_PACKED : FieldType.FIXED64_LIST;
        case FLOAT:
          if (!fd.isRepeated()) {
            return FieldType.FLOAT;
          }
          return fd.isPacked() ? FieldType.FLOAT_LIST_PACKED : FieldType.FLOAT_LIST;
        case GROUP:
          return fd.isRepeated() ? FieldType.GROUP_LIST : FieldType.GROUP;
        case INT32:
          if (!fd.isRepeated()) {
            return FieldType.INT32;
          }
          return fd.isPacked() ? FieldType.INT32_LIST_PACKED : FieldType.INT32_LIST;
        case INT64:
          if (!fd.isRepeated()) {
            return FieldType.INT64;
          }
          return fd.isPacked() ? FieldType.INT64_LIST_PACKED : FieldType.INT64_LIST;
        case MESSAGE:
          if (fd.isMapField()) {
            return FieldType.MAP;
          }
          return fd.isRepeated() ? FieldType.MESSAGE_LIST : FieldType.MESSAGE;
        case SFIXED32:
          if (!fd.isRepeated()) {
            return FieldType.SFIXED32;
          }
          return fd.isPacked() ? FieldType.SFIXED32_LIST_PACKED : FieldType.SFIXED32_LIST;
        case SFIXED64:
          if (!fd.isRepeated()) {
            return FieldType.SFIXED64;
          }
          return fd.isPacked() ? FieldType.SFIXED64_LIST_PACKED : FieldType.SFIXED64_LIST;
        case SINT32:
          if (!fd.isRepeated()) {
            return FieldType.SINT32;
          }
          return fd.isPacked() ? FieldType.SINT32_LIST_PACKED : FieldType.SINT32_LIST;
        case SINT64:
          if (!fd.isRepeated()) {
            return FieldType.SINT64;
          }
          return fd.isPacked() ? FieldType.SINT64_LIST_PACKED : FieldType.SINT64_LIST;
        case STRING:
          return fd.isRepeated() ? FieldType.STRING_LIST : FieldType.STRING;
        case UINT32:
          if (!fd.isRepeated()) {
            return FieldType.UINT32;
          }
          return fd.isPacked() ? FieldType.UINT32_LIST_PACKED : FieldType.UINT32_LIST;
        case UINT64:
          if (!fd.isRepeated()) {
            return FieldType.UINT64;
          }
          return fd.isPacked() ? FieldType.UINT64_LIST_PACKED : FieldType.UINT64_LIST;
        default:
          throw new IllegalArgumentException("Unsupported field type: " + fd.getType());
      }
    }
  
    private static Field bitField(Class<?> messageType, int index) {
      return field(messageType, "bitField" + index + "_");
    }
  
    private static Field field(Class<?> messageType, FieldDescriptor fd) {
      return field(messageType, getFieldName(fd));
    }
  
    private static Field cachedSizeField(Class<?> messageType, FieldDescriptor fd) {
      return field(messageType, getCachedSizeFieldName(fd));
    }
  
    private static Field field(Class<?> messageType, String fieldName) {
      try {
        return messageType.getDeclaredField(fieldName);
      } catch (Exception e) {
        throw new IllegalArgumentException(
            "Unable to find field " + fieldName + " in message class " + messageType.getName());
      }
    }
  
    static String getFieldName(FieldDescriptor fd) {
      String name = (fd.getType() == FieldDescriptor.Type.GROUP)
                    ? fd.getMessageType().getName()
                    : fd.getName();
      String suffix = specialFieldNames.contains(name) ? "__" : "_";
      return snakeCaseToCamelCase(name) + suffix;
    }
  
    private static String getCachedSizeFieldName(FieldDescriptor fd) {
      return snakeCaseToCamelCase(fd.getName()) + "MemoizedSerializedSize";
    }
  
    /**
     * This method must match exactly with the corresponding function in protocol compiler. See:
     * https://github.com/google/protobuf/blob/v3.0.0/src/google/protobuf/compiler/java/java_helpers.cc#L153
     */
    private static String snakeCaseToCamelCase(String snakeCase) {
      StringBuilder sb = new StringBuilder(snakeCase.length() + 1);
      boolean capNext = false;
      for (int ctr = 0; ctr < snakeCase.length(); ctr++) {
        char next = snakeCase.charAt(ctr);
        if (next == '_') {
          capNext = true;
        } else if (Character.isDigit(next)) {
          sb.append(next);
          capNext = true;
        } else if (capNext) {
          sb.append(Character.toUpperCase(next));
          capNext = false;
        } else if (ctr == 0) {
          sb.append(Character.toLowerCase(next));
        } else {
          sb.append(next);
        }
      }
      return sb.toString();
    }
  
    /**
     * Inspects the message to identify the stored type for a message field that is part of a oneof.
     */
    private static Class<?> getOneofStoredTypeForMessage(Class<?> messageType, FieldDescriptor fd) {
      try {
        String name = fd.getType() == Type.GROUP ? fd.getMessageType().getName() : fd.getName();
        Method getter = messageType.getDeclaredMethod(getterForField(name));
        return getter.getReturnType();
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
    }
  
    /** Inspects the message to identify the message type of a repeated message field. */
    private static Class<?> getTypeForRepeatedMessageField(Class<?> messageType, FieldDescriptor fd) {
      try {
        String name = fd.getType() == Type.GROUP ? fd.getMessageType().getName() : fd.getName();
        Method getter = messageType.getDeclaredMethod(getterForField(name), int.class);
        return getter.getReturnType();
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
    }
  
    /** Constructs the name of the get method for the given field in the proto. */
    private static String getterForField(String snakeCase) {
      String camelCase = snakeCaseToCamelCase(snakeCase);
      StringBuilder builder = new StringBuilder("get");
      // Capitalize the first character in the field name.
      builder.append(Character.toUpperCase(camelCase.charAt(0)));
      builder.append(camelCase.substring(1, camelCase.length()));
      return builder.toString();
    }
  
    private static final class OneofState {
      private OneofInfo[] oneofs = new OneofInfo[2];
  
      OneofInfo getOneof(Class<?> messageType, OneofDescriptor desc) {
        int index = desc.getIndex();
        if (index >= oneofs.length) {
          // Grow the array.
          oneofs = Arrays.copyOf(oneofs, index * 2);
        }
        OneofInfo info = oneofs[index];
        if (info == null) {
          info = newInfo(messageType, desc);
          oneofs[index] = info;
        }
        return info;
      }
  
      private static OneofInfo newInfo(Class<?> messageType, OneofDescriptor desc) {
        String camelCase = snakeCaseToCamelCase(desc.getName());
        String valueFieldName = camelCase + "_";
        String caseFieldName = camelCase + "Case_";
  
        return new OneofInfo(
            desc.getIndex(), field(messageType, caseFieldName), field(messageType, valueFieldName));
      }
    }
  }
  