// 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.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. * 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> { /** * Interface for a FieldDescriptor or lite extension descriptor. This * prevents FieldSet from depending on {@link Descriptors.FieldDescriptor}. */ public interface FieldDescriptorLite> extends Comparable { 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 fields; /** Construct a new FieldSet. */ private FieldSet() { // Use a TreeMap because fields need to be in canonical order when // serializing. // 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(); } /** * Construct an empty FieldSet. This is only used to initialize * DEFAULT_INSTANCE. */ private FieldSet(final boolean dummy) { this.fields = Collections.emptyMap(); } /** Construct a new FieldSet. */ public static > FieldSet newFieldSet() { return new FieldSet(); } /** Get an immutable empty FieldSet. */ @SuppressWarnings("unchecked") public static > FieldSet 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() { for (final Map.Entry entry: fields.entrySet()) { if (entry.getKey().isRepeated()) { final List value = (List)entry.getValue(); fields.put(entry.getKey(), Collections.unmodifiableList(value)); } } fields = Collections.unmodifiableMap(fields); } // ================================================================= /** See {@link Message.Builder#clear()}. */ public void clear() { fields.clear(); } /** * Get a simple map containing all the fields. */ public Map getAllFields() { return 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. */ public Iterator> 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. */ @SuppressWarnings("unchecked") public boolean isInitialized() { for (final Map.Entry entry: fields.entrySet()) { final FieldDescriptorType descriptor = entry.getKey(); if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE) { if (descriptor.isRepeated()) { for (final MessageLite element: (List) 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}. */ @SuppressWarnings("unchecked") public void mergeFrom(final FieldSet other) { for (final Map.Entry 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); } } 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 (final Map.Entry entry: fields.entrySet()) { writeField(entry.getKey(), entry.getValue(), output); } } /** * Like {@link #writeTo} but uses MessageSet wire format. */ public void writeMessageSetTo(final CodedOutputStream output) throws IOException { for (final Map.Entry 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 { 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 (final Map.Entry entry: fields.entrySet()) { size += computeFieldSize(entry.getKey(), entry.getValue()); } return size; } /** * Like {@link #getSerializedSize} but uses MessageSet wire format. */ public int getMessageSetSerializedSize() { int size = 0; for (final Map.Entry 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 { size += computeFieldSize(descriptor, entry.getValue()); } } return size; } /** * 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); } } }