diff options
Diffstat (limited to 'java/src/main/java/com/google/protobuf/FieldSet.java')
-rw-r--r-- | java/src/main/java/com/google/protobuf/FieldSet.java | 910 |
1 files changed, 449 insertions, 461 deletions
diff --git a/java/src/main/java/com/google/protobuf/FieldSet.java b/java/src/main/java/com/google/protobuf/FieldSet.java index 01da3faf..93e55f29 100644 --- a/java/src/main/java/com/google/protobuf/FieldSet.java +++ b/java/src/main/java/com/google/protobuf/FieldSet.java @@ -30,16 +30,13 @@ package com.google.protobuf; -import com.google.protobuf.Descriptors.Descriptor; -import com.google.protobuf.Descriptors.FieldDescriptor; -import com.google.protobuf.Descriptors.EnumValueDescriptor; - import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.TreeMap; import java.util.List; import java.util.Map; +import java.io.IOException; /** * A class which represents an arbitrary set of fields of some message type. @@ -49,43 +46,70 @@ import java.util.Map; * * @author kenton@google.com Kenton Varda */ -final class FieldSet { - private Map<FieldDescriptor, Object> fields; +final class FieldSet<FieldDescriptorType extends + FieldSet.FieldDescriptorLite<FieldDescriptorType>> { + /** + * Interface for a FieldDescriptor or lite extension descriptor. This + * prevents FieldSet from depending on {@link Descriptors.FieldDescriptor}. + */ + public interface FieldDescriptorLite<T extends FieldDescriptorLite<T>> + extends Comparable<T> { + int getNumber(); + WireFormat.FieldType getLiteType(); + WireFormat.JavaType getLiteJavaType(); + boolean isRepeated(); + boolean isPacked(); + Internal.EnumLiteMap<?> getEnumType(); + + // If getLiteJavaType() == MESSAGE, this merges a message object of the + // type into a builder of the type. Returns {@code to}. + MessageLite.Builder internalMergeFrom( + MessageLite.Builder to, MessageLite from); + } + + private Map<FieldDescriptorType, Object> fields; /** Construct a new FieldSet. */ private FieldSet() { // Use a TreeMap because fields need to be in canonical order when // serializing. - this.fields = new TreeMap<FieldDescriptor, Object>(); + // TODO(kenton): Maybe use some sort of sparse array instead? It would + // even make sense to store the first 16 or so tags in a flat array + // to make DynamicMessage faster. + fields = new TreeMap<FieldDescriptorType, Object>(); } /** - * Construct a new FieldSet with the given map. This is only used by - * DEFAULT_INSTANCE, to pass in an immutable empty map. + * Construct an empty FieldSet. This is only used to initialize + * DEFAULT_INSTANCE. */ - private FieldSet(Map<FieldDescriptor, Object> fields) { - this.fields = fields; + private FieldSet(final boolean dummy) { + this.fields = Collections.emptyMap(); } /** Construct a new FieldSet. */ - public static FieldSet newFieldSet() { - return new FieldSet(); + public static <T extends FieldSet.FieldDescriptorLite<T>> + FieldSet<T> newFieldSet() { + return new FieldSet<T>(); } /** Get an immutable empty FieldSet. */ - public static FieldSet emptySet() { + @SuppressWarnings("unchecked") + public static <T extends FieldSet.FieldDescriptorLite<T>> + FieldSet<T> emptySet() { return DEFAULT_INSTANCE; } - private static final FieldSet DEFAULT_INSTANCE = - new FieldSet(Collections.<FieldDescriptor, Object>emptyMap()); + @SuppressWarnings("unchecked") + private static final FieldSet DEFAULT_INSTANCE = new FieldSet(true); /** Make this FieldSet immutable from this point forward. */ @SuppressWarnings("unchecked") public void makeImmutable() { - for (Map.Entry<FieldDescriptor, Object> entry: fields.entrySet()) { + for (final Map.Entry<FieldDescriptorType, Object> entry: + fields.entrySet()) { if (entry.getKey().isRepeated()) { - List value = (List)entry.getValue(); - entry.setValue(Collections.unmodifiableList(value)); + final List value = (List)entry.getValue(); + fields.put(entry.getKey(), Collections.unmodifiableList(value)); } } fields = Collections.unmodifiableMap(fields); @@ -98,56 +122,52 @@ final class FieldSet { fields.clear(); } - /** See {@link Message#getAllFields()}. */ - public Map<Descriptors.FieldDescriptor, Object> getAllFields() { + /** + * Get a simple map containing all the fields. + */ + public Map<FieldDescriptorType, Object> getAllFields() { return Collections.unmodifiableMap(fields); } /** - * Get an interator to the field map. This iterator should not be leaked + * Get an iterator to the field map. This iterator should not be leaked * out of the protobuf library as it is not protected from mutation. */ - public Iterator<Map.Entry<Descriptors.FieldDescriptor, Object>> iterator() { + public Iterator<Map.Entry<FieldDescriptorType, Object>> iterator() { return fields.entrySet().iterator(); } - /** See {@link Message#hasField(Descriptors.FieldDescriptor)}. */ - public boolean hasField(Descriptors.FieldDescriptor field) { - if (field.isRepeated()) { + /** + * Useful for implementing + * {@link Message#hasField(Descriptors.FieldDescriptor)}. + */ + public boolean hasField(final FieldDescriptorType descriptor) { + if (descriptor.isRepeated()) { throw new IllegalArgumentException( "hasField() can only be called on non-repeated fields."); } - return fields.containsKey(field); + return fields.get(descriptor) != null; } /** - * See {@link Message#getField(Descriptors.FieldDescriptor)}. This method - * returns {@code null} if the field is a singular message type and is not - * set; in this case it is up to the caller to fetch the message's default - * instance. + * Useful for implementing + * {@link Message#getField(Descriptors.FieldDescriptor)}. This method + * returns {@code null} if the field is not set; in this case it is up + * to the caller to fetch the field's default value. */ - public Object getField(Descriptors.FieldDescriptor field) { - Object result = fields.get(field); - if (result == null) { - if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { - if (field.isRepeated()) { - return Collections.emptyList(); - } else { - return null; - } - } else { - return field.getDefaultValue(); - } - } else { - return result; - } + public Object getField(final FieldDescriptorType descriptor) { + return fields.get(descriptor); } - /** See {@link Message.Builder#setField(Descriptors.FieldDescriptor,Object)}. */ + /** + * Useful for implementing + * {@link Message.Builder#setField(Descriptors.FieldDescriptor,Object)}. + */ @SuppressWarnings("unchecked") - public void setField(Descriptors.FieldDescriptor field, Object value) { - if (field.isRepeated()) { + public void setField(final FieldDescriptorType descriptor, + Object value) { + if (descriptor.isRepeated()) { if (!(value instanceof List)) { throw new IllegalArgumentException( "Wrong object type used with protocol message reflection."); @@ -155,78 +175,108 @@ final class FieldSet { // Wrap the contents in a new list so that the caller cannot change // the list's contents after setting it. - List newList = new ArrayList(); + final List newList = new ArrayList(); newList.addAll((List)value); - for (Object element : newList) { - verifyType(field, element); + for (final Object element : newList) { + verifyType(descriptor.getLiteType(), element); } value = newList; } else { - verifyType(field, value); + verifyType(descriptor.getLiteType(), value); } - fields.put(field, value); + fields.put(descriptor, value); } - /** See {@link Message.Builder#clearField(Descriptors.FieldDescriptor)}. */ - public void clearField(Descriptors.FieldDescriptor field) { - fields.remove(field); + /** + * Useful for implementing + * {@link Message.Builder#clearField(Descriptors.FieldDescriptor)}. + */ + public void clearField(final FieldDescriptorType descriptor) { + fields.remove(descriptor); } - /** See {@link Message#getRepeatedFieldCount(Descriptors.FieldDescriptor)}. */ - public int getRepeatedFieldCount(Descriptors.FieldDescriptor field) { - if (!field.isRepeated()) { + /** + * Useful for implementing + * {@link Message#getRepeatedFieldCount(Descriptors.FieldDescriptor)}. + */ + public int getRepeatedFieldCount(final FieldDescriptorType descriptor) { + if (!descriptor.isRepeated()) { throw new IllegalArgumentException( - "getRepeatedFieldCount() can only be called on repeated fields."); + "getRepeatedField() can only be called on repeated fields."); } - return ((List)getField(field)).size(); + final Object value = fields.get(descriptor); + if (value == null) { + return 0; + } else { + return ((List) value).size(); + } } - /** See {@link Message#getRepeatedField(Descriptors.FieldDescriptor,int)}. */ - public Object getRepeatedField(Descriptors.FieldDescriptor field, int index) { - if (!field.isRepeated()) { + /** + * Useful for implementing + * {@link Message#getRepeatedField(Descriptors.FieldDescriptor,int)}. + */ + public Object getRepeatedField(final FieldDescriptorType descriptor, + final int index) { + if (!descriptor.isRepeated()) { throw new IllegalArgumentException( "getRepeatedField() can only be called on repeated fields."); } - return ((List)getField(field)).get(index); + final Object value = fields.get(descriptor); + + if (value == null) { + throw new IndexOutOfBoundsException(); + } else { + return ((List) value).get(index); + } } - /** See {@link Message.Builder#setRepeatedField(Descriptors.FieldDescriptor,int,Object)}. */ + /** + * Useful for implementing + * {@link Message.Builder#setRepeatedField(Descriptors.FieldDescriptor,int,Object)}. + */ @SuppressWarnings("unchecked") - public void setRepeatedField(Descriptors.FieldDescriptor field, int index, - Object value) { - if (!field.isRepeated()) { + public void setRepeatedField(final FieldDescriptorType descriptor, + final int index, + final Object value) { + if (!descriptor.isRepeated()) { throw new IllegalArgumentException( - "setRepeatedField() can only be called on repeated fields."); + "getRepeatedField() can only be called on repeated fields."); } - verifyType(field, value); - - List list = (List)fields.get(field); + final Object list = fields.get(descriptor); if (list == null) { throw new IndexOutOfBoundsException(); } - list.set(index, value); + verifyType(descriptor.getLiteType(), value); + ((List) list).set(index, value); } - /** See {@link Message.Builder#addRepeatedField(Descriptors.FieldDescriptor,Object)}. */ + /** + * Useful for implementing + * {@link Message.Builder#addRepeatedField(Descriptors.FieldDescriptor,Object)}. + */ @SuppressWarnings("unchecked") - public void addRepeatedField(Descriptors.FieldDescriptor field, - Object value) { - if (!field.isRepeated()) { + public void addRepeatedField(final FieldDescriptorType descriptor, + final Object value) { + if (!descriptor.isRepeated()) { throw new IllegalArgumentException( - "setRepeatedField() can only be called on repeated fields."); + "addRepeatedField() can only be called on repeated fields."); } - verifyType(field, value); + verifyType(descriptor.getLiteType(), value); - List list = (List)fields.get(field); - if (list == null) { + final Object existingValue = fields.get(descriptor); + List list; + if (existingValue == null) { list = new ArrayList(); - fields.put(field, list); + fields.put(descriptor, list); + } else { + list = (List) existingValue; } list.add(value); @@ -239,9 +289,14 @@ final class FieldSet { * * @throws IllegalArgumentException The value is not of the right type. */ - private void verifyType(FieldDescriptor field, Object value) { + private static void verifyType(final WireFormat.FieldType type, + final Object value) { + if (value == null) { + throw new NullPointerException(); + } + boolean isValid = false; - switch (field.getJavaType()) { + switch (type.getJavaType()) { case INT: isValid = value instanceof Integer ; break; case LONG: isValid = value instanceof Long ; break; case FLOAT: isValid = value instanceof Float ; break; @@ -250,26 +305,25 @@ final class FieldSet { case STRING: isValid = value instanceof String ; break; case BYTE_STRING: isValid = value instanceof ByteString; break; case ENUM: - isValid = value instanceof EnumValueDescriptor && - ((EnumValueDescriptor)value).getType() == field.getEnumType(); + // TODO(kenton): Caller must do type checking here, I guess. + isValid = value instanceof Internal.EnumLite; break; case MESSAGE: - isValid = value instanceof Message && - ((Message)value).getDescriptorForType() == field.getMessageType(); + // TODO(kenton): Caller must do type checking here, I guess. + isValid = value instanceof MessageLite; break; } if (!isValid) { - // When chaining calls to setField(), it can be hard to tell from - // the stack trace which exact call failed, since the whole chain is - // considered one line of code. So, let's make sure to include the - // field name and other useful info in the exception. + // TODO(kenton): When chaining calls to setField(), it can be hard to + // tell from the stack trace which exact call failed, since the whole + // chain is considered one line of code. It would be nice to print + // more information here, e.g. naming the field. We used to do that. + // But we can't now that FieldSet doesn't use descriptors. Maybe this + // isn't a big deal, though, since it would only really apply when using + // reflection and generally people don't chain reflection setters. throw new IllegalArgumentException( - "Wrong object type used with protocol message reflection. " + - "Message type \"" + field.getContainingType().getFullName() + - "\", field \"" + - (field.isExtension() ? field.getFullName() : field.getName()) + - "\", value was type \"" + value.getClass().getName() + "\"."); + "Wrong object type used with protocol message reflection."); } } @@ -284,17 +338,19 @@ final class FieldSet { */ @SuppressWarnings("unchecked") public boolean isInitialized() { - for (Map.Entry<FieldDescriptor, Object> entry : fields.entrySet()) { - FieldDescriptor field = entry.getKey(); - if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { - if (field.isRepeated()) { - for (Message element : (List<Message>) entry.getValue()) { + for (final Map.Entry<FieldDescriptorType, Object> entry: + fields.entrySet()) { + final FieldDescriptorType descriptor = entry.getKey(); + if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE) { + if (descriptor.isRepeated()) { + for (final MessageLite element: + (List<MessageLite>) entry.getValue()) { if (!element.isInitialized()) { return false; } } } else { - if (!((Message) entry.getValue()).isInitialized()) { + if (!((MessageLite) entry.getValue()).isInitialized()) { return false; } } @@ -305,59 +361,17 @@ final class FieldSet { } /** - * Like {@link #isInitialized()}, but also checks for the presence of - * all required fields in the given type. + * Given a field type, return the wire type. + * + * @returns One of the {@code WIRETYPE_} constants defined in + * {@link WireFormat}. */ - @SuppressWarnings("unchecked") - public boolean isInitialized(Descriptor type) { - // Check that all required fields are present. - for (FieldDescriptor field : type.getFields()) { - if (field.isRequired()) { - if (!hasField(field)) { - return false; - } - } - } - - // Check that embedded messages are initialized. - return isInitialized(); - } - - /** See {@link Message.Builder#mergeFrom(Message)}. */ - @SuppressWarnings("unchecked") - public void mergeFrom(Message other) { - // Note: We don't attempt to verify that other's fields have valid - // types. Doing so would be a losing battle. We'd have to verify - // all sub-messages as well, and we'd have to make copies of all of - // them to insure that they don't change after verification (since - // the Message interface itself cannot enforce immutability of - // implementations). - // TODO(kenton): Provide a function somewhere called makeDeepCopy() - // which allows people to make secure deep copies of messages. - - for (Map.Entry<FieldDescriptor, Object> entry : - other.getAllFields().entrySet()) { - FieldDescriptor field = entry.getKey(); - if (field.isRepeated()) { - List existingValue = (List)fields.get(field); - if (existingValue == null) { - existingValue = new ArrayList(); - fields.put(field, existingValue); - } - existingValue.addAll((List)entry.getValue()); - } else if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { - Message existingValue = (Message)fields.get(field); - if (existingValue == null) { - setField(field, entry.getValue()); - } else { - setField(field, - existingValue.toBuilder() - .mergeFrom((Message)entry.getValue()) - .build()); - } - } else { - setField(field, entry.getValue()); - } + static int getWireFormatForFieldType(final WireFormat.FieldType type, + boolean isPacked) { + if (isPacked) { + return WireFormat.WIRETYPE_LENGTH_DELIMITED; + } else { + return type.getWireType(); } } @@ -365,360 +379,334 @@ final class FieldSet { * Like {@link #mergeFrom(Message)}, but merges from another {@link FieldSet}. */ @SuppressWarnings("unchecked") - public void mergeFrom(FieldSet other) { - for (Map.Entry<FieldDescriptor, Object> entry : other.fields.entrySet()) { - FieldDescriptor field = entry.getKey(); - Object value = entry.getValue(); - - if (field.isRepeated()) { - List existingValue = (List)fields.get(field); - if (existingValue == null) { - existingValue = new ArrayList(); - fields.put(field, existingValue); + public void mergeFrom(final FieldSet<FieldDescriptorType> other) { + for (final Map.Entry<FieldDescriptorType, Object> entry: + other.fields.entrySet()) { + final FieldDescriptorType descriptor = entry.getKey(); + final Object otherValue = entry.getValue(); + + if (descriptor.isRepeated()) { + Object value = fields.get(descriptor); + if (value == null) { + // Our list is empty, but we still need to make a defensive copy of + // the other list since we don't know if the other FieldSet is still + // mutable. + fields.put(descriptor, new ArrayList((List) otherValue)); + } else { + // Concatenate the lists. + ((List) value).addAll((List) otherValue); } - existingValue.addAll((List)value); - } else if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { - Message existingValue = (Message)fields.get(field); - if (existingValue == null) { - setField(field, value); + } else if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE) { + Object value = fields.get(descriptor); + if (value == null) { + fields.put(descriptor, otherValue); } else { - setField(field, - existingValue.toBuilder() - .mergeFrom((Message)value) + // Merge the messages. + fields.put(descriptor, + descriptor.internalMergeFrom( + ((MessageLite) value).toBuilder(), (MessageLite) otherValue) .build()); } + } else { - setField(field, value); + fields.put(descriptor, otherValue); } } } - // TODO(kenton): Move parsing code into AbstractMessage, since it no longer - // uses any special knowledge from FieldSet. + // TODO(kenton): Move static parsing and serialization methods into some + // other class. Probably WireFormat. /** - * See {@link Message.Builder#mergeFrom(CodedInputStream)}. - * @param builder The {@code Builder} for the target message. + * Read a field of any primitive type from a CodedInputStream. Enums, + * groups, and embedded messages are not handled by this method. + * + * @param input The stream from which to read. + * @param type Declared type of the field. + * @return An object representing the field's value, of the exact + * type which would be returned by + * {@link Message#getField(Descriptors.FieldDescriptor)} for + * this field. */ - public static void mergeFrom(CodedInputStream input, - UnknownFieldSet.Builder unknownFields, - ExtensionRegistry extensionRegistry, - Message.Builder builder) - throws java.io.IOException { - while (true) { - int tag = input.readTag(); - if (tag == 0) { - break; - } + public static Object readPrimitiveField( + CodedInputStream input, + final WireFormat.FieldType type) throws IOException { + switch (type) { + case DOUBLE : return input.readDouble (); + case FLOAT : return input.readFloat (); + case INT64 : return input.readInt64 (); + case UINT64 : return input.readUInt64 (); + case INT32 : return input.readInt32 (); + case FIXED64 : return input.readFixed64 (); + case FIXED32 : return input.readFixed32 (); + case BOOL : return input.readBool (); + case STRING : return input.readString (); + case BYTES : return input.readBytes (); + case UINT32 : return input.readUInt32 (); + case SFIXED32: return input.readSFixed32(); + case SFIXED64: return input.readSFixed64(); + case SINT32 : return input.readSInt32 (); + case SINT64 : return input.readSInt64 (); + + case GROUP: + throw new IllegalArgumentException( + "readPrimitiveField() cannot handle nested groups."); + case MESSAGE: + throw new IllegalArgumentException( + "readPrimitiveField() cannot handle embedded messages."); + case ENUM: + // We don't handle enums because we don't know what to do if the + // value is not recognized. + throw new IllegalArgumentException( + "readPrimitiveField() cannot handle enums."); + } - if (!mergeFieldFrom(input, unknownFields, extensionRegistry, - builder, tag)) { - // end group tag - break; - } + throw new RuntimeException( + "There is no way to get here, but the compiler thinks otherwise."); + } + + /** See {@link Message#writeTo(CodedOutputStream)}. */ + public void writeTo(final CodedOutputStream output) + throws IOException { + for (final Map.Entry<FieldDescriptorType, Object> entry: + fields.entrySet()) { + writeField(entry.getKey(), entry.getValue(), output); } } /** - * Like {@link #mergeFrom(CodedInputStream, UnknownFieldSet.Builder, - * ExtensionRegistry, Message.Builder)}, but parses a single field. - * @param tag The tag, which should have already been read. - * @return {@code true} unless the tag is an end-group tag. + * Like {@link #writeTo} but uses MessageSet wire format. */ - @SuppressWarnings("unchecked") - public static boolean mergeFieldFrom( - CodedInputStream input, - UnknownFieldSet.Builder unknownFields, - ExtensionRegistry extensionRegistry, - Message.Builder builder, - int tag) throws java.io.IOException { - Descriptor type = builder.getDescriptorForType(); - - if (type.getOptions().getMessageSetWireFormat() && - tag == WireFormat.MESSAGE_SET_ITEM_TAG) { - mergeMessageSetExtensionFromCodedStream( - input, unknownFields, extensionRegistry, builder); - return true; - } - - int wireType = WireFormat.getTagWireType(tag); - int fieldNumber = WireFormat.getTagFieldNumber(tag); - - FieldDescriptor field; - Message defaultInstance = null; - - if (type.isExtensionNumber(fieldNumber)) { - ExtensionRegistry.ExtensionInfo extension = - extensionRegistry.findExtensionByNumber(type, fieldNumber); - if (extension == null) { - field = null; + public void writeMessageSetTo(final CodedOutputStream output) + throws IOException { + for (final Map.Entry<FieldDescriptorType, Object> entry: + fields.entrySet()) { + final FieldDescriptorType descriptor = entry.getKey(); + if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE && + !descriptor.isRepeated() && !descriptor.isPacked()) { + output.writeMessageSetExtension(entry.getKey().getNumber(), + (MessageLite) entry.getValue()); } else { - field = extension.descriptor; - defaultInstance = extension.defaultInstance; + writeField(descriptor, entry.getValue(), output); } - } else { - field = type.findFieldByNumber(fieldNumber); } + } - if (field == null || - wireType != WireFormat.getWireFormatForField(field)) { - // Unknown field or wrong wire type. Skip. - return unknownFields.mergeFieldFrom(tag, input); + /** + * Write a single tag-value pair to the stream. + * + * @param output The output stream. + * @param type The field's type. + * @param number The field's number. + * @param value Object representing the field's value. Must be of the exact + * type which would be returned by + * {@link Message#getField(Descriptors.FieldDescriptor)} for + * this field. + */ + private static void writeElement(final CodedOutputStream output, + final WireFormat.FieldType type, + final int number, + final Object value) throws IOException { + // Special case for groups, which need a start and end tag; other fields + // can just use writeTag() and writeFieldNoTag(). + if (type == WireFormat.FieldType.GROUP) { + output.writeGroup(number, (MessageLite) value); } else { - if (field.getOptions().getPacked()) { - int length = input.readRawVarint32(); - int limit = input.pushLimit(length); - if (field.getType() == FieldDescriptor.Type.ENUM) { - while (input.getBytesUntilLimit() > 0) { - int rawValue = input.readEnum(); - Object value = field.getEnumType().findValueByNumber(rawValue); - if (value == null) { - // If the number isn't recognized as a valid value for this - // enum, drop it (don't even add it to unknownFields). - return true; - } - builder.addRepeatedField(field, value); - } - } else { - while (input.getBytesUntilLimit() > 0) { - Object value = input.readPrimitiveField(field.getType()); - builder.addRepeatedField(field, value); - } - } - input.popLimit(limit); - } else { - Object value; - switch (field.getType()) { - case GROUP: { - Message.Builder subBuilder; - if (defaultInstance != null) { - subBuilder = defaultInstance.newBuilderForType(); - } else { - subBuilder = builder.newBuilderForField(field); - } - if (!field.isRepeated()) { - subBuilder.mergeFrom((Message) builder.getField(field)); - } - input.readGroup(field.getNumber(), subBuilder, extensionRegistry); - value = subBuilder.build(); - break; - } - case MESSAGE: { - Message.Builder subBuilder; - if (defaultInstance != null) { - subBuilder = defaultInstance.newBuilderForType(); - } else { - subBuilder = builder.newBuilderForField(field); - } - if (!field.isRepeated()) { - subBuilder.mergeFrom((Message) builder.getField(field)); - } - input.readMessage(subBuilder, extensionRegistry); - value = subBuilder.build(); - break; - } - case ENUM: { - int rawValue = input.readEnum(); - value = field.getEnumType().findValueByNumber(rawValue); - // If the number isn't recognized as a valid value for this enum, - // drop it. - if (value == null) { - unknownFields.mergeVarintField(fieldNumber, rawValue); - return true; - } - break; - } - default: - value = input.readPrimitiveField(field.getType()); - break; - } - - if (field.isRepeated()) { - builder.addRepeatedField(field, value); - } else { - builder.setField(field, value); - } - } + output.writeTag(number, getWireFormatForFieldType(type, false)); + writeElementNoTag(output, type, value); } - - return true; } - /** Called by {@code #mergeFieldFrom()} to parse a MessageSet extension. */ - private static void mergeMessageSetExtensionFromCodedStream( - CodedInputStream input, - UnknownFieldSet.Builder unknownFields, - ExtensionRegistry extensionRegistry, - Message.Builder builder) throws java.io.IOException { - Descriptor type = builder.getDescriptorForType(); - - // The wire format for MessageSet is: - // message MessageSet { - // repeated group Item = 1 { - // required int32 typeId = 2; - // required bytes message = 3; - // } - // } - // "typeId" is the extension's field number. The extension can only be - // a message type, where "message" contains the encoded bytes of that - // message. - // - // In practice, we will probably never see a MessageSet item in which - // the message appears before the type ID, or where either field does not - // appear exactly once. However, in theory such cases are valid, so we - // should be prepared to accept them. - - int typeId = 0; - ByteString rawBytes = null; // If we encounter "message" before "typeId" - Message.Builder subBuilder = null; - FieldDescriptor field = null; - - while (true) { - int tag = input.readTag(); - if (tag == 0) { + /** + * Write a field of arbitrary type, without its tag, to the stream. + * + * @param output The output stream. + * @param type The field's type. + * @param value Object representing the field's value. Must be of the exact + * type which would be returned by + * {@link Message#getField(Descriptors.FieldDescriptor)} for + * this field. + */ + private static void writeElementNoTag( + final CodedOutputStream output, + final WireFormat.FieldType type, + final Object value) throws IOException { + switch (type) { + case DOUBLE : output.writeDoubleNoTag ((Double ) value); break; + case FLOAT : output.writeFloatNoTag ((Float ) value); break; + case INT64 : output.writeInt64NoTag ((Long ) value); break; + case UINT64 : output.writeUInt64NoTag ((Long ) value); break; + case INT32 : output.writeInt32NoTag ((Integer ) value); break; + case FIXED64 : output.writeFixed64NoTag ((Long ) value); break; + case FIXED32 : output.writeFixed32NoTag ((Integer ) value); break; + case BOOL : output.writeBoolNoTag ((Boolean ) value); break; + case STRING : output.writeStringNoTag ((String ) value); break; + case GROUP : output.writeGroupNoTag ((MessageLite) value); break; + case MESSAGE : output.writeMessageNoTag ((MessageLite) value); break; + case BYTES : output.writeBytesNoTag ((ByteString ) value); break; + case UINT32 : output.writeUInt32NoTag ((Integer ) value); break; + case SFIXED32: output.writeSFixed32NoTag((Integer ) value); break; + case SFIXED64: output.writeSFixed64NoTag((Long ) value); break; + case SINT32 : output.writeSInt32NoTag ((Integer ) value); break; + case SINT64 : output.writeSInt64NoTag ((Long ) value); break; + + case ENUM: + output.writeEnumNoTag(((Internal.EnumLite) value).getNumber()); break; - } + } + } - if (tag == WireFormat.MESSAGE_SET_TYPE_ID_TAG) { - typeId = input.readUInt32(); - // Zero is not a valid type ID. - if (typeId != 0) { - ExtensionRegistry.ExtensionInfo extension = - extensionRegistry.findExtensionByNumber(type, typeId); - if (extension != null) { - field = extension.descriptor; - subBuilder = extension.defaultInstance.newBuilderForType(); - Message originalMessage = (Message)builder.getField(field); - if (originalMessage != null) { - subBuilder.mergeFrom(originalMessage); - } - if (rawBytes != null) { - // We already encountered the message. Parse it now. - subBuilder.mergeFrom( - CodedInputStream.newInstance(rawBytes.newInput())); - rawBytes = null; - } - } else { - // Unknown extension number. If we already saw data, put it - // in rawBytes. - if (rawBytes != null) { - unknownFields.mergeField(typeId, - UnknownFieldSet.Field.newBuilder() - .addLengthDelimited(rawBytes) - .build()); - rawBytes = null; - } - } + /** Write a single field. */ + public static void writeField(final FieldDescriptorLite<?> descriptor, + final Object value, + final CodedOutputStream output) + throws IOException { + WireFormat.FieldType type = descriptor.getLiteType(); + int number = descriptor.getNumber(); + if (descriptor.isRepeated()) { + final List valueList = (List)value; + if (descriptor.isPacked()) { + output.writeTag(number, WireFormat.WIRETYPE_LENGTH_DELIMITED); + // Compute the total data size so the length can be written. + int dataSize = 0; + for (final Object element : valueList) { + dataSize += computeElementSizeNoTag(type, element); } - } else if (tag == WireFormat.MESSAGE_SET_MESSAGE_TAG) { - if (typeId == 0) { - // We haven't seen a type ID yet, so we have to store the raw bytes - // for now. - rawBytes = input.readBytes(); - } else if (subBuilder == null) { - // We don't know how to parse this. Ignore it. - unknownFields.mergeField(typeId, - UnknownFieldSet.Field.newBuilder() - .addLengthDelimited(input.readBytes()) - .build()); - } else { - // We already know the type, so we can parse directly from the input - // with no copying. Hooray! - input.readMessage(subBuilder, extensionRegistry); + output.writeRawVarint32(dataSize); + // Write the data itself, without any tags. + for (final Object element : valueList) { + writeElementNoTag(output, type, element); } } else { - // Unknown tag. Skip it. - if (!input.skipField(tag)) { - break; // end of group + for (final Object element : valueList) { + writeElement(output, type, number, element); } } - } - - input.checkLastTagWas(WireFormat.MESSAGE_SET_ITEM_END_TAG); - - if (subBuilder != null) { - builder.setField(field, subBuilder.build()); + } else { + writeElement(output, type, number, value); } } - /** See {@link Message#writeTo(CodedOutputStream)}. */ - public void writeTo(CodedOutputStream output) - throws java.io.IOException { - for (Map.Entry<FieldDescriptor, Object> entry : fields.entrySet()) { - writeField(entry.getKey(), entry.getValue(), output); + /** + * See {@link Message#getSerializedSize()}. It's up to the caller to cache + * the resulting size if desired. + */ + public int getSerializedSize() { + int size = 0; + for (final Map.Entry<FieldDescriptorType, Object> entry: + fields.entrySet()) { + size += computeFieldSize(entry.getKey(), entry.getValue()); } + return size; } - /** Write a single field. */ - public void writeField(FieldDescriptor field, Object value, - CodedOutputStream output) throws java.io.IOException { - if (field.isExtension() && - field.getContainingType().getOptions().getMessageSetWireFormat()) { - output.writeMessageSetExtension(field.getNumber(), (Message)value); - } else { - if (field.isRepeated()) { - List valueList = (List)value; - if (field.getOptions().getPacked()) { - output.writeTag(field.getNumber(), - WireFormat.WIRETYPE_LENGTH_DELIMITED); - // Compute the total data size so the length can be written. - int dataSize = 0; - for (Object element : valueList) { - dataSize += output.computeFieldSizeNoTag(field.getType(), element); - } - output.writeRawVarint32(dataSize); - // Write the data itself, without any tags. - for (Object element : valueList) { - output.writeFieldNoTag(field.getType(), element); - } - } else { - for (Object element : valueList) { - output.writeField(field.getType(), field.getNumber(), element); - } - } + /** + * Like {@link #getSerializedSize} but uses MessageSet wire format. + */ + public int getMessageSetSerializedSize() { + int size = 0; + for (final Map.Entry<FieldDescriptorType, Object> entry: + fields.entrySet()) { + final FieldDescriptorType descriptor = entry.getKey(); + if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE && + !descriptor.isRepeated() && !descriptor.isPacked()) { + size += CodedOutputStream.computeMessageSetExtensionSize( + entry.getKey().getNumber(), (MessageLite) entry.getValue()); } else { - output.writeField(field.getType(), field.getNumber(), value); + size += computeFieldSize(descriptor, entry.getValue()); } } + return size; } /** - * See {@link Message#getSerializedSize()}. It's up to the caller to cache - * the resulting size if desired. + * Compute the number of bytes that would be needed to encode a + * single tag/value pair of arbitrary type. + * + * @param type The field's type. + * @param number The field's number. + * @param value Object representing the field's value. Must be of the exact + * type which would be returned by + * {@link Message#getField(Descriptors.FieldDescriptor)} for + * this field. */ - public int getSerializedSize() { - int size = 0; - for (Map.Entry<FieldDescriptor, Object> entry : fields.entrySet()) { - FieldDescriptor field = entry.getKey(); - Object value = entry.getValue(); + private static int computeElementSize( + final WireFormat.FieldType type, + final int number, final Object value) { + int tagSize = CodedOutputStream.computeTagSize(number); + if (type == WireFormat.FieldType.GROUP) { + tagSize *= 2; + } + return tagSize + computeElementSizeNoTag(type, value); + } - if (field.isExtension() && - field.getContainingType().getOptions().getMessageSetWireFormat()) { - size += CodedOutputStream.computeMessageSetExtensionSize( - field.getNumber(), (Message) value); + /** + * Compute the number of bytes that would be needed to encode a + * particular value of arbitrary type, excluding tag. + * + * @param type The field's type. + * @param value Object representing the field's value. Must be of the exact + * type which would be returned by + * {@link Message#getField(Descriptors.FieldDescriptor)} for + * this field. + */ + private static int computeElementSizeNoTag( + final WireFormat.FieldType type, final Object value) { + switch (type) { + // Note: Minor violation of 80-char limit rule here because this would + // actually be harder to read if we wrapped the lines. + case DOUBLE : return CodedOutputStream.computeDoubleSizeNoTag ((Double )value); + case FLOAT : return CodedOutputStream.computeFloatSizeNoTag ((Float )value); + case INT64 : return CodedOutputStream.computeInt64SizeNoTag ((Long )value); + case UINT64 : return CodedOutputStream.computeUInt64SizeNoTag ((Long )value); + case INT32 : return CodedOutputStream.computeInt32SizeNoTag ((Integer )value); + case FIXED64 : return CodedOutputStream.computeFixed64SizeNoTag ((Long )value); + case FIXED32 : return CodedOutputStream.computeFixed32SizeNoTag ((Integer )value); + case BOOL : return CodedOutputStream.computeBoolSizeNoTag ((Boolean )value); + case STRING : return CodedOutputStream.computeStringSizeNoTag ((String )value); + case GROUP : return CodedOutputStream.computeGroupSizeNoTag ((MessageLite)value); + case MESSAGE : return CodedOutputStream.computeMessageSizeNoTag ((MessageLite)value); + case BYTES : return CodedOutputStream.computeBytesSizeNoTag ((ByteString )value); + case UINT32 : return CodedOutputStream.computeUInt32SizeNoTag ((Integer )value); + case SFIXED32: return CodedOutputStream.computeSFixed32SizeNoTag((Integer )value); + case SFIXED64: return CodedOutputStream.computeSFixed64SizeNoTag((Long )value); + case SINT32 : return CodedOutputStream.computeSInt32SizeNoTag ((Integer )value); + case SINT64 : return CodedOutputStream.computeSInt64SizeNoTag ((Long )value); + + case ENUM: + return CodedOutputStream.computeEnumSizeNoTag( + ((Internal.EnumLite) value).getNumber()); + } + + throw new RuntimeException( + "There is no way to get here, but the compiler thinks otherwise."); + } + + /** + * Compute the number of bytes needed to encode a particular field. + */ + public static int computeFieldSize(final FieldDescriptorLite<?> descriptor, + final Object value) { + WireFormat.FieldType type = descriptor.getLiteType(); + int number = descriptor.getNumber(); + if (descriptor.isRepeated()) { + if (descriptor.isPacked()) { + int dataSize = 0; + for (final Object element : (List)value) { + dataSize += computeElementSizeNoTag(type, element); + } + return dataSize + + CodedOutputStream.computeTagSize(number) + + CodedOutputStream.computeRawVarint32Size(dataSize); } else { - if (field.isRepeated()) { - if (field.getOptions().getPacked()) { - int dataSize = 0; - for (Object element : (List)value) { - dataSize += CodedOutputStream.computeFieldSizeNoTag( - field.getType(), element); - } - size += dataSize + - CodedOutputStream.computeTagSize(field.getNumber()) + - CodedOutputStream.computeRawVarint32Size(dataSize); - } else { - for (Object element : (List)value) { - size += CodedOutputStream.computeFieldSize( - field.getType(), field.getNumber(), element); - } - } - } else { - size += CodedOutputStream.computeFieldSize( - field.getType(), field.getNumber(), value); + int size = 0; + for (final Object element : (List)value) { + size += computeElementSize(type, number, element); } + return size; } + } else { + return computeElementSize(type, number, value); } - return size; } } |