| // 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.Internal.checkNotNull; |
| |
| import com.google.protobuf.Internal.EnumVerifier; |
| import java.lang.reflect.Field; |
| |
| /** Information for a single field in a protobuf message class. */ |
| @CheckReturnValue |
| @ExperimentalApi |
| final class FieldInfo implements Comparable<FieldInfo> { |
| private final Field field; |
| private final FieldType type; |
| private final Class<?> messageClass; // The message type for repeated message fields. |
| private final int fieldNumber; |
| private final Field presenceField; |
| private final int presenceMask; |
| private final boolean required; |
| private final boolean enforceUtf8; |
| private final OneofInfo oneof; |
| private final Field cachedSizeField; |
| /** |
| * The actual type stored in the oneof value for this field. Since the oneof value is an {@link |
| * Object}, primitives will store their boxed type. Only valid in conjunction with {@link #oneof} |
| * (both must be either null or non-null. |
| */ |
| private final Class<?> oneofStoredType; |
| |
| // TODO(liujisi): make map default entry lazy? |
| private final Object mapDefaultEntry; |
| |
| private final EnumVerifier enumVerifier; |
| |
| /** Constructs a new descriptor for a field. */ |
| public static FieldInfo forField( |
| Field field, int fieldNumber, FieldType fieldType, boolean enforceUtf8) { |
| checkFieldNumber(fieldNumber); |
| checkNotNull(field, "field"); |
| checkNotNull(fieldType, "fieldType"); |
| if (fieldType == FieldType.MESSAGE_LIST || fieldType == FieldType.GROUP_LIST) { |
| throw new IllegalStateException("Shouldn't be called for repeated message fields."); |
| } |
| return new FieldInfo( |
| field, |
| fieldNumber, |
| fieldType, |
| /* messageClass= */ null, |
| /* presenceField= */ null, |
| /* presenceMask= */ 0, |
| /* required= */ false, |
| enforceUtf8, |
| /* oneof= */ null, |
| /* oneofStoredType= */ null, |
| /* mapDefaultEntry= */ null, |
| /* enumVerifier= */ null, |
| /* cachedSizeField= */ null); |
| } |
| |
| /** Constructs a new descriptor for a packed field. */ |
| public static FieldInfo forPackedField( |
| Field field, int fieldNumber, FieldType fieldType, Field cachedSizeField) { |
| checkFieldNumber(fieldNumber); |
| checkNotNull(field, "field"); |
| checkNotNull(fieldType, "fieldType"); |
| if (fieldType == FieldType.MESSAGE_LIST || fieldType == FieldType.GROUP_LIST) { |
| throw new IllegalStateException("Shouldn't be called for repeated message fields."); |
| } |
| return new FieldInfo( |
| field, |
| fieldNumber, |
| fieldType, |
| /* messageClass= */ null, |
| /* presenceField= */ null, |
| /* presenceMask= */ 0, |
| /* required= */ false, |
| /* enforceUtf8= */ false, |
| /* oneof= */ null, |
| /* oneofStoredType= */ null, |
| /* mapDefaultEntry= */ null, |
| /* enumVerifier= */ null, |
| cachedSizeField); |
| } |
| |
| /** Constructs a new descriptor for a repeated message field. */ |
| public static FieldInfo forRepeatedMessageField( |
| Field field, int fieldNumber, FieldType fieldType, Class<?> messageClass) { |
| checkFieldNumber(fieldNumber); |
| checkNotNull(field, "field"); |
| checkNotNull(fieldType, "fieldType"); |
| checkNotNull(messageClass, "messageClass"); |
| return new FieldInfo( |
| field, |
| fieldNumber, |
| fieldType, |
| messageClass, |
| /* presenceField= */ null, |
| /* presenceMask= */ 0, |
| /* required= */ false, |
| /* enforceUtf8= */ false, |
| /* oneof= */ null, |
| /* oneofStoredType= */ null, |
| /* mapDefaultEntry= */ null, |
| /* enumVerifier= */ null, |
| /* cachedSizeField= */ null); |
| } |
| |
| public static FieldInfo forFieldWithEnumVerifier( |
| Field field, int fieldNumber, FieldType fieldType, EnumVerifier enumVerifier) { |
| checkFieldNumber(fieldNumber); |
| checkNotNull(field, "field"); |
| return new FieldInfo( |
| field, |
| fieldNumber, |
| fieldType, |
| /* messageClass= */ null, |
| /* presenceField= */ null, |
| /* presenceMask= */ 0, |
| /* required= */ false, |
| /* enforceUtf8= */ false, |
| /* oneof= */ null, |
| /* oneofStoredType= */ null, |
| /* mapDefaultEntry= */ null, |
| enumVerifier, |
| /* cachedSizeField= */ null); |
| } |
| |
| public static FieldInfo forPackedFieldWithEnumVerifier( |
| Field field, |
| int fieldNumber, |
| FieldType fieldType, |
| EnumVerifier enumVerifier, |
| Field cachedSizeField) { |
| checkFieldNumber(fieldNumber); |
| checkNotNull(field, "field"); |
| return new FieldInfo( |
| field, |
| fieldNumber, |
| fieldType, |
| /* messageClass= */ null, |
| /* presenceField= */ null, |
| /* presenceMask= */ 0, |
| /* required= */ false, |
| /* enforceUtf8= */ false, |
| /* oneof= */ null, |
| /* oneofStoredType= */ null, |
| /* mapDefaultEntry= */ null, |
| enumVerifier, |
| cachedSizeField); |
| } |
| |
| /** Constructor for a proto2 optional field. */ |
| public static FieldInfo forProto2OptionalField( |
| Field field, |
| int fieldNumber, |
| FieldType fieldType, |
| Field presenceField, |
| int presenceMask, |
| boolean enforceUtf8, |
| EnumVerifier enumVerifier) { |
| checkFieldNumber(fieldNumber); |
| checkNotNull(field, "field"); |
| checkNotNull(fieldType, "fieldType"); |
| checkNotNull(presenceField, "presenceField"); |
| if (presenceField != null && !isExactlyOneBitSet(presenceMask)) { |
| throw new IllegalArgumentException( |
| "presenceMask must have exactly one bit set: " + presenceMask); |
| } |
| return new FieldInfo( |
| field, |
| fieldNumber, |
| fieldType, |
| /* messageClass= */ null, |
| presenceField, |
| presenceMask, |
| /* required= */ false, |
| enforceUtf8, |
| /* oneof= */ null, |
| /* oneofStoredType= */ null, |
| /* mapDefaultEntry= */ null, |
| enumVerifier, |
| /* cachedSizeField= */ null); |
| } |
| |
| /** |
| * Constructor for a field that is part of a oneof. |
| * |
| * @param fieldNumber the unique field number for this field within the message. |
| * @param fieldType the type of the field (must be non-null). |
| * @param oneof the oneof for which this field is associated (must be non-null). |
| * @param oneofStoredType the actual type stored in the oneof value for this field. Since the |
| * oneof value is an {@link Object}, primitives will store their boxed type. Must be non-null. |
| * @param enforceUtf8 Only used for string fields. If {@code true}, will enforce UTF-8 on a string |
| * field. |
| * @return the {@link FieldInfo} describing this field. |
| */ |
| public static FieldInfo forOneofMemberField( |
| int fieldNumber, |
| FieldType fieldType, |
| OneofInfo oneof, |
| Class<?> oneofStoredType, |
| boolean enforceUtf8, |
| EnumVerifier enumVerifier) { |
| checkFieldNumber(fieldNumber); |
| checkNotNull(fieldType, "fieldType"); |
| checkNotNull(oneof, "oneof"); |
| checkNotNull(oneofStoredType, "oneofStoredType"); |
| if (!fieldType.isScalar()) { |
| throw new IllegalArgumentException( |
| "Oneof is only supported for scalar fields. Field " |
| + fieldNumber |
| + " is of type " |
| + fieldType); |
| } |
| return new FieldInfo( |
| /* field= */ null, |
| fieldNumber, |
| fieldType, |
| /* messageClass= */ null, |
| /* presenceField= */ null, |
| /* presenceMask= */ 0, |
| /* required= */ false, |
| enforceUtf8, |
| oneof, |
| oneofStoredType, |
| /* mapDefaultEntry= */ null, |
| enumVerifier, |
| /* cachedSizeField= */ null); |
| } |
| |
| private static void checkFieldNumber(int fieldNumber) { |
| if (fieldNumber <= 0) { |
| throw new IllegalArgumentException("fieldNumber must be positive: " + fieldNumber); |
| } |
| } |
| |
| /** Constructor for a proto2 required field. */ |
| public static FieldInfo forProto2RequiredField( |
| Field field, |
| int fieldNumber, |
| FieldType fieldType, |
| Field presenceField, |
| int presenceMask, |
| boolean enforceUtf8, |
| EnumVerifier enumVerifier) { |
| checkFieldNumber(fieldNumber); |
| checkNotNull(field, "field"); |
| checkNotNull(fieldType, "fieldType"); |
| checkNotNull(presenceField, "presenceField"); |
| if (presenceField != null && !isExactlyOneBitSet(presenceMask)) { |
| throw new IllegalArgumentException( |
| "presenceMask must have exactly one bit set: " + presenceMask); |
| } |
| return new FieldInfo( |
| field, |
| fieldNumber, |
| fieldType, |
| /* messageClass= */ null, |
| presenceField, |
| presenceMask, |
| /* required= */ true, |
| enforceUtf8, |
| /* oneof= */ null, |
| /* oneofStoredType= */ null, |
| /* mapDefaultEntry= */ null, |
| /* enumVerifier= */ enumVerifier, |
| /* cachedSizeField= */ null); |
| } |
| |
| public static FieldInfo forMapField( |
| Field field, int fieldNumber, Object mapDefaultEntry, EnumVerifier enumVerifier) { |
| checkNotNull(mapDefaultEntry, "mapDefaultEntry"); |
| checkFieldNumber(fieldNumber); |
| checkNotNull(field, "field"); |
| return new FieldInfo( |
| field, |
| fieldNumber, |
| FieldType.MAP, |
| /* messageClass= */ null, |
| /* presenceField= */ null, |
| /* presenceMask= */ 0, |
| /* required= */ false, |
| /* enforceUtf8= */ true, |
| /* oneof= */ null, |
| /* oneofStoredType= */ null, |
| mapDefaultEntry, |
| enumVerifier, |
| /* cachedSizeField= */ null); |
| } |
| |
| private FieldInfo( |
| Field field, |
| int fieldNumber, |
| FieldType type, |
| Class<?> messageClass, |
| Field presenceField, |
| int presenceMask, |
| boolean required, |
| boolean enforceUtf8, |
| OneofInfo oneof, |
| Class<?> oneofStoredType, |
| Object mapDefaultEntry, |
| EnumVerifier enumVerifier, |
| Field cachedSizeField) { |
| this.field = field; |
| this.type = type; |
| this.messageClass = messageClass; |
| this.fieldNumber = fieldNumber; |
| this.presenceField = presenceField; |
| this.presenceMask = presenceMask; |
| this.required = required; |
| this.enforceUtf8 = enforceUtf8; |
| this.oneof = oneof; |
| this.oneofStoredType = oneofStoredType; |
| this.mapDefaultEntry = mapDefaultEntry; |
| this.enumVerifier = enumVerifier; |
| this.cachedSizeField = cachedSizeField; |
| } |
| |
| /** Gets the field number for the field. */ |
| public int getFieldNumber() { |
| return fieldNumber; |
| } |
| |
| /** Gets the subject {@link Field} of this descriptor. */ |
| public Field getField() { |
| return field; |
| } |
| |
| /** Gets the type information for the field. */ |
| public FieldType getType() { |
| return type; |
| } |
| |
| /** Gets the oneof for which this field is a member, or {@code null} if not part of a oneof. */ |
| public OneofInfo getOneof() { |
| return oneof; |
| } |
| |
| /** |
| * Gets the actual type stored in the oneof value by this field. Since the oneof value is an |
| * {@link Object}, primitives will store their boxed type. For non-oneof fields, this will always |
| * be {@code null}. |
| */ |
| public Class<?> getOneofStoredType() { |
| return oneofStoredType; |
| } |
| |
| /** Gets the {@code EnumVerifier} if the field is an enum field. */ |
| public EnumVerifier getEnumVerifier() { |
| return enumVerifier; |
| } |
| |
| @Override |
| public int compareTo(FieldInfo o) { |
| return fieldNumber - o.fieldNumber; |
| } |
| |
| /** |
| * For repeated message fields, returns the message type of the field. For other fields, returns |
| * {@code null}. |
| */ |
| public Class<?> getListElementType() { |
| return messageClass; |
| } |
| |
| /** Gets the presence bit field. Only valid for unary fields. For lists, returns {@code null}. */ |
| public Field getPresenceField() { |
| return presenceField; |
| } |
| |
| public Object getMapDefaultEntry() { |
| return mapDefaultEntry; |
| } |
| |
| /** |
| * If {@link #getPresenceField()} is non-{@code null}, returns the mask used to identify the |
| * presence bit for this field in the message. |
| */ |
| public int getPresenceMask() { |
| return presenceMask; |
| } |
| |
| /** Whether this is a required field. */ |
| public boolean isRequired() { |
| return required; |
| } |
| |
| /** |
| * Whether a UTF-8 should be enforced on string fields. Only applies to strings and string lists. |
| */ |
| public boolean isEnforceUtf8() { |
| return enforceUtf8; |
| } |
| |
| public Field getCachedSizeField() { |
| return cachedSizeField; |
| } |
| |
| /** |
| * For singular or repeated message fields, returns the message type. For other fields, returns |
| * {@code null}. |
| */ |
| public Class<?> getMessageFieldClass() { |
| switch (type) { |
| case MESSAGE: |
| case GROUP: |
| return field != null ? field.getType() : oneofStoredType; |
| case MESSAGE_LIST: |
| case GROUP_LIST: |
| return messageClass; |
| default: |
| return null; |
| } |
| } |
| |
| public static Builder newBuilder() { |
| return new Builder(); |
| } |
| |
| /** A builder for {@link FieldInfo} instances. */ |
| public static final class Builder { |
| private Field field; |
| private FieldType type; |
| private int fieldNumber; |
| private Field presenceField; |
| private int presenceMask; |
| private boolean required; |
| private boolean enforceUtf8; |
| private OneofInfo oneof; |
| private Class<?> oneofStoredType; |
| private Object mapDefaultEntry; |
| private EnumVerifier enumVerifier; |
| private Field cachedSizeField; |
| |
| private Builder() {} |
| |
| /** |
| * Specifies the actual field on the message represented by this field. This should not be |
| * called for oneof member fields. |
| */ |
| public Builder withField(Field field) { |
| if (oneof != null) { |
| throw new IllegalStateException("Cannot set field when building a oneof."); |
| } |
| this.field = field; |
| return this; |
| } |
| |
| /** Specifies the type of this field. */ |
| public Builder withType(FieldType type) { |
| this.type = type; |
| return this; |
| } |
| |
| /** Specifies the unique field number for this field within the message. */ |
| public Builder withFieldNumber(int fieldNumber) { |
| this.fieldNumber = fieldNumber; |
| return this; |
| } |
| |
| /** Specifies proto2 presence information. This should not be called for oneof fields. */ |
| public Builder withPresence(Field presenceField, int presenceMask) { |
| this.presenceField = checkNotNull(presenceField, "presenceField"); |
| this.presenceMask = presenceMask; |
| return this; |
| } |
| |
| /** |
| * Sets the information for building a oneof member field. This is incompatible with {@link |
| * #withField(Field)} and {@link #withPresence(Field, int)}. |
| * |
| * @param oneof the oneof for which this field is associated. |
| * @param oneofStoredType the actual type stored in the oneof value for this field. Since the |
| * oneof value is an {@link Object}, primitives will store their boxed type. |
| */ |
| public Builder withOneof(OneofInfo oneof, Class<?> oneofStoredType) { |
| if (field != null || presenceField != null) { |
| throw new IllegalStateException( |
| "Cannot set oneof when field or presenceField have been provided"); |
| } |
| this.oneof = oneof; |
| this.oneofStoredType = oneofStoredType; |
| return this; |
| } |
| |
| public Builder withRequired(boolean required) { |
| this.required = required; |
| return this; |
| } |
| |
| public Builder withMapDefaultEntry(Object mapDefaultEntry) { |
| this.mapDefaultEntry = mapDefaultEntry; |
| return this; |
| } |
| |
| public Builder withEnforceUtf8(boolean enforceUtf8) { |
| this.enforceUtf8 = enforceUtf8; |
| return this; |
| } |
| |
| public Builder withEnumVerifier(EnumVerifier enumVerifier) { |
| this.enumVerifier = enumVerifier; |
| return this; |
| } |
| |
| public Builder withCachedSizeField(Field cachedSizeField) { |
| this.cachedSizeField = cachedSizeField; |
| return this; |
| } |
| |
| public FieldInfo build() { |
| if (oneof != null) { |
| return forOneofMemberField( |
| fieldNumber, type, oneof, oneofStoredType, enforceUtf8, enumVerifier); |
| } |
| if (mapDefaultEntry != null) { |
| return forMapField(field, fieldNumber, mapDefaultEntry, enumVerifier); |
| } |
| if (presenceField != null) { |
| if (required) { |
| return forProto2RequiredField( |
| field, fieldNumber, type, presenceField, presenceMask, enforceUtf8, enumVerifier); |
| } else { |
| return forProto2OptionalField( |
| field, fieldNumber, type, presenceField, presenceMask, enforceUtf8, enumVerifier); |
| } |
| } |
| if (enumVerifier != null) { |
| if (cachedSizeField == null) { |
| return forFieldWithEnumVerifier(field, fieldNumber, type, enumVerifier); |
| } else { |
| return forPackedFieldWithEnumVerifier( |
| field, fieldNumber, type, enumVerifier, cachedSizeField); |
| } |
| } else { |
| if (cachedSizeField == null) { |
| return forField(field, fieldNumber, type, enforceUtf8); |
| } else { |
| return forPackedField(field, fieldNumber, type, cachedSizeField); |
| } |
| } |
| } |
| } |
| |
| private static boolean isExactlyOneBitSet(int value) { |
| return value != 0 && (value & (value - 1)) == 0; |
| } |
| } |