Blame | Last modification | View Log | Download | RSS feed
// Protocol Buffers - Google's data interchange format// Copyright 2008 Google Inc. All rights reserved.// http://code.google.com/p/protobuf///// 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 java.util.ArrayList;import java.util.Collections;import java.util.Iterator;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.* 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<FieldDescriptorType extendsFieldSet.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 final SmallSortedMap<FieldDescriptorType, Object> fields;private boolean isImmutable;/** Construct a new FieldSet. */private FieldSet() {this.fields = SmallSortedMap.newFieldMap(16);}/*** Construct an empty FieldSet. This is only used to initialize* DEFAULT_INSTANCE.*/private FieldSet(final boolean dummy) {this.fields = SmallSortedMap.newFieldMap(0);makeImmutable();}/** Construct a new FieldSet. */public static <T extends FieldSet.FieldDescriptorLite<T>>FieldSet<T> newFieldSet() {return new FieldSet<T>();}/** Get an immutable empty FieldSet. */@SuppressWarnings("unchecked")public static <T extends FieldSet.FieldDescriptorLite<T>>FieldSet<T> emptySet() {return DEFAULT_INSTANCE;}@SuppressWarnings("unchecked")private static final FieldSet DEFAULT_INSTANCE = new FieldSet(true);/** Make this FieldSet immutable from this point forward. */@SuppressWarnings("unchecked")public void makeImmutable() {if (isImmutable) {return;}fields.makeImmutable();isImmutable = true;}/*** Retuns whether the FieldSet is immutable. This is true if it is the* {@link #emptySet} or if {@link #makeImmutable} were called.** @return whether the FieldSet is immutable.*/public boolean isImmutable() {return isImmutable;}/*** Clones the FieldSet. The returned FieldSet will be mutable even if the* original FieldSet was immutable.** @return the newly cloned FieldSet*/@Overridepublic FieldSet<FieldDescriptorType> clone() {// We can't just call fields.clone because List objects in the map// should not be shared.FieldSet<FieldDescriptorType> clone = FieldSet.newFieldSet();for (int i = 0; i < fields.getNumArrayEntries(); i++) {Map.Entry<FieldDescriptorType, Object> entry = fields.getArrayEntryAt(i);FieldDescriptorType descriptor = entry.getKey();clone.setField(descriptor, entry.getValue());}for (Map.Entry<FieldDescriptorType, Object> entry :fields.getOverflowEntries()) {FieldDescriptorType descriptor = entry.getKey();clone.setField(descriptor, entry.getValue());}return clone;}// =================================================================/** See {@link Message.Builder#clear()}. */public void clear() {fields.clear();}/*** Get a simple map containing all the fields.*/public Map<FieldDescriptorType, Object> getAllFields() {return fields.isImmutable() ? fields : Collections.unmodifiableMap(fields);}/*** 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 when* fields is not immutable.*/public Iterator<Map.Entry<FieldDescriptorType, Object>> iterator() {return fields.entrySet().iterator();}/*** 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.get(descriptor) != null;}/*** 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(final FieldDescriptorType descriptor) {return fields.get(descriptor);}/*** Useful for implementing* {@link Message.Builder#setField(Descriptors.FieldDescriptor,Object)}.*/@SuppressWarnings("unchecked")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.");}// Wrap the contents in a new list so that the caller cannot change// the list's contents after setting it.final List newList = new ArrayList();newList.addAll((List)value);for (final Object element : newList) {verifyType(descriptor.getLiteType(), element);}value = newList;} else {verifyType(descriptor.getLiteType(), value);}fields.put(descriptor, value);}/*** Useful for implementing* {@link Message.Builder#clearField(Descriptors.FieldDescriptor)}.*/public void clearField(final FieldDescriptorType descriptor) {fields.remove(descriptor);}/*** Useful for implementing* {@link Message#getRepeatedFieldCount(Descriptors.FieldDescriptor)}.*/public int getRepeatedFieldCount(final FieldDescriptorType descriptor) {if (!descriptor.isRepeated()) {throw new IllegalArgumentException("getRepeatedField() can only be called on repeated fields.");}final Object value = fields.get(descriptor);if (value == null) {return 0;} else {return ((List<?>) value).size();}}/*** 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.");}final Object value = fields.get(descriptor);if (value == null) {throw new IndexOutOfBoundsException();} else {return ((List<?>) value).get(index);}}/*** Useful for implementing* {@link Message.Builder#setRepeatedField(Descriptors.FieldDescriptor,int,Object)}.*/@SuppressWarnings("unchecked")public void setRepeatedField(final FieldDescriptorType descriptor,final int index,final Object value) {if (!descriptor.isRepeated()) {throw new IllegalArgumentException("getRepeatedField() can only be called on repeated fields.");}final Object list = fields.get(descriptor);if (list == null) {throw new IndexOutOfBoundsException();}verifyType(descriptor.getLiteType(), value);((List) list).set(index, value);}/*** Useful for implementing* {@link Message.Builder#addRepeatedField(Descriptors.FieldDescriptor,Object)}.*/@SuppressWarnings("unchecked")public void addRepeatedField(final FieldDescriptorType descriptor,final Object value) {if (!descriptor.isRepeated()) {throw new IllegalArgumentException("addRepeatedField() can only be called on repeated fields.");}verifyType(descriptor.getLiteType(), value);final Object existingValue = fields.get(descriptor);List list;if (existingValue == null) {list = new ArrayList();fields.put(descriptor, list);} else {list = (List) existingValue;}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 static void verifyType(final WireFormat.FieldType type,final Object value) {if (value == null) {throw new NullPointerException();}boolean isValid = false;switch (type.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:// TODO(kenton): Caller must do type checking here, I guess.isValid = value instanceof Internal.EnumLite;break;case MESSAGE:// TODO(kenton): Caller must do type checking here, I guess.isValid = value instanceof MessageLite;break;}if (!isValid) {// 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.");}}// =================================================================// 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.*/public boolean isInitialized() {for (int i = 0; i < fields.getNumArrayEntries(); i++) {if (!isInitialized(fields.getArrayEntryAt(i))) {return false;}}for (final Map.Entry<FieldDescriptorType, Object> entry :fields.getOverflowEntries()) {if (!isInitialized(entry)) {return false;}}return true;}@SuppressWarnings("unchecked")private boolean isInitialized(final Map.Entry<FieldDescriptorType, Object> entry) {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 (!((MessageLite) entry.getValue()).isInitialized()) {return false;}}}return true;}/*** Given a field type, return the wire type.** @returns One of the {@code WIRETYPE_} constants defined in* {@link WireFormat}.*/static int getWireFormatForFieldType(final WireFormat.FieldType type,boolean isPacked) {if (isPacked) {return WireFormat.WIRETYPE_LENGTH_DELIMITED;} else {return type.getWireType();}}/*** Like {@link #mergeFrom(Message)}, but merges from another {@link FieldSet}.*/public void mergeFrom(final FieldSet<FieldDescriptorType> other) {for (int i = 0; i < other.fields.getNumArrayEntries(); i++) {mergeFromField(other.fields.getArrayEntryAt(i));}for (final Map.Entry<FieldDescriptorType, Object> entry :other.fields.getOverflowEntries()) {mergeFromField(entry);}}@SuppressWarnings("unchecked")private void mergeFromField(final Map.Entry<FieldDescriptorType, Object> entry) {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);}} else if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE) {Object value = fields.get(descriptor);if (value == null) {fields.put(descriptor, otherValue);} else {// Merge the messages.fields.put(descriptor,descriptor.internalMergeFrom(((MessageLite) value).toBuilder(), (MessageLite) otherValue).build());}} else {fields.put(descriptor, otherValue);}}// TODO(kenton): Move static parsing and serialization methods into some// other class. Probably WireFormat./*** 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 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.");}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 (int i = 0; i < fields.getNumArrayEntries(); i++) {final Map.Entry<FieldDescriptorType, Object> entry =fields.getArrayEntryAt(i);writeField(entry.getKey(), entry.getValue(), output);}for (final Map.Entry<FieldDescriptorType, Object> entry :fields.getOverflowEntries()) {writeField(entry.getKey(), entry.getValue(), output);}}/*** Like {@link #writeTo} but uses MessageSet wire format.*/public void writeMessageSetTo(final CodedOutputStream output)throws IOException {for (int i = 0; i < fields.getNumArrayEntries(); i++) {writeMessageSetTo(fields.getArrayEntryAt(i), output);}for (final Map.Entry<FieldDescriptorType, Object> entry :fields.getOverflowEntries()) {writeMessageSetTo(entry, output);}}private void writeMessageSetTo(final Map.Entry<FieldDescriptorType, Object> entry,final CodedOutputStream output) throws IOException {final FieldDescriptorType descriptor = entry.getKey();if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE &&!descriptor.isRepeated() && !descriptor.isPacked()) {output.writeMessageSetExtension(entry.getKey().getNumber(),(MessageLite) entry.getValue());} else {writeField(descriptor, entry.getValue(), output);}}/*** 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 {output.writeTag(number, getWireFormatForFieldType(type, false));writeElementNoTag(output, type, value);}}/*** 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;}}/** 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);}output.writeRawVarint32(dataSize);// Write the data itself, without any tags.for (final Object element : valueList) {writeElementNoTag(output, type, element);}} else {for (final Object element : valueList) {writeElement(output, type, number, element);}}} else {writeElement(output, type, number, 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 (int i = 0; i < fields.getNumArrayEntries(); i++) {final Map.Entry<FieldDescriptorType, Object> entry =fields.getArrayEntryAt(i);size += computeFieldSize(entry.getKey(), entry.getValue());}for (final Map.Entry<FieldDescriptorType, Object> entry :fields.getOverflowEntries()) {size += computeFieldSize(entry.getKey(), entry.getValue());}return size;}/*** Like {@link #getSerializedSize} but uses MessageSet wire format.*/public int getMessageSetSerializedSize() {int size = 0;for (int i = 0; i < fields.getNumArrayEntries(); i++) {size += getMessageSetSerializedSize(fields.getArrayEntryAt(i));}for (final Map.Entry<FieldDescriptorType, Object> entry :fields.getOverflowEntries()) {size += getMessageSetSerializedSize(entry);}return size;}private int getMessageSetSerializedSize(final Map.Entry<FieldDescriptorType, Object> entry) {final FieldDescriptorType descriptor = entry.getKey();if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE &&!descriptor.isRepeated() && !descriptor.isPacked()) {return CodedOutputStream.computeMessageSetExtensionSize(entry.getKey().getNumber(), (MessageLite) entry.getValue());} else {return computeFieldSize(descriptor, entry.getValue());}}/*** 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.*/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);}/*** 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 {int size = 0;for (final Object element : (List<?>)value) {size += computeElementSize(type, number, element);}return size;}} else {return computeElementSize(type, number, value);}}}