| // Protocol Buffers - Google's data interchange format |
| // Copyright 2008 Google Inc. |
| // http://code.google.com/p/protobuf/ |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| 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; |
| |
| /** |
| * A class which represents an arbitrary set of fields of some message type. |
| * This is used to implement {@link DynamicMessage}, and also to represent |
| * extensions in {@link GeneratedMessage}. This class is package-private, |
| * since outside users should probably be using {@link DynamicMessage}. |
| * |
| * @author kenton@google.com Kenton Varda |
| */ |
| final class FieldSet { |
| private Map<FieldDescriptor, 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>(); |
| } |
| |
| /** |
| * Construct a new FieldSet with the given map. This is only used by |
| * DEFAULT_INSTANCE, to pass in an immutable empty map. |
| */ |
| private FieldSet(Map<FieldDescriptor, Object> fields) { |
| this.fields = fields; |
| } |
| |
| /** Construct a new FieldSet. */ |
| public static FieldSet newFieldSet() { |
| return new FieldSet(); |
| } |
| |
| /** Get an immutable empty FieldSet. */ |
| public static FieldSet emptySet() { |
| return DEFAULT_INSTANCE; |
| } |
| private static final FieldSet DEFAULT_INSTANCE = |
| new FieldSet(Collections.<FieldDescriptor, Object>emptyMap()); |
| |
| /** Make this FieldSet immutable from this point forward. */ |
| @SuppressWarnings("unchecked") |
| public void makeImmutable() { |
| for (Map.Entry<FieldDescriptor, Object> entry: fields.entrySet()) { |
| if (entry.getKey().isRepeated()) { |
| List value = (List)entry.getValue(); |
| entry.setValue(Collections.unmodifiableList(value)); |
| } |
| } |
| fields = Collections.unmodifiableMap(fields); |
| } |
| |
| // ================================================================= |
| |
| /** See {@link Message.Builder#clear()}. */ |
| public void clear() { |
| fields.clear(); |
| } |
| |
| /** See {@link Message#getAllFields()}. */ |
| public Map<Descriptors.FieldDescriptor, Object> getAllFields() { |
| return Collections.unmodifiableMap(fields); |
| } |
| |
| /** |
| * Get an interator 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() { |
| return fields.entrySet().iterator(); |
| } |
| |
| /** See {@link Message#hasField(Descriptors.FieldDescriptor)}. */ |
| public boolean hasField(Descriptors.FieldDescriptor field) { |
| if (field.isRepeated()) { |
| throw new IllegalArgumentException( |
| "hasField() can only be called on non-repeated fields."); |
| } |
| |
| return fields.containsKey(field); |
| } |
| |
| /** |
| * 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. |
| */ |
| 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; |
| } |
| } |
| |
| /** See {@link Message.Builder#setField(Descriptors.FieldDescriptor,Object)}. */ |
| @SuppressWarnings("unchecked") |
| public void setField(Descriptors.FieldDescriptor field, Object value) { |
| if (field.isRepeated()) { |
| if (!(value instanceof List)) { |
| throw new IllegalArgumentException( |
| "Wrong object type used with protocol message reflection."); |
| } |
| |
| // Wrap the contents in a new list so that the caller cannot change |
| // the list's contents after setting it. |
| List newList = new ArrayList(); |
| newList.addAll((List)value); |
| for (Object element : newList) { |
| verifyType(field, element); |
| } |
| value = newList; |
| } else { |
| verifyType(field, value); |
| } |
| |
| fields.put(field, value); |
| } |
| |
| /** See {@link Message.Builder#clearField(Descriptors.FieldDescriptor)}. */ |
| public void clearField(Descriptors.FieldDescriptor field) { |
| fields.remove(field); |
| } |
| |
| /** See {@link Message#getRepeatedFieldCount(Descriptors.FieldDescriptor)}. */ |
| public int getRepeatedFieldCount(Descriptors.FieldDescriptor field) { |
| if (!field.isRepeated()) { |
| throw new IllegalArgumentException( |
| "getRepeatedFieldCount() can only be called on repeated fields."); |
| } |
| |
| return ((List)getField(field)).size(); |
| } |
| |
| /** See {@link Message#getRepeatedField(Descriptors.FieldDescriptor,int)}. */ |
| public Object getRepeatedField(Descriptors.FieldDescriptor field, int index) { |
| if (!field.isRepeated()) { |
| throw new IllegalArgumentException( |
| "getRepeatedField() can only be called on repeated fields."); |
| } |
| |
| return ((List)getField(field)).get(index); |
| } |
| |
| /** See {@link Message.Builder#setRepeatedField(Descriptors.FieldDescriptor,int,Object)}. */ |
| @SuppressWarnings("unchecked") |
| public void setRepeatedField(Descriptors.FieldDescriptor field, int index, |
| Object value) { |
| if (!field.isRepeated()) { |
| throw new IllegalArgumentException( |
| "setRepeatedField() can only be called on repeated fields."); |
| } |
| |
| verifyType(field, value); |
| |
| List list = (List)fields.get(field); |
| if (list == null) { |
| throw new IndexOutOfBoundsException(); |
| } |
| |
| list.set(index, value); |
| } |
| |
| /** See {@link Message.Builder#addRepeatedField(Descriptors.FieldDescriptor,Object)}. */ |
| @SuppressWarnings("unchecked") |
| public void addRepeatedField(Descriptors.FieldDescriptor field, |
| Object value) { |
| if (!field.isRepeated()) { |
| throw new IllegalArgumentException( |
| "setRepeatedField() can only be called on repeated fields."); |
| } |
| |
| verifyType(field, value); |
| |
| List list = (List)fields.get(field); |
| if (list == null) { |
| list = new ArrayList(); |
| fields.put(field, list); |
| } |
| |
| list.add(value); |
| } |
| |
| /** |
| * Verifies that the given object is of the correct type to be a valid |
| * value for the given field. (For repeated fields, this checks if the |
| * object is the right type to be one element of the field.) |
| * |
| * @throws IllegalArgumentException The value is not of the right type. |
| */ |
| private void verifyType(FieldDescriptor field, Object value) { |
| boolean isValid = false; |
| switch (field.getJavaType()) { |
| case INT: isValid = value instanceof Integer ; break; |
| case LONG: isValid = value instanceof Long ; break; |
| case FLOAT: isValid = value instanceof Float ; break; |
| case DOUBLE: isValid = value instanceof Double ; break; |
| case BOOLEAN: isValid = value instanceof Boolean ; break; |
| 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(); |
| break; |
| case MESSAGE: |
| isValid = value instanceof Message && |
| ((Message)value).getDescriptorForType() == field.getMessageType(); |
| 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. |
| 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() + "\"."); |
| } |
| } |
| |
| // ================================================================= |
| // Parsing and serialization |
| |
| /** |
| * See {@link Message#isInitialized()}. Note: Since {@code FieldSet} |
| * itself does not have any way of knowing about required fields that |
| * aren't actually present in the set, it is up to the caller to check |
| * that all required fields are present. |
| */ |
| @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()) { |
| if (!element.isInitialized()) { |
| return false; |
| } |
| } |
| } else { |
| if (!((Message) entry.getValue()).isInitialized()) { |
| return false; |
| } |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Like {@link #isInitialized()}, but also checks for the presence of |
| * all required fields in the given type. |
| */ |
| @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.newBuilderForType() |
| .mergeFrom(existingValue) |
| .mergeFrom((Message)entry.getValue()) |
| .build()); |
| } |
| } else { |
| setField(field, entry.getValue()); |
| } |
| } |
| } |
| |
| /** |
| * 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); |
| } |
| existingValue.addAll((List)value); |
| } else if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { |
| Message existingValue = (Message)fields.get(field); |
| if (existingValue == null) { |
| setField(field, value); |
| } else { |
| setField(field, |
| existingValue.newBuilderForType() |
| .mergeFrom(existingValue) |
| .mergeFrom((Message)value) |
| .build()); |
| } |
| } else { |
| setField(field, value); |
| } |
| } |
| } |
| |
| // TODO(kenton): Move parsing code into AbstractMessage, since it no longer |
| // uses any special knowledge from FieldSet. |
| |
| /** |
| * See {@link Message.Builder#mergeFrom(CodedInputStream)}. |
| * @param builder The {@code Builder} for the target message. |
| */ |
| 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; |
| } |
| |
| if (!mergeFieldFrom(input, unknownFields, extensionRegistry, |
| builder, tag)) { |
| // end group tag |
| break; |
| } |
| } |
| } |
| |
| /** |
| * 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. |
| */ |
| @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; |
| } else { |
| field = extension.descriptor; |
| defaultInstance = extension.defaultInstance; |
| } |
| } else { |
| field = type.findFieldByNumber(fieldNumber); |
| } |
| |
| if (field == null || |
| wireType != WireFormat.getWireFormatForFieldType(field.getType())) { |
| // Unknown field or wrong wire type. Skip. |
| return unknownFields.mergeFieldFrom(tag, input); |
| } 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); |
| } |
| } |
| |
| 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) { |
| 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; |
| } |
| } |
| } |
| } 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); |
| } |
| } else { |
| // Unknown tag. Skip it. |
| if (!input.skipField(tag)) { |
| break; // end of group |
| } |
| } |
| } |
| |
| input.checkLastTagWas(WireFormat.MESSAGE_SET_ITEM_END_TAG); |
| |
| if (subBuilder != null) { |
| builder.setField(field, subBuilder.build()); |
| } |
| } |
| |
| /** 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); |
| } |
| } |
| |
| /** 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()) { |
| for (Object element : (List)value) { |
| output.writeField(field.getType(), field.getNumber(), element); |
| } |
| } else { |
| output.writeField(field.getType(), field.getNumber(), value); |
| } |
| } |
| } |
| |
| /** |
| * See {@link Message#getSerializedSize()}. It's up to the caller to cache |
| * the resulting size if desired. |
| */ |
| public int getSerializedSize() { |
| int size = 0; |
| for (Map.Entry<FieldDescriptor, Object> entry : fields.entrySet()) { |
| FieldDescriptor field = entry.getKey(); |
| Object value = entry.getValue(); |
| |
| if (field.isExtension() && |
| field.getContainingType().getOptions().getMessageSetWireFormat()) { |
| size += CodedOutputStream.computeMessageSetExtensionSize( |
| field.getNumber(), (Message)value); |
| } else { |
| if (field.isRepeated()) { |
| for (Object element : (List)value) { |
| size += CodedOutputStream.computeFieldSize( |
| field.getType(), field.getNumber(), element); |
| } |
| } else { |
| size += CodedOutputStream.computeFieldSize( |
| field.getType(), field.getNumber(), value); |
| } |
| } |
| } |
| return size; |
| } |
| } |