aboutsummaryrefslogtreecommitdiff
path: root/java/src/main/java/com/google/protobuf/FieldSet.java
diff options
context:
space:
mode:
authortemporal <temporal@630680e5-0e50-0410-840e-4b1c322b438d>2008-07-10 02:12:20 +0000
committertemporal <temporal@630680e5-0e50-0410-840e-4b1c322b438d>2008-07-10 02:12:20 +0000
commit40ee551715c3a784ea6132dbf604b0e665ca2def (patch)
tree6e3ea9674be5b0f59106f88f3afa1313854beebf /java/src/main/java/com/google/protobuf/FieldSet.java
downloadprotobuf-40ee551715c3a784ea6132dbf604b0e665ca2def.tar.gz
protobuf-40ee551715c3a784ea6132dbf604b0e665ca2def.tar.bz2
protobuf-40ee551715c3a784ea6132dbf604b0e665ca2def.zip
Initial checkin.
Diffstat (limited to 'java/src/main/java/com/google/protobuf/FieldSet.java')
-rw-r--r--java/src/main/java/com/google/protobuf/FieldSet.java662
1 files changed, 662 insertions, 0 deletions
diff --git a/java/src/main/java/com/google/protobuf/FieldSet.java b/java/src/main/java/com/google/protobuf/FieldSet.java
new file mode 100644
index 00000000..3a5dc488
--- /dev/null
+++ b/java/src/main/java/com/google/protobuf/FieldSet.java
@@ -0,0 +1,662 @@
+// 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;
+ }
+}