From 6ef984af4b0c63c1c33127a12dcfc8e6359f0c9e Mon Sep 17 00:00:00 2001 From: Feng Xiao Date: Mon, 10 Nov 2014 17:34:54 -0800 Subject: Down-integrate from internal code base. --- java/pom.xml | 29 +- .../java/com/google/protobuf/AbstractMessage.java | 60 ++- .../java/com/google/protobuf/CodedInputStream.java | 30 +- .../main/java/com/google/protobuf/Descriptors.java | 115 +++++ .../java/com/google/protobuf/DynamicMessage.java | 27 +- .../main/java/com/google/protobuf/Extension.java | 23 +- .../java/com/google/protobuf/ExtensionLite.java | 63 +++ .../main/java/com/google/protobuf/FieldSet.java | 4 +- .../java/com/google/protobuf/GeneratedMessage.java | 364 +++++++++++-- .../com/google/protobuf/GeneratedMessageLite.java | 319 +++++++----- .../main/java/com/google/protobuf/Internal.java | 166 ++++++ .../com/google/protobuf/LazyStringArrayList.java | 4 + .../com/google/protobuf/LiteralByteString.java | 9 + .../main/java/com/google/protobuf/MapEntry.java | 433 ++++++++++++++++ .../java/com/google/protobuf/MapEntryLite.java | 331 ++++++++++++ .../main/java/com/google/protobuf/MapField.java | 259 ++++++++++ .../java/com/google/protobuf/MapFieldLite.java | 182 +++++++ .../src/main/java/com/google/protobuf/Message.java | 19 + .../com/google/protobuf/MessageReflection.java | 33 +- .../com/google/protobuf/RepeatedFieldBuilder.java | 24 +- .../main/java/com/google/protobuf/TextFormat.java | 14 +- .../java/com/google/protobuf/UnknownFieldSet.java | 15 + .../com/google/protobuf/UnknownFieldSetLite.java | 297 +++++++++++ .../com/google/protobuf/CodedInputStreamTest.java | 10 +- .../java/com/google/protobuf/DescriptorsTest.java | 6 + .../com/google/protobuf/FieldPresenceTest.java | 363 +++++++++++++ .../com/google/protobuf/GeneratedMessageTest.java | 141 ++++- .../com/google/protobuf/LazyMessageLiteTest.java | 31 -- .../com/google/protobuf/LiteEqualsAndHashTest.java | 23 + .../com/google/protobuf/MapForProto2LiteTest.java | 277 ++++++++++ .../java/com/google/protobuf/MapForProto2Test.java | 488 ++++++++++++++++++ .../src/test/java/com/google/protobuf/MapTest.java | 569 +++++++++++++++++++++ .../com/google/protobuf/TestBadIdentifiers.java | 2 +- .../java/com/google/protobuf/TextFormatTest.java | 15 +- .../com/google/protobuf/UnknownEnumValueTest.java | 255 +++++++++ .../google/protobuf/UnknownFieldSetLiteTest.java | 317 ++++++++++++ .../com/google/protobuf/field_presence_test.proto | 93 ++++ .../com/google/protobuf/lazy_fields_lite.proto | 1 + .../com/google/protobuf/lite_equals_and_hash.proto | 17 + .../google/protobuf/map_for_proto2_lite_test.proto | 63 +++ .../com/google/protobuf/map_for_proto2_test.proto | 62 +++ .../test/java/com/google/protobuf/map_test.proto | 63 +++ .../com/google/protobuf/multiple_files_test.proto | 1 + .../com/google/protobuf/nested_builders_test.proto | 1 + .../com/google/protobuf/nested_extension.proto | 1 + .../google/protobuf/nested_extension_lite.proto | 1 + .../com/google/protobuf/non_nested_extension.proto | 1 + .../protobuf/non_nested_extension_lite.proto | 1 + .../google/protobuf/outer_class_name_test.proto | 2 + .../google/protobuf/outer_class_name_test2.proto | 2 + .../google/protobuf/outer_class_name_test3.proto | 2 + .../com/google/protobuf/test_bad_identifiers.proto | 24 +- .../java/com/google/protobuf/test_check_utf8.proto | 1 + .../com/google/protobuf/test_check_utf8_size.proto | 1 + .../com/google/protobuf/test_custom_options.proto | 1 + .../google/protobuf/test_extra_interfaces.proto | 1 + 56 files changed, 5365 insertions(+), 291 deletions(-) create mode 100644 java/src/main/java/com/google/protobuf/ExtensionLite.java create mode 100644 java/src/main/java/com/google/protobuf/MapEntry.java create mode 100644 java/src/main/java/com/google/protobuf/MapEntryLite.java create mode 100644 java/src/main/java/com/google/protobuf/MapField.java create mode 100644 java/src/main/java/com/google/protobuf/MapFieldLite.java create mode 100644 java/src/main/java/com/google/protobuf/UnknownFieldSetLite.java create mode 100644 java/src/test/java/com/google/protobuf/FieldPresenceTest.java create mode 100644 java/src/test/java/com/google/protobuf/MapForProto2LiteTest.java create mode 100644 java/src/test/java/com/google/protobuf/MapForProto2Test.java create mode 100644 java/src/test/java/com/google/protobuf/MapTest.java create mode 100644 java/src/test/java/com/google/protobuf/UnknownEnumValueTest.java create mode 100644 java/src/test/java/com/google/protobuf/UnknownFieldSetLiteTest.java create mode 100644 java/src/test/java/com/google/protobuf/field_presence_test.proto create mode 100644 java/src/test/java/com/google/protobuf/map_for_proto2_lite_test.proto create mode 100644 java/src/test/java/com/google/protobuf/map_for_proto2_test.proto create mode 100644 java/src/test/java/com/google/protobuf/map_test.proto (limited to 'java') diff --git a/java/pom.xml b/java/pom.xml index 1e3baa3c..a2bd1d1e 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -130,6 +130,10 @@ + + + + target/generated-test-sources @@ -164,34 +168,37 @@ **/AbstractMessageLite.java + **/AbstractParser.java + **/BoundedByteString.java **/ByteString.java **/CodedInputStream.java **/CodedOutputStream.java + **/ExtensionLite.java **/ExtensionRegistryLite.java **/FieldSet.java **/GeneratedMessageLite.java **/Internal.java **/InvalidProtocolBufferException.java + **/LazyFieldLite.java **/LazyStringArrayList.java **/LazyStringList.java + **/LiteralByteString.java + **/MapEntryLite.java + **/MapFieldLite.java **/MessageLite.java **/MessageLiteOrBuilder.java + **/Parser.java + **/ProtocolStringList.java + **/RopeByteString.java **/SmallSortedMap.java **/UninitializedMessageException.java + **/UnknownFieldSetLite.java **/UnmodifiableLazyStringList.java - **/WireFormat.java - **/Parser.java - **/AbstractParser.java - **/BoundedByteString.java - **/LiteralByteString.java - **/RopeByteString.java **/Utf8.java - **/LazyField.java - **/LazyFieldLite.java - **/ProtocolStringList.java + **/WireFormat.java - **/LiteTest.java + **/**LiteTest.java **/*Lite.java @@ -200,7 +207,7 @@ maven-surefire-plugin - **/LiteTest.java + **/**LiteTest.java diff --git a/java/src/main/java/com/google/protobuf/AbstractMessage.java b/java/src/main/java/com/google/protobuf/AbstractMessage.java index ae9d5e39..6de4cae3 100644 --- a/java/src/main/java/com/google/protobuf/AbstractMessage.java +++ b/java/src/main/java/com/google/protobuf/AbstractMessage.java @@ -37,6 +37,9 @@ import com.google.protobuf.Internal.EnumLite; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; @@ -143,6 +146,40 @@ public abstract class AbstractMessage extends AbstractMessageLite return toByteString(a).equals(toByteString(b)); } + /** + * Converts a list of MapEntry messages into a Map used for equals() and + * hashCode(). + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + private static Map convertMapEntryListToMap(List list) { + if (list.isEmpty()) { + return Collections.emptyMap(); + } + Map result = new HashMap(); + Iterator iterator = list.iterator(); + Message entry = (Message) iterator.next(); + Descriptors.Descriptor descriptor = entry.getDescriptorForType(); + Descriptors.FieldDescriptor key = descriptor.findFieldByName("key"); + Descriptors.FieldDescriptor value = descriptor.findFieldByName("value"); + result.put(entry.getField(key), entry.getField(value)); + while (iterator.hasNext()) { + entry = (Message) iterator.next(); + result.put(entry.getField(key), entry.getField(value)); + } + return result; + } + + /** + * Compares two map fields. The parameters must be a list of MapEntry + * messages. + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + private static boolean compareMapField(Object a, Object b) { + Map ma = convertMapEntryListToMap((List) a); + Map mb = convertMapEntryListToMap((List) b); + return MapFieldLite.equals(ma, mb); + } + /** * Compares two set of fields. * This method is used to implement {@link AbstractMessage#equals(Object)} @@ -181,6 +218,10 @@ public abstract class AbstractMessage extends AbstractMessageLite return false; } } + } else if (descriptor.isMapField()) { + if (!compareMapField(value1, value2)) { + return false; + } } else { // Compare non-bytes fields. if (!value1.equals(value2)) { @@ -190,6 +231,15 @@ public abstract class AbstractMessage extends AbstractMessageLite } return true; } + + /** + * Calculates the hash code of a map field. {@code value} must be a list of + * MapEntry messages. + */ + @SuppressWarnings("unchecked") + private static int hashMapField(Object value) { + return MapFieldLite.calculateHashCodeForMap(convertMapEntryListToMap((List) value)); + } /** Get a hash code for given fields and values, using the given seed. */ @SuppressWarnings("unchecked") @@ -198,7 +248,9 @@ public abstract class AbstractMessage extends AbstractMessageLite FieldDescriptor field = entry.getKey(); Object value = entry.getValue(); hash = (37 * hash) + field.getNumber(); - if (field.getType() != FieldDescriptor.Type.ENUM){ + if (field.isMapField()) { + hash = (53 * hash) + hashMapField(value); + } else if (field.getType() != FieldDescriptor.Type.ENUM){ hash = (53 * hash) + value.hashCode(); } else if (field.isRepeated()) { List list = (List) value; @@ -359,6 +411,12 @@ public abstract class AbstractMessage extends AbstractMessageLite "getFieldBuilder() called on an unsupported message type."); } + public Message.Builder getRepeatedFieldBuilder(final FieldDescriptor field, + int index) { + throw new UnsupportedOperationException( + "getRepeatedFieldBuilder() called on an unsupported message type."); + } + public String toString() { return TextFormat.printToString(this); } diff --git a/java/src/main/java/com/google/protobuf/CodedInputStream.java b/java/src/main/java/com/google/protobuf/CodedInputStream.java index a00ae86f..0ca00bab 100644 --- a/java/src/main/java/com/google/protobuf/CodedInputStream.java +++ b/java/src/main/java/com/google/protobuf/CodedInputStream.java @@ -612,16 +612,16 @@ public final class CodedInputStream { return x; } else if (bufferSize - pos < 9) { break fastpath; - } else if ((x ^= (buffer[pos++] << 7)) < 0L) { - x ^= (~0L << 7); - } else if ((x ^= (buffer[pos++] << 14)) >= 0L) { - x ^= (~0L << 7) ^ (~0L << 14); - } else if ((x ^= (buffer[pos++] << 21)) < 0L) { - x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21); + } else if ((x ^= (buffer[pos++] << 7)) < 0) { + x ^= (~0 << 7); + } else if ((x ^= (buffer[pos++] << 14)) >= 0) { + x ^= (~0 << 7) ^ (~0 << 14); + } else if ((x ^= (buffer[pos++] << 21)) < 0) { + x ^= (~0 << 7) ^ (~0 << 14) ^ (~0 << 21); } else { int y = buffer[pos++]; x ^= y << 28; - x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28); + x ^= (~0 << 7) ^ (~0 << 14) ^ (~0 << 21) ^ (~0 << 28); if (y < 0 && buffer[pos++] < 0 && buffer[pos++] < 0 && @@ -739,13 +739,13 @@ public final class CodedInputStream { return y; } else if (bufferSize - pos < 9) { break fastpath; - } else if ((x = y ^ (buffer[pos++] << 7)) < 0L) { - x ^= (~0L << 7); - } else if ((x ^= (buffer[pos++] << 14)) >= 0L) { - x ^= (~0L << 7) ^ (~0L << 14); - } else if ((x ^= (buffer[pos++] << 21)) < 0L) { - x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21); - } else if ((x ^= ((long) buffer[pos++] << 28)) >= 0L) { + } else if ((y ^= (buffer[pos++] << 7)) < 0) { + x = y ^ (~0 << 7); + } else if ((y ^= (buffer[pos++] << 14)) >= 0) { + x = y ^ ((~0 << 7) ^ (~0 << 14)); + } else if ((y ^= (buffer[pos++] << 21)) < 0) { + x = y ^ ((~0 << 7) ^ (~0 << 14) ^ (~0 << 21)); + } else if ((x = ((long) y) ^ ((long) buffer[pos++] << 28)) >= 0L) { x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28); } else if ((x ^= ((long) buffer[pos++] << 35)) < 0L) { x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35); @@ -882,7 +882,7 @@ public final class CodedInputStream { /** See setSizeLimit() */ private int sizeLimit = DEFAULT_SIZE_LIMIT; - private static final int DEFAULT_RECURSION_LIMIT = 64; + private static final int DEFAULT_RECURSION_LIMIT = 100; private static final int DEFAULT_SIZE_LIMIT = 64 << 20; // 64MB private static final int BUFFER_SIZE = 4096; diff --git a/java/src/main/java/com/google/protobuf/Descriptors.java b/java/src/main/java/com/google/protobuf/Descriptors.java index caae0f77..d65e8b49 100644 --- a/java/src/main/java/com/google/protobuf/Descriptors.java +++ b/java/src/main/java/com/google/protobuf/Descriptors.java @@ -32,6 +32,7 @@ package com.google.protobuf; import com.google.protobuf.DescriptorProtos.*; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -40,6 +41,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.WeakHashMap; import java.util.logging.Logger; import java.io.UnsupportedEncodingException; @@ -123,6 +125,26 @@ public final class Descriptors { return Collections.unmodifiableList(Arrays.asList(publicDependencies)); } + /** The syntax of the .proto file. */ + public enum Syntax { + UNKNOWN("unknown"), + PROTO2("proto2"), + PROTO3("proto3"); + + Syntax(String name) { + this.name = name; + } + private final String name; + } + + /** Get the syntax of the .proto file. */ + public Syntax getSyntax() { + if (Syntax.PROTO3.name.equals(proto.getSyntax())) { + return Syntax.PROTO3; + } + return Syntax.PROTO2; + } + /** * Find a message type in the file by name. Does not find nested types. * @@ -539,6 +561,10 @@ public final class Descriptors { extensions[i].setProto(proto.getExtension(i)); } } + + boolean supportsUnknownEnumValue() { + return getSyntax() == Syntax.PROTO3; + } } // ================================================================= @@ -871,6 +897,11 @@ public final class Descriptors { return (type == Type.STRING) && (getFile().getOptions().getJavaStringCheckUtf8()); } + boolean isMapField() { + return getType() == Type.MESSAGE && isRepeated() + && getMessageType().getOptions().getMapEntry(); + } + // I'm pretty sure values() constructs a new array every time, since there // is nothing stopping the caller from mutating the array. Therefore we // make a static copy here. @@ -1001,6 +1032,11 @@ public final class Descriptors { return getNumber() - other.getNumber(); } + @Override + public String toString() { + return getFullName(); + } + private final int index; private FieldDescriptorProto proto; @@ -1420,6 +1456,64 @@ public final class Descriptors { return file.pool.enumValuesByNumber.get( new DescriptorPool.DescriptorIntPair(this, number)); } + + /** + * Get the enum value for a number. If no enum value has this number, + * construct an EnumValueDescriptor for it. + */ + public EnumValueDescriptor findValueByNumberCreatingIfUnknown(final int number) { + EnumValueDescriptor result = findValueByNumber(number); + if (result != null) { + return result; + } + // The number represents an unknown enum value. + synchronized (this) { + // Descriptors are compared by object identity so for the same number + // we need to return the same EnumValueDescriptor object. This means + // we have to store created EnumValueDescriptors. However, as there + // are potentially 2G unknown enum values, storing all of these + // objects persistently will consume lots of memory for long-running + // services and it's also unnecessary as not many EnumValueDescriptors + // will be used at the same time. + // + // To solve the problem we take advantage of Java's weak references and + // rely on gc to release unused descriptors. + // + // Here is how it works: + // * We store unknown EnumValueDescriptors in a WeakHashMap with the + // value being a weak reference to the descriptor. + // * The descriptor holds a strong reference to the key so as long + // as the EnumValueDescriptor is in use, the key will be there + // and the corresponding map entry will be there. Following-up + // queries with the same number will return the same descriptor. + // * If the user no longer uses an unknown EnumValueDescriptor, + // it will be gc-ed since we only hold a weak reference to it in + // the map. The key in the corresponding map entry will also be + // gc-ed as the only strong reference to it is in the descriptor + // which is just gc-ed. With the key being gone WeakHashMap will + // then remove the whole entry. This way unknown descriptors will + // be freed automatically and we don't need to do anything to + // clean-up unused map entries. + + // Note: We must use "new Integer(number)" here because we don't want + // these Integer objects to be cached. + Integer key = new Integer(number); + WeakReference reference = unknownValues.get(key); + if (reference != null) { + result = reference.get(); + } + if (result == null) { + result = new EnumValueDescriptor(file, this, key); + unknownValues.put(key, new WeakReference(result)); + } + } + return result; + } + + // Used in tests only. + int getUnknownEnumValueDescriptorCount() { + return unknownValues.size(); + } private final int index; private EnumDescriptorProto proto; @@ -1427,6 +1521,8 @@ public final class Descriptors { private final FileDescriptor file; private final Descriptor containingType; private EnumValueDescriptor[] values; + private final WeakHashMap> unknownValues = + new WeakHashMap>(); private EnumDescriptor(final EnumDescriptorProto proto, final FileDescriptor file, @@ -1531,6 +1627,25 @@ public final class Descriptors { file.pool.addSymbol(this); file.pool.addEnumValueByNumber(this); } + + private Integer number; + // Create an unknown enum value. + private EnumValueDescriptor( + final FileDescriptor file, + final EnumDescriptor parent, + final Integer number) { + String name = "UNKNOWN_ENUM_VALUE_" + parent.getName() + "_" + number; + EnumValueDescriptorProto proto = EnumValueDescriptorProto + .newBuilder().setName(name).setNumber(number).build(); + this.index = -1; + this.proto = proto; + this.file = file; + this.type = parent; + this.fullName = parent.getFullName() + '.' + proto.getName(); + this.number = number; + + // Don't add this descriptor into pool. + } /** See {@link FileDescriptor#setProto}. */ private void setProto(final EnumValueDescriptorProto proto) { diff --git a/java/src/main/java/com/google/protobuf/DynamicMessage.java b/java/src/main/java/com/google/protobuf/DynamicMessage.java index c9ce667b..06b30fff 100644 --- a/java/src/main/java/com/google/protobuf/DynamicMessage.java +++ b/java/src/main/java/com/google/protobuf/DynamicMessage.java @@ -549,12 +549,22 @@ public final class DynamicMessage extends AbstractMessage { } public Builder setUnknownFields(UnknownFieldSet unknownFields) { + if (getDescriptorForType().getFile().getSyntax() + == Descriptors.FileDescriptor.Syntax.PROTO3) { + // Proto3 discards unknown fields. + return this; + } this.unknownFields = unknownFields; return this; } @Override public Builder mergeUnknownFields(UnknownFieldSet unknownFields) { + if (getDescriptorForType().getFile().getSyntax() + == Descriptors.FileDescriptor.Syntax.PROTO3) { + // Proto3 discards unknown fields. + return this; + } this.unknownFields = UnknownFieldSet.newBuilder(this.unknownFields) .mergeFrom(unknownFields) @@ -588,10 +598,12 @@ public final class DynamicMessage extends AbstractMessage { throw new IllegalArgumentException( "DynamicMessage should use EnumValueDescriptor to set Enum Value."); } - if (field.getEnumType() != ((EnumValueDescriptor) value).getType()) { - throw new IllegalArgumentException( - "EnumValueDescriptor doesn't much Enum Field."); - } + // TODO(xiaofeng): Re-enable this check after Orgstore is fixed to not + // set incorrect EnumValueDescriptors. + // if (field.getEnumType() != ((EnumValueDescriptor) value).getType()) { + // throw new IllegalArgumentException( + // "EnumValueDescriptor doesn't match Enum Field."); + // } } /** Verifies the value for an enum field. */ @@ -618,5 +630,12 @@ public final class DynamicMessage extends AbstractMessage { throw new UnsupportedOperationException( "getFieldBuilder() called on a dynamic message type."); } + + @Override + public com.google.protobuf.Message.Builder getRepeatedFieldBuilder(FieldDescriptor field, + int index) { + throw new UnsupportedOperationException( + "getRepeatedFieldBuilder() called on a dynamic message type."); + } } } diff --git a/java/src/main/java/com/google/protobuf/Extension.java b/java/src/main/java/com/google/protobuf/Extension.java index 0baa22b3..68d29f33 100644 --- a/java/src/main/java/com/google/protobuf/Extension.java +++ b/java/src/main/java/com/google/protobuf/Extension.java @@ -35,27 +35,16 @@ package com.google.protobuf; * * @author liujisi@google.com (Jisi Liu) */ -public abstract class Extension { - /** Returns the field number of the extension. */ - public abstract int getNumber(); - - /** Returns the type of the field. */ - public abstract WireFormat.FieldType getLiteType(); - - /** Returns whether it is a repeated field. */ - public abstract boolean isRepeated(); +public abstract class Extension + extends ExtensionLite { /** Returns the descriptor of the extension. */ public abstract Descriptors.FieldDescriptor getDescriptor(); - /** Returns the default value of the extension field. */ - public abstract Type getDefaultValue(); - - /** - * Returns the default instance of the extension field, if it's a message - * extension. - */ - public abstract MessageLite getMessageDefaultInstance(); + /** Returns whether or not this extension is a Lite Extension. */ + final boolean isLite() { + return false; + } // All the methods below are extension implementation details. diff --git a/java/src/main/java/com/google/protobuf/ExtensionLite.java b/java/src/main/java/com/google/protobuf/ExtensionLite.java new file mode 100644 index 00000000..f8f5bd2c --- /dev/null +++ b/java/src/main/java/com/google/protobuf/ExtensionLite.java @@ -0,0 +1,63 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// 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; + +/** + * Lite interface that generated extensions implement. + *

+ * Methods are for use by generated code only. You can hold a reference to + * extensions using this type name. + */ +public abstract class ExtensionLite { + + /** Returns the field number of the extension. */ + public abstract int getNumber(); + + /** Returns the type of the field. */ + public abstract WireFormat.FieldType getLiteType(); + + /** Returns whether it is a repeated field. */ + public abstract boolean isRepeated(); + + /** Returns the default value of the extension field. */ + public abstract Type getDefaultValue(); + + /** + * Returns the default instance of the extension field, if it's a message + * extension. + */ + public abstract MessageLite getMessageDefaultInstance(); + + /** Returns whether or not this extension is a Lite Extension. */ + boolean isLite() { + return true; + } +} diff --git a/java/src/main/java/com/google/protobuf/FieldSet.java b/java/src/main/java/com/google/protobuf/FieldSet.java index 392f4efc..ff9b5bc0 100644 --- a/java/src/main/java/com/google/protobuf/FieldSet.java +++ b/java/src/main/java/com/google/protobuf/FieldSet.java @@ -672,7 +672,7 @@ final class FieldSet builder) { + unknownFields = builder.getUnknownFields(); } public Parser getParserForType() { @@ -291,13 +300,12 @@ public abstract class GeneratedMessage extends AbstractMessage return isClean; } - // This is implemented here only to work around an apparent bug in the - // Java compiler and/or build system. See bug #1898463. The mere presence - // of this dummy clone() implementation makes it go away. @Override public BuilderType clone() { - throw new UnsupportedOperationException( - "This is supposed to be overridden by subclasses."); + BuilderType builder = + (BuilderType) getDefaultInstanceForType().newBuilderForType(); + builder.mergeFrom(buildPartial()); + return builder; } /** @@ -357,6 +365,13 @@ public abstract class GeneratedMessage extends AbstractMessage return internalGetFieldAccessorTable().getField(field).getBuilder(this); } + //@Override (Java 1.6 override semantics, but we must support 1.5) + public Message.Builder getRepeatedFieldBuilder(final FieldDescriptor field, + int index) { + return internalGetFieldAccessorTable().getField(field).getRepeatedBuilder( + this, index); + } + //@Override (Java 1.6 override semantics, but we must support 1.5) public boolean hasOneof(final OneofDescriptor oneof) { return internalGetFieldAccessorTable().getOneof(oneof).has(this); @@ -428,7 +443,7 @@ public abstract class GeneratedMessage extends AbstractMessage return (BuilderType) this; } - public final BuilderType setUnknownFields( + public BuilderType setUnknownFields( final UnknownFieldSet unknownFields) { this.unknownFields = unknownFields; onChanged(); @@ -436,7 +451,7 @@ public abstract class GeneratedMessage extends AbstractMessage } @Override - public final BuilderType mergeUnknownFields( + public BuilderType mergeUnknownFields( final UnknownFieldSet unknownFields) { this.unknownFields = UnknownFieldSet.newBuilder(this.unknownFields) @@ -529,6 +544,25 @@ public abstract class GeneratedMessage extends AbstractMessage isClean = false; } } + + /** + * Gets the map field with the given field number. This method should be + * overridden in the generated message class if the message contains map + * fields. + * + * Unlike other field types, reflection support for map fields can't be + * implemented based on generated public API because we need to access a + * map field as a list in reflection API but the generated API only allows + * us to access it as a map. This method returns the underlying map field + * directly and thus enables us to access the map field as a list. + */ + @SuppressWarnings({"unused", "rawtypes"}) + protected MapField internalGetMapField(int fieldNumber) { + // Note that we can't use descriptor names here because this method will + // be called when descriptor is being initialized. + throw new RuntimeException( + "No map fields found in " + getClass().getName()); + } } // ================================================================= @@ -541,19 +575,19 @@ public abstract class GeneratedMessage extends AbstractMessage /** Check if a singular extension is present. */ boolean hasExtension( - Extension extension); + ExtensionLite extension); /** Get the number of elements in a repeated extension. */ int getExtensionCount( - Extension> extension); + ExtensionLite> extension); /** Get the value of an extension. */ Type getExtension( - Extension extension); + ExtensionLite extension); /** Get one element of a repeated extension. */ Type getExtension( - Extension> extension, + ExtensionLite> extension, int index); } @@ -625,7 +659,9 @@ public abstract class GeneratedMessage extends AbstractMessage /** Check if a singular extension is present. */ //@Override (Java 1.6 override semantics, but we must support 1.5) public final boolean hasExtension( - final Extension extension) { + final ExtensionLite extensionLite) { + Extension extension = checkNotLite(extensionLite); + verifyExtensionContainingType(extension); return extensions.hasField(extension.getDescriptor()); } @@ -633,7 +669,9 @@ public abstract class GeneratedMessage extends AbstractMessage /** Get the number of elements in a repeated extension. */ //@Override (Java 1.6 override semantics, but we must support 1.5) public final int getExtensionCount( - final Extension> extension) { + final ExtensionLite> extensionLite) { + Extension> extension = checkNotLite(extensionLite); + verifyExtensionContainingType(extension); final FieldDescriptor descriptor = extension.getDescriptor(); return extensions.getRepeatedFieldCount(descriptor); @@ -643,7 +681,9 @@ public abstract class GeneratedMessage extends AbstractMessage //@Override (Java 1.6 override semantics, but we must support 1.5) @SuppressWarnings("unchecked") public final Type getExtension( - final Extension extension) { + final ExtensionLite extensionLite) { + Extension extension = checkNotLite(extensionLite); + verifyExtensionContainingType(extension); FieldDescriptor descriptor = extension.getDescriptor(); final Object value = extensions.getField(descriptor); @@ -666,8 +706,10 @@ public abstract class GeneratedMessage extends AbstractMessage //@Override (Java 1.6 override semantics, but we must support 1.5) @SuppressWarnings("unchecked") public final Type getExtension( - final Extension> extension, + final ExtensionLite> extensionLite, final int index) { + Extension> extension = checkNotLite(extensionLite); + verifyExtensionContainingType(extension); FieldDescriptor descriptor = extension.getDescriptor(); return (Type) extension.singularFromReflectionType( @@ -914,11 +956,10 @@ public abstract class GeneratedMessage extends AbstractMessage // This is implemented here only to work around an apparent bug in the // Java compiler and/or build system. See bug #1898463. The mere presence - // of this dummy clone() implementation makes it go away. + // of this clone() implementation makes it go away. @Override public BuilderType clone() { - throw new UnsupportedOperationException( - "This is supposed to be overridden by subclasses."); + return super.clone(); } private void ensureExtensionsIsMutable() { @@ -943,7 +984,9 @@ public abstract class GeneratedMessage extends AbstractMessage /** Check if a singular extension is present. */ //@Override (Java 1.6 override semantics, but we must support 1.5) public final boolean hasExtension( - final Extension extension) { + final ExtensionLite extensionLite) { + Extension extension = checkNotLite(extensionLite); + verifyExtensionContainingType(extension); return extensions.hasField(extension.getDescriptor()); } @@ -951,7 +994,9 @@ public abstract class GeneratedMessage extends AbstractMessage /** Get the number of elements in a repeated extension. */ //@Override (Java 1.6 override semantics, but we must support 1.5) public final int getExtensionCount( - final Extension> extension) { + final ExtensionLite> extensionLite) { + Extension> extension = checkNotLite(extensionLite); + verifyExtensionContainingType(extension); final FieldDescriptor descriptor = extension.getDescriptor(); return extensions.getRepeatedFieldCount(descriptor); @@ -960,7 +1005,9 @@ public abstract class GeneratedMessage extends AbstractMessage /** Get the value of an extension. */ //@Override (Java 1.6 override semantics, but we must support 1.5) public final Type getExtension( - final Extension extension) { + final ExtensionLite extensionLite) { + Extension extension = checkNotLite(extensionLite); + verifyExtensionContainingType(extension); FieldDescriptor descriptor = extension.getDescriptor(); final Object value = extensions.getField(descriptor); @@ -982,8 +1029,10 @@ public abstract class GeneratedMessage extends AbstractMessage /** Get one element of a repeated extension. */ //@Override (Java 1.6 override semantics, but we must support 1.5) public final Type getExtension( - final Extension> extension, + final ExtensionLite> extensionLite, final int index) { + Extension> extension = checkNotLite(extensionLite); + verifyExtensionContainingType(extension); FieldDescriptor descriptor = extension.getDescriptor(); return (Type) extension.singularFromReflectionType( @@ -992,8 +1041,10 @@ public abstract class GeneratedMessage extends AbstractMessage /** Set the value of an extension. */ public final BuilderType setExtension( - final Extension extension, + final ExtensionLite extensionLite, final Type value) { + Extension extension = checkNotLite(extensionLite); + verifyExtensionContainingType(extension); ensureExtensionsIsMutable(); final FieldDescriptor descriptor = extension.getDescriptor(); @@ -1004,8 +1055,10 @@ public abstract class GeneratedMessage extends AbstractMessage /** Set the value of one element of a repeated extension. */ public final BuilderType setExtension( - final Extension> extension, + final ExtensionLite> extensionLite, final int index, final Type value) { + Extension> extension = checkNotLite(extensionLite); + verifyExtensionContainingType(extension); ensureExtensionsIsMutable(); final FieldDescriptor descriptor = extension.getDescriptor(); @@ -1018,8 +1071,10 @@ public abstract class GeneratedMessage extends AbstractMessage /** Append a value to a repeated extension. */ public final BuilderType addExtension( - final Extension> extension, + final ExtensionLite> extensionLite, final Type value) { + Extension> extension = checkNotLite(extensionLite); + verifyExtensionContainingType(extension); ensureExtensionsIsMutable(); final FieldDescriptor descriptor = extension.getDescriptor(); @@ -1031,7 +1086,9 @@ public abstract class GeneratedMessage extends AbstractMessage /** Clear an extension. */ public final BuilderType clearExtension( - final Extension extension) { + final ExtensionLite extensionLite) { + Extension extension = checkNotLite(extensionLite); + verifyExtensionContainingType(extension); ensureExtensionsIsMutable(); extensions.clearField(extension.getDescriptor()); @@ -1594,6 +1651,25 @@ public abstract class GeneratedMessage extends AbstractMessage } } } + + /** + * Gets the map field with the given field number. This method should be + * overridden in the generated message class if the message contains map + * fields. + * + * Unlike other field types, reflection support for map fields can't be + * implemented based on generated public API because we need to access a + * map field as a list in reflection API but the generated API only allows + * us to access it as a map. This method returns the underlying map field + * directly and thus enables us to access the map field as a list. + */ + @SuppressWarnings({"rawtypes", "unused"}) + protected MapField internalGetMapField(int fieldNumber) { + // Note that we can't use descriptor names here because this method will + // be called when descriptor is being initialized. + throw new RuntimeException( + "No map fields found in " + getClass().getName()); + } /** * Users should ignore this class. This class provides the implementation @@ -1633,6 +1709,11 @@ public abstract class GeneratedMessage extends AbstractMessage oneofs = new OneofAccessor[descriptor.getOneofs().size()]; initialized = false; } + + private boolean isMapFieldEnabled(FieldDescriptor field) { + boolean result = true; + return result; + } /** * Ensures the field accessors are initialized. This method is thread-safe. @@ -1657,8 +1738,13 @@ public abstract class GeneratedMessage extends AbstractMessage } if (field.isRepeated()) { if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { - fields[i] = new RepeatedMessageFieldAccessor( - field, camelCaseNames[i], messageClass, builderClass); + if (field.isMapField() && isMapFieldEnabled(field)) { + fields[i] = new MapFieldAccessor( + field, camelCaseNames[i], messageClass, builderClass); + } else { + fields[i] = new RepeatedMessageFieldAccessor( + field, camelCaseNames[i], messageClass, builderClass); + } } else if (field.getJavaType() == FieldDescriptor.JavaType.ENUM) { fields[i] = new RepeatedEnumFieldAccessor( field, camelCaseNames[i], messageClass, builderClass); @@ -1744,6 +1830,8 @@ public abstract class GeneratedMessage extends AbstractMessage void clear(Builder builder); Message.Builder newBuilder(); Message.Builder getBuilder(GeneratedMessage.Builder builder); + Message.Builder getRepeatedBuilder(GeneratedMessage.Builder builder, + int index); } /** OneofAccessor provides access to a single oneof. */ @@ -1799,9 +1887,9 @@ public abstract class GeneratedMessage extends AbstractMessage invokeOrDie(clearMethod, builder); } } - + private static boolean supportFieldPresence(FileDescriptor file) { - return true; + return file.getSyntax() == FileDescriptor.Syntax.PROTO2; } // --------------------------------------------------------------- @@ -1919,6 +2007,11 @@ public abstract class GeneratedMessage extends AbstractMessage throw new UnsupportedOperationException( "getFieldBuilder() called on a non-Message type."); } + public Message.Builder getRepeatedBuilder(GeneratedMessage.Builder builder, + int index) { + throw new UnsupportedOperationException( + "getRepeatedFieldBuilder() called on a non-Message type."); + } } private static class RepeatedFieldAccessor implements FieldAccessor { @@ -2014,6 +2107,113 @@ public abstract class GeneratedMessage extends AbstractMessage throw new UnsupportedOperationException( "getFieldBuilder() called on a non-Message type."); } + public Message.Builder getRepeatedBuilder(GeneratedMessage.Builder builder, + int index) { + throw new UnsupportedOperationException( + "getRepeatedFieldBuilder() called on a non-Message type."); + } + } + + private static class MapFieldAccessor implements FieldAccessor { + MapFieldAccessor( + final FieldDescriptor descriptor, final String camelCaseName, + final Class messageClass, + final Class builderClass) { + field = descriptor; + Method getDefaultInstanceMethod = + getMethodOrDie(messageClass, "getDefaultInstance"); + MapField defaultMapField = getMapField( + (GeneratedMessage) invokeOrDie(getDefaultInstanceMethod, null)); + mapEntryMessageDefaultInstance = + defaultMapField.getMapEntryMessageDefaultInstance(); + } + + private final FieldDescriptor field; + private final Message mapEntryMessageDefaultInstance; + + private MapField getMapField(GeneratedMessage message) { + return (MapField) message.internalGetMapField(field.getNumber()); + } + + private MapField getMapField(GeneratedMessage.Builder builder) { + return (MapField) builder.internalGetMapField(field.getNumber()); + } + + public Object get(GeneratedMessage message) { + List result = new ArrayList(); + for (int i = 0; i < getRepeatedCount(message); i++) { + result.add(getRepeated(message, i)); + } + return Collections.unmodifiableList(result); + } + + public Object get(Builder builder) { + List result = new ArrayList(); + for (int i = 0; i < getRepeatedCount(builder); i++) { + result.add(getRepeated(builder, i)); + } + return Collections.unmodifiableList(result); + } + + public void set(Builder builder, Object value) { + clear(builder); + for (Object entry : (List) value) { + addRepeated(builder, entry); + } + } + + public Object getRepeated(GeneratedMessage message, int index) { + return getMapField(message).getList().get(index); + } + + public Object getRepeated(Builder builder, int index) { + return getMapField(builder).getList().get(index); + } + + public void setRepeated(Builder builder, int index, Object value) { + getMapField(builder).getMutableList().set(index, (Message) value); + } + + public void addRepeated(Builder builder, Object value) { + getMapField(builder).getMutableList().add((Message) value); + } + + public boolean has(GeneratedMessage message) { + throw new UnsupportedOperationException( + "hasField() is not supported for repeated fields."); + } + + public boolean has(Builder builder) { + throw new UnsupportedOperationException( + "hasField() is not supported for repeated fields."); + } + + public int getRepeatedCount(GeneratedMessage message) { + return getMapField(message).getList().size(); + } + + public int getRepeatedCount(Builder builder) { + return getMapField(builder).getList().size(); + } + + public void clear(Builder builder) { + getMapField(builder).getMutableList().clear(); + } + + public com.google.protobuf.Message.Builder newBuilder() { + return mapEntryMessageDefaultInstance.newBuilderForType(); + } + + public com.google.protobuf.Message.Builder getBuilder(Builder builder) { + throw new UnsupportedOperationException( + "Nested builder not supported for map fields."); + } + + public com.google.protobuf.Message.Builder getRepeatedBuilder( + Builder builder, int index) { + throw new UnsupportedOperationException( + "Nested builder not supported for map fields."); + } } // --------------------------------------------------------------- @@ -2026,28 +2226,60 @@ public abstract class GeneratedMessage extends AbstractMessage final Class builderClass, final String containingOneofCamelCaseName) { super(descriptor, camelCaseName, messageClass, builderClass, containingOneofCamelCaseName); + + enumDescriptor = descriptor.getEnumType(); valueOfMethod = getMethodOrDie(type, "valueOf", EnumValueDescriptor.class); getValueDescriptorMethod = getMethodOrDie(type, "getValueDescriptor"); + + supportUnknownEnumValue = descriptor.getFile().supportsUnknownEnumValue(); + if (supportUnknownEnumValue) { + getValueMethod = + getMethodOrDie(messageClass, "get" + camelCaseName + "Value"); + getValueMethodBuilder = + getMethodOrDie(builderClass, "get" + camelCaseName + "Value"); + setValueMethod = + getMethodOrDie(builderClass, "set" + camelCaseName + "Value", int.class); + } } + + private EnumDescriptor enumDescriptor; private Method valueOfMethod; private Method getValueDescriptorMethod; + + private boolean supportUnknownEnumValue; + private Method getValueMethod; + private Method getValueMethodBuilder; + private Method setValueMethod; @Override public Object get(final GeneratedMessage message) { + if (supportUnknownEnumValue) { + int value = (Integer) invokeOrDie(getValueMethod, message); + return enumDescriptor.findValueByNumberCreatingIfUnknown(value); + } return invokeOrDie(getValueDescriptorMethod, super.get(message)); } @Override public Object get(final GeneratedMessage.Builder builder) { + if (supportUnknownEnumValue) { + int value = (Integer) invokeOrDie(getValueMethodBuilder, builder); + return enumDescriptor.findValueByNumberCreatingIfUnknown(value); + } return invokeOrDie(getValueDescriptorMethod, super.get(builder)); } @Override public void set(final Builder builder, final Object value) { + if (supportUnknownEnumValue) { + invokeOrDie(setValueMethod, builder, + ((EnumValueDescriptor) value).getNumber()); + return; + } super.set(builder, invokeOrDie(valueOfMethod, null, value)); } } @@ -2059,22 +2291,44 @@ public abstract class GeneratedMessage extends AbstractMessage final Class messageClass, final Class builderClass) { super(descriptor, camelCaseName, messageClass, builderClass); + + enumDescriptor = descriptor.getEnumType(); valueOfMethod = getMethodOrDie(type, "valueOf", EnumValueDescriptor.class); getValueDescriptorMethod = getMethodOrDie(type, "getValueDescriptor"); + + supportUnknownEnumValue = descriptor.getFile().supportsUnknownEnumValue(); + if (supportUnknownEnumValue) { + getRepeatedValueMethod = + getMethodOrDie(messageClass, "get" + camelCaseName + "Value", int.class); + getRepeatedValueMethodBuilder = + getMethodOrDie(builderClass, "get" + camelCaseName + "Value", int.class); + setRepeatedValueMethod = + getMethodOrDie(builderClass, "set" + camelCaseName + "Value", int.class, int.class); + addRepeatedValueMethod = + getMethodOrDie(builderClass, "add" + camelCaseName + "Value", int.class); + } } + private EnumDescriptor enumDescriptor; private final Method valueOfMethod; private final Method getValueDescriptorMethod; + + private boolean supportUnknownEnumValue; + private Method getRepeatedValueMethod; + private Method getRepeatedValueMethodBuilder; + private Method setRepeatedValueMethod; + private Method addRepeatedValueMethod; @Override @SuppressWarnings("unchecked") public Object get(final GeneratedMessage message) { final List newList = new ArrayList(); - for (final Object element : (List) super.get(message)) { - newList.add(invokeOrDie(getValueDescriptorMethod, element)); + final int size = getRepeatedCount(message); + for (int i = 0; i < size; i++) { + newList.add(getRepeated(message, i)); } return Collections.unmodifiableList(newList); } @@ -2083,8 +2337,9 @@ public abstract class GeneratedMessage extends AbstractMessage @SuppressWarnings("unchecked") public Object get(final GeneratedMessage.Builder builder) { final List newList = new ArrayList(); - for (final Object element : (List) super.get(builder)) { - newList.add(invokeOrDie(getValueDescriptorMethod, element)); + final int size = getRepeatedCount(builder); + for (int i = 0; i < size; i++) { + newList.add(getRepeated(builder, i)); } return Collections.unmodifiableList(newList); } @@ -2092,23 +2347,41 @@ public abstract class GeneratedMessage extends AbstractMessage @Override public Object getRepeated(final GeneratedMessage message, final int index) { + if (supportUnknownEnumValue) { + int value = (Integer) invokeOrDie(getRepeatedValueMethod, message, index); + return enumDescriptor.findValueByNumberCreatingIfUnknown(value); + } return invokeOrDie(getValueDescriptorMethod, super.getRepeated(message, index)); } @Override public Object getRepeated(final GeneratedMessage.Builder builder, final int index) { + if (supportUnknownEnumValue) { + int value = (Integer) invokeOrDie(getRepeatedValueMethodBuilder, builder, index); + return enumDescriptor.findValueByNumberCreatingIfUnknown(value); + } return invokeOrDie(getValueDescriptorMethod, super.getRepeated(builder, index)); } @Override public void setRepeated(final Builder builder, final int index, final Object value) { + if (supportUnknownEnumValue) { + invokeOrDie(setRepeatedValueMethod, builder, index, + ((EnumValueDescriptor) value).getNumber()); + return; + } super.setRepeated(builder, index, invokeOrDie(valueOfMethod, null, value)); } @Override public void addRepeated(final Builder builder, final Object value) { + if (supportUnknownEnumValue) { + invokeOrDie(addRepeatedValueMethod, builder, + ((EnumValueDescriptor) value).getNumber()); + return; + } super.addRepeated(builder, invokeOrDie(valueOfMethod, null, value)); } } @@ -2168,9 +2441,12 @@ public abstract class GeneratedMessage extends AbstractMessage super(descriptor, camelCaseName, messageClass, builderClass); newBuilderMethod = getMethodOrDie(type, "newBuilder"); + getBuilderMethodBuilder = getMethodOrDie(builderClass, + "get" + camelCaseName + "Builder", Integer.TYPE); } private final Method newBuilderMethod; + private final Method getBuilderMethodBuilder; private Object coerceType(final Object value) { if (type.isInstance(value)) { @@ -2198,6 +2474,12 @@ public abstract class GeneratedMessage extends AbstractMessage public Message.Builder newBuilder() { return (Message.Builder) invokeOrDie(newBuilderMethod, null); } + @Override + public Message.Builder getRepeatedBuilder( + final GeneratedMessage.Builder builder, final int index) { + return (Message.Builder) invokeOrDie( + getBuilderMethodBuilder, builder, index); + } } } @@ -2210,4 +2492,18 @@ public abstract class GeneratedMessage extends AbstractMessage protected Object writeReplace() throws ObjectStreamException { return new GeneratedMessageLite.SerializedForm(this); } + + /** + * Checks that the {@link Extension} is non-Lite and returns it as a + * {@link GeneratedExtension}. + */ + private static , T> + Extension checkNotLite( + ExtensionLite extension) { + if (extension.isLite()) { + throw new IllegalArgumentException("Expected non-lite extension."); + } + + return (Extension) extension; + } } diff --git a/java/src/main/java/com/google/protobuf/GeneratedMessageLite.java b/java/src/main/java/com/google/protobuf/GeneratedMessageLite.java index 6c5136fd..8fe33bc6 100644 --- a/java/src/main/java/com/google/protobuf/GeneratedMessageLite.java +++ b/java/src/main/java/com/google/protobuf/GeneratedMessageLite.java @@ -30,6 +30,8 @@ package com.google.protobuf; +import com.google.protobuf.WireFormat.FieldType; + import java.io.IOException; import java.io.ObjectStreamException; import java.io.Serializable; @@ -50,10 +52,15 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite implements Serializable { private static final long serialVersionUID = 1L; + /** For use by generated code only. */ + protected UnknownFieldSetLite unknownFields; + protected GeneratedMessageLite() { + unknownFields = UnknownFieldSetLite.getDefaultInstance(); } protected GeneratedMessageLite(Builder builder) { + unknownFields = builder.unknownFields; } public Parser getParserForType() { @@ -62,15 +69,16 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite } /** - * Called by subclasses to parse an unknown field. + * Called by subclasses to parse an unknown field. For use by generated code + * only. * @return {@code true} unless the tag is an end-group tag. */ - protected boolean parseUnknownField( + protected static boolean parseUnknownField( CodedInputStream input, - CodedOutputStream unknownFieldsCodedOutput, + UnknownFieldSetLite.Builder unknownFields, ExtensionRegistryLite extensionRegistry, int tag) throws IOException { - return input.skipField(tag, unknownFieldsCodedOutput); + return unknownFields.mergeFieldFrom(tag, input); } /** @@ -84,22 +92,28 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite public abstract static class Builder extends AbstractMessageLite.Builder { + + private UnknownFieldSetLite unknownFields = + UnknownFieldSetLite.getDefaultInstance(); + protected Builder() {} //@Override (Java 1.6 override semantics, but we must support 1.5) public BuilderType clear() { - unknownFields = ByteString.EMPTY; + unknownFields = UnknownFieldSetLite.getDefaultInstance(); return (BuilderType) this; } - // This is implemented here only to work around an apparent bug in the - // Java compiler and/or build system. See bug #1898463. The mere presence - // of this dummy clone() implementation makes it go away. - @Override + //@Override (Java 1.6 override semantics, but we must support 1.5) public BuilderType clone() { - throw new UnsupportedOperationException( - "This is supposed to be overridden by subclasses."); + BuilderType builder = + (BuilderType) getDefaultInstanceForType().newBuilderForType(); + builder.mergeFrom(buildPartial()); + return builder; } + + /** All subclasses implement this. */ + public abstract MessageType buildPartial(); /** All subclasses implement this. */ public abstract BuilderType mergeFrom(MessageType message); @@ -113,22 +127,43 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite */ protected boolean parseUnknownField( CodedInputStream input, - CodedOutputStream unknownFieldsCodedOutput, + UnknownFieldSetLite.Builder unknownFields, ExtensionRegistryLite extensionRegistry, int tag) throws IOException { - return input.skipField(tag, unknownFieldsCodedOutput); + return unknownFields.mergeFieldFrom(tag, input); } - public final ByteString getUnknownFields() { - return unknownFields; + /** + * Merge some unknown fields into the {@link UnknownFieldSetLite} for this + * message. + * + *

For use by generated code only. + */ + protected final BuilderType mergeUnknownFields( + final UnknownFieldSetLite unknownFields) { + this.unknownFields = UnknownFieldSetLite.concat(this.unknownFields, unknownFields); + return (BuilderType) this; } - - public final BuilderType setUnknownFields(final ByteString unknownFields) { - this.unknownFields = unknownFields; + + public BuilderType mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + MessageType parsedMessage = null; + try { + parsedMessage = + (MessageType) getDefaultInstanceForType().getParserForType().parsePartialFrom( + input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (MessageType) e.getUnfinishedMessage(); + throw e; + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } return (BuilderType) this; } - - private ByteString unknownFields = ByteString.EMPTY; } @@ -143,18 +178,18 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite /** Check if a singular extension is present. */ boolean hasExtension( - GeneratedExtension extension); + ExtensionLite extension); /** Get the number of elements in a repeated extension. */ int getExtensionCount( - GeneratedExtension> extension); + ExtensionLite> extension); /** Get the value of an extension. */ - Type getExtension(GeneratedExtension extension); + Type getExtension(ExtensionLite extension); /** Get one element of a repeated extension. */ Type getExtension( - GeneratedExtension> extension, + ExtensionLite> extension, int index); } @@ -166,7 +201,11 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite extends GeneratedMessageLite implements ExtendableMessageOrBuilder { - private final FieldSet extensions; + /** + * Represents the set of extensions on this message. For use by generated + * code only. + */ + protected final FieldSet extensions; protected ExtendableMessage() { this.extensions = FieldSet.newFieldSet(); @@ -190,30 +229,39 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite /** Check if a singular extension is present. */ //@Override (Java 1.6 override semantics, but we must support 1.5) public final boolean hasExtension( - final GeneratedExtension extension) { - verifyExtensionContainingType(extension); - return extensions.hasField(extension.descriptor); + final ExtensionLite extension) { + GeneratedExtension extensionLite = + checkIsLite(extension); + + verifyExtensionContainingType(extensionLite); + return extensions.hasField(extensionLite.descriptor); } /** Get the number of elements in a repeated extension. */ //@Override (Java 1.6 override semantics, but we must support 1.5) public final int getExtensionCount( - final GeneratedExtension> extension) { - verifyExtensionContainingType(extension); - return extensions.getRepeatedFieldCount(extension.descriptor); + final ExtensionLite> extension) { + GeneratedExtension> extensionLite = + checkIsLite(extension); + + verifyExtensionContainingType(extensionLite); + return extensions.getRepeatedFieldCount(extensionLite.descriptor); } /** Get the value of an extension. */ //@Override (Java 1.6 override semantics, but we must support 1.5) @SuppressWarnings("unchecked") public final Type getExtension( - final GeneratedExtension extension) { - verifyExtensionContainingType(extension); - final Object value = extensions.getField(extension.descriptor); + final ExtensionLite extension) { + GeneratedExtension extensionLite = + checkIsLite(extension); + + verifyExtensionContainingType(extensionLite); + final Object value = extensions.getField(extensionLite.descriptor); if (value == null) { - return extension.defaultValue; + return extensionLite.defaultValue; } else { - return (Type) extension.fromFieldSetType(value); + return (Type) extensionLite.fromFieldSetType(value); } } @@ -221,11 +269,14 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite //@Override (Java 1.6 override semantics, but we must support 1.5) @SuppressWarnings("unchecked") public final Type getExtension( - final GeneratedExtension> extension, + final ExtensionLite> extension, final int index) { - verifyExtensionContainingType(extension); - return (Type) extension.singularFromFieldSetType( - extensions.getRepeatedField(extension.descriptor, index)); + GeneratedExtension> extensionLite = + checkIsLite(extension); + + verifyExtensionContainingType(extensionLite); + return (Type) extensionLite.singularFromFieldSetType( + extensions.getRepeatedField(extensionLite.descriptor, index)); } /** Called by subclasses to check if all extensions are initialized. */ @@ -233,25 +284,6 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite return extensions.isInitialized(); } - /** - * Called by subclasses to parse an unknown field or an extension. - * @return {@code true} unless the tag is an end-group tag. - */ - @Override - protected boolean parseUnknownField( - CodedInputStream input, - CodedOutputStream unknownFieldsCodedOutput, - ExtensionRegistryLite extensionRegistry, - int tag) throws IOException { - return GeneratedMessageLite.parseUnknownField( - extensions, - getDefaultInstanceForType(), - input, - unknownFieldsCodedOutput, - extensionRegistry, - tag); - } - /** * Used by parsing constructors in generated classes. @@ -377,30 +409,39 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite /** Check if a singular extension is present. */ //@Override (Java 1.6 override semantics, but we must support 1.5) public final boolean hasExtension( - final GeneratedExtension extension) { - verifyExtensionContainingType(extension); - return extensions.hasField(extension.descriptor); + final ExtensionLite extension) { + GeneratedExtension extensionLite = + checkIsLite(extension); + + verifyExtensionContainingType(extensionLite); + return extensions.hasField(extensionLite.descriptor); } /** Get the number of elements in a repeated extension. */ //@Override (Java 1.6 override semantics, but we must support 1.5) public final int getExtensionCount( - final GeneratedExtension> extension) { - verifyExtensionContainingType(extension); - return extensions.getRepeatedFieldCount(extension.descriptor); + final ExtensionLite> extension) { + GeneratedExtension> extensionLite = + checkIsLite(extension); + + verifyExtensionContainingType(extensionLite); + return extensions.getRepeatedFieldCount(extensionLite.descriptor); } /** Get the value of an extension. */ //@Override (Java 1.6 override semantics, but we must support 1.5) @SuppressWarnings("unchecked") public final Type getExtension( - final GeneratedExtension extension) { - verifyExtensionContainingType(extension); - final Object value = extensions.getField(extension.descriptor); + final ExtensionLite extension) { + GeneratedExtension extensionLite = + checkIsLite(extension); + + verifyExtensionContainingType(extensionLite); + final Object value = extensions.getField(extensionLite.descriptor); if (value == null) { - return extension.defaultValue; + return extensionLite.defaultValue; } else { - return (Type) extension.fromFieldSetType(value); + return (Type) extensionLite.fromFieldSetType(value); } } @@ -408,11 +449,14 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite @SuppressWarnings("unchecked") //@Override (Java 1.6 override semantics, but we must support 1.5) public final Type getExtension( - final GeneratedExtension> extension, + final ExtensionLite> extension, final int index) { - verifyExtensionContainingType(extension); - return (Type) extension.singularFromFieldSetType( - extensions.getRepeatedField(extension.descriptor, index)); + GeneratedExtension> extensionLite = + checkIsLite(extension); + + verifyExtensionContainingType(extensionLite); + return (Type) extensionLite.singularFromFieldSetType( + extensions.getRepeatedField(extensionLite.descriptor, index)); } // This is implemented here only to work around an apparent bug in the @@ -423,46 +467,57 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite throw new UnsupportedOperationException( "This is supposed to be overridden by subclasses."); } - + /** Set the value of an extension. */ public final BuilderType setExtension( - final GeneratedExtension extension, + final ExtensionLite extension, final Type value) { - verifyExtensionContainingType(extension); + GeneratedExtension extensionLite = + checkIsLite(extension); + + verifyExtensionContainingType(extensionLite); ensureExtensionsIsMutable(); - extensions.setField(extension.descriptor, - extension.toFieldSetType(value)); + extensions.setField(extensionLite.descriptor, + extensionLite.toFieldSetType(value)); return (BuilderType) this; } /** Set the value of one element of a repeated extension. */ public final BuilderType setExtension( - final GeneratedExtension> extension, + final ExtensionLite> extension, final int index, final Type value) { - verifyExtensionContainingType(extension); + GeneratedExtension> extensionLite = + checkIsLite(extension); + + verifyExtensionContainingType(extensionLite); ensureExtensionsIsMutable(); - extensions.setRepeatedField(extension.descriptor, index, - extension.singularToFieldSetType(value)); + extensions.setRepeatedField(extensionLite.descriptor, index, + extensionLite.singularToFieldSetType(value)); return (BuilderType) this; } /** Append a value to a repeated extension. */ public final BuilderType addExtension( - final GeneratedExtension> extension, + final ExtensionLite> extension, final Type value) { - verifyExtensionContainingType(extension); + GeneratedExtension> extensionLite = + checkIsLite(extension); + + verifyExtensionContainingType(extensionLite); ensureExtensionsIsMutable(); - extensions.addRepeatedField(extension.descriptor, - extension.singularToFieldSetType(value)); + extensions.addRepeatedField(extensionLite.descriptor, + extensionLite.singularToFieldSetType(value)); return (BuilderType) this; } /** Clear an extension. */ public final BuilderType clearExtension( - final GeneratedExtension extension) { - verifyExtensionContainingType(extension); + final ExtensionLite extension) { + GeneratedExtension extensionLite = checkIsLite(extension); + + verifyExtensionContainingType(extensionLite); ensureExtensionsIsMutable(); - extensions.clearField(extension.descriptor); + extensions.clearField(extensionLite.descriptor); return (BuilderType) this; } @@ -471,44 +526,24 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite return extensions.isInitialized(); } - /** - * Called by subclasses to parse an unknown field or an extension. - * @return {@code true} unless the tag is an end-group tag. - */ - @Override - protected boolean parseUnknownField( - CodedInputStream input, - CodedOutputStream unknownFieldsCodedOutput, - ExtensionRegistryLite extensionRegistry, - int tag) throws IOException { - ensureExtensionsIsMutable(); - return GeneratedMessageLite.parseUnknownField( - extensions, - getDefaultInstanceForType(), - input, - unknownFieldsCodedOutput, - extensionRegistry, - tag); - } - protected final void mergeExtensionFields(final MessageType other) { ensureExtensionsIsMutable(); extensions.mergeFrom(((ExtendableMessage) other).extensions); } } - // ----------------------------------------------------------------- + //----------------------------------------------------------------- /** - * Parse an unknown field or an extension. + * Parse an unknown field or an extension. For use by generated code only. * @return {@code true} unless the tag is an end-group tag. */ - private static + protected static boolean parseUnknownField( FieldSet extensions, MessageType defaultInstance, CodedInputStream input, - CodedOutputStream unknownFieldsCodedOutput, + UnknownFieldSetLite.Builder unknownFields, ExtensionRegistryLite extensionRegistry, int tag) throws IOException { int wireType = WireFormat.getTagWireType(tag); @@ -537,7 +572,7 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite } if (unknown) { // Unknown field or wrong wire type. Skip. - return input.skipField(tag, unknownFieldsCodedOutput); + return unknownFields.mergeFieldFrom(tag, input); } if (packed) { @@ -599,8 +634,7 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite // If the number isn't recognized as a valid value for this enum, // write it to unknown fields object. if (value == null) { - unknownFieldsCodedOutput.writeRawVarint32(tag); - unknownFieldsCodedOutput.writeUInt32NoTag(rawValue); + unknownFields.mergeVarintField(fieldNumber, rawValue); return true; } break; @@ -768,7 +802,8 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite * this type as parameters to extension accessors and ExtensionRegistry.add(). */ public static class GeneratedExtension< - ContainingType extends MessageLite, Type> { + ContainingType extends MessageLite, Type> + extends ExtensionLite { /** * Create a new isntance with the given parameters. @@ -888,6 +923,18 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite return value; } } + + public FieldType getLiteType() { + return descriptor.getLiteType(); + } + + public boolean isRepeated() { + return descriptor.isRepeated; + } + + public Type getDefaultValue() { + return defaultValue; + } } /** @@ -897,8 +944,8 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite static final class SerializedForm implements Serializable { private static final long serialVersionUID = 0L; - private String messageClassName; - private byte[] asBytes; + private final String messageClassName; + private final byte[] asBytes; /** * Creates the serialized form by calling {@link com.google.protobuf.MessageLite#toByteArray}. @@ -918,19 +965,17 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite protected Object readResolve() throws ObjectStreamException { try { Class messageClass = Class.forName(messageClassName); - Method newBuilder = messageClass.getMethod("newBuilder"); - MessageLite.Builder builder = - (MessageLite.Builder) newBuilder.invoke(null); - builder.mergeFrom(asBytes); - return builder.buildPartial(); + Parser parser = + (Parser) messageClass.getField("PARSER").get(null); + return parser.parsePartialFrom(asBytes); } catch (ClassNotFoundException e) { throw new RuntimeException("Unable to find proto buffer class", e); - } catch (NoSuchMethodException e) { - throw new RuntimeException("Unable to find newBuilder method", e); + } catch (NoSuchFieldException e) { + throw new RuntimeException("Unable to find PARSER", e); + } catch (SecurityException e) { + throw new RuntimeException("Unable to call PARSER", e); } catch (IllegalAccessException e) { - throw new RuntimeException("Unable to call newBuilder method", e); - } catch (InvocationTargetException e) { - throw new RuntimeException("Error calling newBuilder", e.getCause()); + throw new RuntimeException("Unable to call parseFrom method", e); } catch (InvalidProtocolBufferException e) { throw new RuntimeException("Unable to understand proto buffer", e); } @@ -946,4 +991,18 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite protected Object writeReplace() throws ObjectStreamException { return new SerializedForm(this); } + + /** + * Checks that the {@link Extension} is Lite and returns it as a + * {@link GeneratedExtension}. + */ + private static , T> + GeneratedExtension checkIsLite( + ExtensionLite extension) { + if (!extension.isLite()) { + throw new IllegalArgumentException("Expected a lite extension."); + } + + return (GeneratedExtension) extension; + } } diff --git a/java/src/main/java/com/google/protobuf/Internal.java b/java/src/main/java/com/google/protobuf/Internal.java index 48d29e69..ba0f526b 100644 --- a/java/src/main/java/com/google/protobuf/Internal.java +++ b/java/src/main/java/com/google/protobuf/Internal.java @@ -33,8 +33,14 @@ package com.google.protobuf; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; +import java.util.AbstractList; +import java.util.AbstractMap; +import java.util.AbstractSet; import java.util.Arrays; +import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.Set; /** * The classes contained within are used internally by the Protocol Buffer @@ -388,4 +394,164 @@ public class Internal { public static final ByteBuffer EMPTY_BYTE_BUFFER = ByteBuffer.wrap(EMPTY_BYTE_ARRAY); + + /** + * Provides an immutable view of List around a List. + * + * Protobuf internal. Used in protobuf generated code only. + */ + public static class ListAdapter extends AbstractList { + /** + * Convert individual elements of the List from F to T. + */ + public interface Converter { + T convert(F from); + } + + private final List fromList; + private final Converter converter; + + public ListAdapter(List fromList, Converter converter) { + this.fromList = fromList; + this.converter = converter; + } + + @Override + public T get(int index) { + return converter.convert(fromList.get(index)); + } + + @Override + public int size() { + return fromList.size(); + } + } + + /** + * Wrap around a Map and provide a Map interface. + */ + public static class MapAdapter extends AbstractMap { + /** + * An interface used to convert between two types. + */ + public interface Converter { + B doForward(A object); + A doBackward(B object); + } + + public static Converter newEnumConverter( + final EnumLiteMap enumMap, final T unrecognizedValue) { + return new Converter() { + public T doForward(Integer value) { + T result = enumMap.findValueByNumber(value); + return result == null ? unrecognizedValue : result; + } + public Integer doBackward(T value) { + return value.getNumber(); + } + }; + } + + private final Map realMap; + private final Converter valueConverter; + + public MapAdapter(Map realMap, + Converter valueConverter) { + this.realMap = realMap; + this.valueConverter = valueConverter; + } + + @SuppressWarnings("unchecked") + @Override + public V get(Object key) { + RealValue result = realMap.get(key); + if (result == null) { + return null; + } + return valueConverter.doForward(result); + } + + @Override + public V put(K key, V value) { + RealValue oldValue = realMap.put(key, valueConverter.doBackward(value)); + if (oldValue == null) { + return null; + } + return valueConverter.doForward(oldValue); + } + + @Override + public Set> entrySet() { + return new SetAdapter(realMap.entrySet()); + } + + private class SetAdapter extends AbstractSet> { + private final Set> realSet; + public SetAdapter(Set> realSet) { + this.realSet = realSet; + } + + @Override + public Iterator> iterator() { + return new IteratorAdapter(realSet.iterator()); + } + + @Override + public int size() { + return realSet.size(); + } + } + + private class IteratorAdapter implements Iterator> { + private final Iterator> realIterator; + + public IteratorAdapter( + Iterator> realIterator) { + this.realIterator = realIterator; + } + + @Override + public boolean hasNext() { + return realIterator.hasNext(); + } + + @Override + public java.util.Map.Entry next() { + return new EntryAdapter(realIterator.next()); + } + + @Override + public void remove() { + realIterator.remove(); + } + } + + private class EntryAdapter implements Map.Entry { + private final Map.Entry realEntry; + + public EntryAdapter(Map.Entry realEntry) { + this.realEntry = realEntry; + } + + @Override + public K getKey() { + return realEntry.getKey(); + } + + @Override + public V getValue() { + return valueConverter.doForward(realEntry.getValue()); + } + + @Override + public V setValue(V value) { + RealValue oldValue = realEntry.setValue( + valueConverter.doBackward(value)); + if (oldValue == null) { + return null; + } + return valueConverter.doForward(oldValue); + } + } + } } diff --git a/java/src/main/java/com/google/protobuf/LazyStringArrayList.java b/java/src/main/java/com/google/protobuf/LazyStringArrayList.java index 61c7e1ea..2d40a51f 100644 --- a/java/src/main/java/com/google/protobuf/LazyStringArrayList.java +++ b/java/src/main/java/com/google/protobuf/LazyStringArrayList.java @@ -74,6 +74,10 @@ public class LazyStringArrayList extends AbstractList list = new ArrayList(); } + public LazyStringArrayList(int intialCapacity) { + list = new ArrayList(intialCapacity); + } + public LazyStringArrayList(LazyStringList from) { list = new ArrayList(from.size()); addAll(from); diff --git a/java/src/main/java/com/google/protobuf/LiteralByteString.java b/java/src/main/java/com/google/protobuf/LiteralByteString.java index 83e71e93..127c574d 100644 --- a/java/src/main/java/com/google/protobuf/LiteralByteString.java +++ b/java/src/main/java/com/google/protobuf/LiteralByteString.java @@ -190,6 +190,15 @@ class LiteralByteString extends ByteString { } if (other instanceof LiteralByteString) { + LiteralByteString otherAsLiteral = (LiteralByteString) other; + // If we know the hash codes and they are not equal, we know the byte + // strings are not equal. + if (hash != 0 + && otherAsLiteral.hash != 0 + && hash != otherAsLiteral.hash) { + return false; + } + return equalsRange((LiteralByteString) other, 0, size()); } else if (other instanceof RopeByteString) { return other.equals(this); diff --git a/java/src/main/java/com/google/protobuf/MapEntry.java b/java/src/main/java/com/google/protobuf/MapEntry.java new file mode 100644 index 00000000..8e3551b6 --- /dev/null +++ b/java/src/main/java/com/google/protobuf/MapEntry.java @@ -0,0 +1,433 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// 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 com.google.protobuf.Descriptors.Descriptor; +import com.google.protobuf.Descriptors.EnumValueDescriptor; +import com.google.protobuf.Descriptors.FieldDescriptor; + +import java.io.IOException; +import java.util.Collections; +import java.util.Map; +import java.util.TreeMap; + +/** + * Implements MapEntry messages. + * + * In reflection API, map fields will be treated as repeated message fields and + * each map entry is accessed as a message. This MapEntry class is used to + * represent these map entry messages in reflection API. + * + * Protobuf internal. Users shouldn't use this class. + */ +public final class MapEntry extends AbstractMessage { + private static class Metadata { + public final Descriptor descriptor; + public final MapEntry defaultInstance; + public final AbstractParser> parser; + + public Metadata( + final Descriptor descriptor, final MapEntry defaultInstance) { + this.descriptor = descriptor; + this.defaultInstance = defaultInstance; + final Metadata thisMetadata = this; + this.parser = new AbstractParser>() { + private final Parser> dataParser = + defaultInstance.data.getParserForType(); + @Override + public MapEntry parsePartialFrom( + CodedInputStream input, ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + MapEntryLite data = + dataParser.parsePartialFrom(input, extensionRegistry); + return new MapEntry(thisMetadata, data); + } + + }; + } + } + + private final Metadata metadata; + private final MapEntryLite data; + + /** Create a default MapEntry instance. */ + private MapEntry(Descriptor descriptor, + WireFormat.FieldType keyType, K defaultKey, + WireFormat.FieldType valueType, V defaultValue) { + this.data = MapEntryLite.newDefaultInstance( + keyType, defaultKey, valueType, defaultValue); + this.metadata = new Metadata(descriptor, this); + } + + /** Create a new MapEntry message. */ + private MapEntry(Metadata metadata, MapEntryLite data) { + this.metadata = metadata; + this.data = data; + } + + /** + * Create a default MapEntry instance. A default MapEntry instance should be + * created only once for each map entry message type. Generated code should + * store the created default instance and use it later to create new MapEntry + * messages of the same type. + */ + public static MapEntry newDefaultInstance( + Descriptor descriptor, + WireFormat.FieldType keyType, K defaultKey, + WireFormat.FieldType valueType, V defaultValue) { + return new MapEntry( + descriptor, keyType, defaultKey, valueType, defaultValue); + } + + public K getKey() { + return data.getKey(); + } + + public V getValue() { + return data.getValue(); + } + + @Override + public int getSerializedSize() { + return data.getSerializedSize(); + } + + @Override + public void writeTo(CodedOutputStream output) throws IOException { + data.writeTo(output); + } + + @Override + public boolean isInitialized() { + return data.isInitialized(); + } + + @Override + public Parser> getParserForType() { + return metadata.parser; + } + + @Override + public Builder newBuilderForType() { + return new Builder(metadata); + } + + @Override + public Builder toBuilder() { + return new Builder(metadata, data); + } + + @Override + public MapEntry getDefaultInstanceForType() { + return metadata.defaultInstance; + } + + @Override + public Descriptor getDescriptorForType() { + return metadata.descriptor; + } + + @Override + public Map getAllFields() { + final TreeMap result = + new TreeMap(); + for (final FieldDescriptor field : metadata.descriptor.getFields()) { + if (hasField(field)) { + result.put(field, getField(field)); + } + } + return Collections.unmodifiableMap(result); + } + + private void checkFieldDescriptor(FieldDescriptor field) { + if (field.getContainingType() != metadata.descriptor) { + throw new RuntimeException( + "Wrong FieldDescriptor \"" + field.getFullName() + + "\" used in message \"" + metadata.descriptor.getFullName()); + } + } + + @Override + public boolean hasField(FieldDescriptor field) { + checkFieldDescriptor(field);; + // A MapEntry always contains two fields. + return true; + } + + @Override + public Object getField(FieldDescriptor field) { + checkFieldDescriptor(field); + Object result = field.getNumber() == 1 ? getKey() : getValue(); + // Convert enums to EnumValueDescriptor. + if (field.getType() == FieldDescriptor.Type.ENUM) { + result = field.getEnumType().findValueByNumberCreatingIfUnknown( + (java.lang.Integer) result); + } + return result; + } + + @Override + public int getRepeatedFieldCount(FieldDescriptor field) { + throw new RuntimeException( + "There is no repeated field in a map entry message."); + } + + @Override + public Object getRepeatedField(FieldDescriptor field, int index) { + throw new RuntimeException( + "There is no repeated field in a map entry message."); + } + + @Override + public UnknownFieldSet getUnknownFields() { + return UnknownFieldSet.getDefaultInstance(); + } + + /** + * Builder to create {@link MapEntry} messages. + */ + public static class Builder + extends AbstractMessage.Builder> { + private final Metadata metadata; + private MapEntryLite data; + private MapEntryLite.Builder dataBuilder; + + private Builder(Metadata metadata) { + this.metadata = metadata; + this.data = metadata.defaultInstance.data; + this.dataBuilder = null; + } + + private Builder(Metadata metadata, MapEntryLite data) { + this.metadata = metadata; + this.data = data; + this.dataBuilder = null; + } + + public K getKey() { + return dataBuilder == null ? data.getKey() : dataBuilder.getKey(); + } + + public V getValue() { + return dataBuilder == null ? data.getValue() : dataBuilder.getValue(); + } + + private void ensureMutable() { + if (dataBuilder == null) { + dataBuilder = data.toBuilder(); + } + } + + public Builder setKey(K key) { + ensureMutable(); + dataBuilder.setKey(key); + return this; + } + + public Builder clearKey() { + ensureMutable(); + dataBuilder.clearKey(); + return this; + } + + public Builder setValue(V value) { + ensureMutable(); + dataBuilder.setValue(value); + return this; + } + + public Builder clearValue() { + ensureMutable(); + dataBuilder.clearValue(); + return this; + } + + @Override + public MapEntry build() { + MapEntry result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @Override + public MapEntry buildPartial() { + if (dataBuilder != null) { + data = dataBuilder.build(); + dataBuilder = null; + } + return new MapEntry(metadata, data); + } + + @Override + public Descriptor getDescriptorForType() { + return metadata.descriptor; + } + + private void checkFieldDescriptor(FieldDescriptor field) { + if (field.getContainingType() != metadata.descriptor) { + throw new RuntimeException( + "Wrong FieldDescriptor \"" + field.getFullName() + + "\" used in message \"" + metadata.descriptor.getFullName()); + } + } + + @Override + public com.google.protobuf.Message.Builder newBuilderForField( + FieldDescriptor field) { + checkFieldDescriptor(field);; + // This method should be called for message fields and in a MapEntry + // message only the value field can possibly be a message field. + if (field.getNumber() != 2 + || field.getJavaType() != FieldDescriptor.JavaType.MESSAGE) { + throw new RuntimeException( + "\"" + field.getFullName() + "\" is not a message value field."); + } + return ((Message) data.getValue()).newBuilderForType(); + } + + @SuppressWarnings("unchecked") + @Override + public Builder setField(FieldDescriptor field, Object value) { + checkFieldDescriptor(field); + if (field.getNumber() == 1) { + setKey((K) value); + } else { + if (field.getType() == FieldDescriptor.Type.ENUM) { + value = ((EnumValueDescriptor) value).getNumber(); + } + setValue((V) value); + } + return this; + } + + @Override + public Builder clearField(FieldDescriptor field) { + checkFieldDescriptor(field); + if (field.getNumber() == 1) { + clearKey(); + } else { + clearValue(); + } + return this; + } + + @Override + public Builder setRepeatedField(FieldDescriptor field, int index, + Object value) { + throw new RuntimeException( + "There is no repeated field in a map entry message."); + } + + @Override + public Builder addRepeatedField(FieldDescriptor field, Object value) { + throw new RuntimeException( + "There is no repeated field in a map entry message."); + } + + @Override + public Builder setUnknownFields(UnknownFieldSet unknownFields) { + // Unknown fields are discarded for MapEntry message. + return this; + } + + @Override + public MapEntry getDefaultInstanceForType() { + return metadata.defaultInstance; + } + + @Override + public boolean isInitialized() { + if (dataBuilder != null) { + return dataBuilder.isInitialized(); + } else { + return data.isInitialized(); + } + } + + @Override + public Map getAllFields() { + final TreeMap result = + new TreeMap(); + for (final FieldDescriptor field : metadata.descriptor.getFields()) { + if (hasField(field)) { + result.put(field, getField(field)); + } + } + return Collections.unmodifiableMap(result); + } + + @Override + public boolean hasField(FieldDescriptor field) { + checkFieldDescriptor(field); + return true; + } + + @Override + public Object getField(FieldDescriptor field) { + checkFieldDescriptor(field); + Object result = field.getNumber() == 1 ? getKey() : getValue(); + // Convert enums to EnumValueDescriptor. + if (field.getType() == FieldDescriptor.Type.ENUM) { + result = field.getEnumType().findValueByNumberCreatingIfUnknown( + (java.lang.Integer) result); + } + return result; + } + + @Override + public int getRepeatedFieldCount(FieldDescriptor field) { + throw new RuntimeException( + "There is no repeated field in a map entry message."); + } + + @Override + public Object getRepeatedField(FieldDescriptor field, int index) { + throw new RuntimeException( + "There is no repeated field in a map entry message."); + } + + @Override + public UnknownFieldSet getUnknownFields() { + return UnknownFieldSet.getDefaultInstance(); + } + + @Override + public Builder clone() { + if (dataBuilder == null) { + return new Builder(metadata, data); + } else { + return new Builder(metadata, dataBuilder.build()); + } + } + } +} diff --git a/java/src/main/java/com/google/protobuf/MapEntryLite.java b/java/src/main/java/com/google/protobuf/MapEntryLite.java new file mode 100644 index 00000000..bcffa946 --- /dev/null +++ b/java/src/main/java/com/google/protobuf/MapEntryLite.java @@ -0,0 +1,331 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// 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.io.IOException; + +/** + * Implements the lite version of map entry messages. + * + * This class serves as an utility class to help do serialization/parsing of + * map entries. It's used in generated code and also in the full version + * MapEntry message. + * + * Protobuf internal. Users shouldn't use. + */ +public class MapEntryLite extends AbstractMessageLite { + private static class Metadata { + public final MapEntryLite defaultInstance; + public final WireFormat.FieldType keyType; + public final WireFormat.FieldType valueType; + public final Parser> parser; + public Metadata( + MapEntryLite defaultInstance, + WireFormat.FieldType keyType, + WireFormat.FieldType valueType) { + this.defaultInstance = defaultInstance; + this.keyType = keyType; + this.valueType = valueType; + final Metadata finalThis = this; + this.parser = new AbstractParser>() { + @Override + public MapEntryLite parsePartialFrom( + CodedInputStream input, ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + return new MapEntryLite(finalThis, input, extensionRegistry); + } + }; + } + } + + private static final int KEY_FIELD_NUMBER = 1; + private static final int VALUE_FIELD_NUMBER = 2; + + private final Metadata metadata; + private final K key; + private final V value; + + /** Creates a default MapEntryLite message instance. */ + private MapEntryLite( + WireFormat.FieldType keyType, K defaultKey, + WireFormat.FieldType valueType, V defaultValue) { + this.metadata = new Metadata(this, keyType, valueType); + this.key = defaultKey; + this.value = defaultValue; + } + + /** Creates a new MapEntryLite message. */ + private MapEntryLite(Metadata metadata, K key, V value) { + this.metadata = metadata; + this.key = key; + this.value = value; + } + + public K getKey() { + return key; + } + + public V getValue() { + return value; + } + + /** + * Creates a default MapEntryLite message instance. + * + * This method is used by generated code to create the default instance for + * a map entry message. The created default instance should be used to create + * new map entry messages of the same type. For each map entry message, only + * one default instance should be created. + */ + public static MapEntryLite newDefaultInstance( + WireFormat.FieldType keyType, K defaultKey, + WireFormat.FieldType valueType, V defaultValue) { + return new MapEntryLite( + keyType, defaultKey, valueType, defaultValue); + } + + @Override + public void writeTo(CodedOutputStream output) throws IOException { + writeField(KEY_FIELD_NUMBER, metadata.keyType, key, output); + writeField(VALUE_FIELD_NUMBER, metadata.valueType, value, output); + } + + private void writeField( + int number, WireFormat.FieldType type, Object value, + CodedOutputStream output) throws IOException { + output.writeTag(number, type.getWireType()); + FieldSet.writeElementNoTag(output, type, value); + } + + private volatile int cachedSerializedSize = -1; + @Override + public int getSerializedSize() { + if (cachedSerializedSize != -1) { + return cachedSerializedSize; + } + int size = 0; + size += getFieldSize(KEY_FIELD_NUMBER, metadata.keyType, key); + size += getFieldSize(VALUE_FIELD_NUMBER, metadata.valueType, value); + cachedSerializedSize = size; + return size; + } + + private int getFieldSize( + int number, WireFormat.FieldType type, Object value) { + return CodedOutputStream.computeTagSize(number) + + FieldSet.computeElementSizeNoTag(type, value); + } + + /** Parsing constructor. */ + private MapEntryLite( + Metadata metadata, + CodedInputStream input, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + try { + K key = metadata.defaultInstance.key; + V value = metadata.defaultInstance.value; + while (true) { + int tag = input.readTag(); + if (tag == 0) { + break; + } + if (tag == WireFormat.makeTag( + KEY_FIELD_NUMBER, metadata.keyType.getWireType())) { + key = mergeField( + input, extensionRegistry, metadata.keyType, key); + } else if (tag == WireFormat.makeTag( + VALUE_FIELD_NUMBER, metadata.valueType.getWireType())) { + value = mergeField( + input, extensionRegistry, metadata.valueType, value); + } else { + if (!input.skipField(tag)) { + break; + } + } + } + this.metadata = metadata; + this.key = key; + this.value = value; + } catch (InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (IOException e) { + throw new InvalidProtocolBufferException(e.getMessage()) + .setUnfinishedMessage(this); + } + } + + @SuppressWarnings("unchecked") + private T mergeField( + CodedInputStream input, ExtensionRegistryLite extensionRegistry, + WireFormat.FieldType type, T value) throws IOException { + switch (type) { + case MESSAGE: + MessageLite.Builder subBuilder = ((MessageLite) value).toBuilder(); + input.readMessage(subBuilder, extensionRegistry); + return (T) subBuilder.buildPartial(); + case ENUM: + return (T) (java.lang.Integer) input.readEnum(); + case GROUP: + throw new RuntimeException("Groups are not allowed in maps."); + default: + return (T) FieldSet.readPrimitiveField(input, type, true); + } + } + + @Override + public Parser> getParserForType() { + return metadata.parser; + } + + @Override + public Builder newBuilderForType() { + return new Builder(metadata); + } + + @Override + public Builder toBuilder() { + return new Builder(metadata, key, value); + } + + @Override + public MapEntryLite getDefaultInstanceForType() { + return metadata.defaultInstance; + } + + @Override + public boolean isInitialized() { + if (metadata.valueType.getJavaType() == WireFormat.JavaType.MESSAGE) { + return ((MessageLite) value).isInitialized(); + } + return true; + } + + /** + * Builder used to create {@link MapEntryLite} messages. + */ + public static class Builder + extends AbstractMessageLite.Builder> { + private final Metadata metadata; + private K key; + private V value; + + private Builder(Metadata metadata) { + this.metadata = metadata; + this.key = metadata.defaultInstance.key; + this.value = metadata.defaultInstance.value; + } + + public K getKey() { + return key; + } + + public V getValue() { + return value; + } + + public Builder setKey(K key) { + this.key = key; + return this; + } + + public Builder setValue(V value) { + this.value = value; + return this; + } + + public Builder clearKey() { + this.key = metadata.defaultInstance.key; + return this; + } + + public Builder clearValue() { + this.value = metadata.defaultInstance.value; + return this; + } + + @Override + public Builder clear() { + this.key = metadata.defaultInstance.key; + this.value = metadata.defaultInstance.value; + return this; + } + + @Override + public MapEntryLite build() { + MapEntryLite result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @Override + public MapEntryLite buildPartial() { + return new MapEntryLite(metadata, key, value); + } + + @Override + public MessageLite getDefaultInstanceForType() { + return metadata.defaultInstance; + } + + @Override + public boolean isInitialized() { + if (metadata.valueType.getJavaType() == WireFormat.JavaType.MESSAGE) { + return ((MessageLite) value).isInitialized(); + } + return true; + } + + private Builder(Metadata metadata, K key, V value) { + this.metadata = metadata; + this.key = key; + this.value = value; + } + + @Override + public Builder clone() { + return new Builder(metadata, key, value); + } + + @Override + public Builder mergeFrom( + CodedInputStream input, ExtensionRegistryLite extensionRegistry) + throws IOException { + MapEntryLite entry = + new MapEntryLite(metadata, input, extensionRegistry); + this.key = entry.key; + this.value = entry.value; + return this; + } + } +} diff --git a/java/src/main/java/com/google/protobuf/MapField.java b/java/src/main/java/com/google/protobuf/MapField.java new file mode 100644 index 00000000..5bd70dbb --- /dev/null +++ b/java/src/main/java/com/google/protobuf/MapField.java @@ -0,0 +1,259 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// 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.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Internal representation of map fields in generated messages. + * + * This class supports accessing the map field as a {@link Map} to be used in + * generated API and also supports accessing the field as a {@link List} to be + * used in reflection API. It keeps track of where the data is currently stored + * and do necessary conversions between map and list. + * + * This class is a protobuf implementation detail. Users shouldn't use this + * class directly. + * + * THREAD-SAFETY NOTE: Read-only access is thread-safe. Users can call getMap() + * and getList() concurrently in multiple threads. If write-access is needed, + * all access must be synchronized. + */ +public class MapField { + /** + * Indicates where the data of this map field is currently stored. + * + * MAP: Data is stored in mapData. + * LIST: Data is stored in listData. + * BOTH: mapData and listData have the same data. + * + * When the map field is accessed (through generated API or reflection API), + * it will shift between these 3 modes: + * + * getMap() getList() getMutableMap() getMutableList() + * MAP MAP BOTH MAP LIST + * LIST BOTH LIST MAP LIST + * BOTH BOTH BOTH MAP LIST + * + * As the map field changes its mode, the list/map reference returned in a + * previous method call may be invalidated. + */ + private enum StorageMode {MAP, LIST, BOTH} + + private volatile StorageMode mode; + private Map mapData; + private List listData; + + // Convert between a map entry Message and a key-value pair. + private static interface Converter { + Message convertKeyAndValueToMessage(K key, V value); + void convertMessageToKeyAndValue(Message message, Map map); + + Message getMessageDefaultInstance(); + } + + private static class ImmutableMessageConverter implements Converter { + private final MapEntry defaultEntry; + public ImmutableMessageConverter(MapEntry defaultEntry) { + this.defaultEntry = defaultEntry; + } + + public Message convertKeyAndValueToMessage(K key, V value) { + return defaultEntry.newBuilderForType().setKey(key).setValue(value).build(); + } + + public void convertMessageToKeyAndValue(Message message, Map map) { + MapEntry entry = (MapEntry) message; + map.put(entry.getKey(), entry.getValue()); + } + + public Message getMessageDefaultInstance() { + return defaultEntry; + } + } + + + private final Converter converter; + + private MapField( + Converter converter, + StorageMode mode, + Map mapData, + List listData) { + this.converter = converter; + this.mode = mode; + this.mapData = mapData; + this.listData = listData; + } + + private MapField( + MapEntry defaultEntry, + StorageMode mode, + Map mapData, + List listData) { + this(new ImmutableMessageConverter(defaultEntry), mode, mapData, listData); + } + + + /** Returns an immutable empty MapField. */ + public static MapField emptyMapField( + MapEntry defaultEntry) { + return new MapField( + defaultEntry, StorageMode.MAP, Collections.emptyMap(), null); + } + + + /** Creates a new mutable empty MapField. */ + public static MapField newMapField(MapEntry defaultEntry) { + return new MapField( + defaultEntry, StorageMode.MAP, new HashMap(), null); + } + + + private Message convertKeyAndValueToMessage(K key, V value) { + return converter.convertKeyAndValueToMessage(key, value); + } + + @SuppressWarnings("unchecked") + private void convertMessageToKeyAndValue(Message message, Map map) { + converter.convertMessageToKeyAndValue(message, map); + } + + private List convertMapToList(Map mapData) { + List listData = new ArrayList(); + for (Map.Entry entry : mapData.entrySet()) { + listData.add( + convertKeyAndValueToMessage( + entry.getKey(), entry.getValue())); + } + return listData; + } + + private Map convertListToMap(List listData) { + Map mapData = new HashMap(); + for (Message item : listData) { + convertMessageToKeyAndValue(item, mapData); + } + return mapData; + } + + /** Returns the content of this MapField as a read-only Map. */ + public Map getMap() { + if (mode == StorageMode.LIST) { + synchronized (this) { + if (mode == StorageMode.LIST) { + mapData = convertListToMap(listData); + mode = StorageMode.BOTH; + } + } + } + return Collections.unmodifiableMap(mapData); + } + + /** Gets a mutable Map view of this MapField. */ + public Map getMutableMap() { + if (mode != StorageMode.MAP) { + if (mode == StorageMode.LIST) { + mapData = convertListToMap(listData); + } + listData = null; + mode = StorageMode.MAP; + } + return mapData; + } + + public void mergeFrom(MapField other) { + getMutableMap().putAll(MapFieldLite.copy(other.getMap())); + } + + public void clear() { + mapData = new HashMap(); + mode = StorageMode.MAP; + } + + @SuppressWarnings("unchecked") + @Override + public boolean equals(Object object) { + if (!(object instanceof MapField)) { + return false; + } + MapField other = (MapField) object; + return MapFieldLite.equals(getMap(), other.getMap()); + } + + @Override + public int hashCode() { + return MapFieldLite.calculateHashCodeForMap(getMap()); + } + + /** Returns a deep copy of this MapField. */ + public MapField copy() { + return new MapField( + converter, StorageMode.MAP, MapFieldLite.copy(getMap()), null); + } + + /** Gets the content of this MapField as a read-only List. */ + List getList() { + if (mode == StorageMode.MAP) { + synchronized (this) { + if (mode == StorageMode.MAP) { + listData = convertMapToList(mapData); + mode = StorageMode.BOTH; + } + } + } + return Collections.unmodifiableList(listData); + } + + /** Gets a mutable List view of this MapField. */ + List getMutableList() { + if (mode != StorageMode.LIST) { + if (mode == StorageMode.MAP) { + listData = convertMapToList(mapData); + } + mapData = null; + mode = StorageMode.LIST; + } + return listData; + } + + /** + * Gets the default instance of the message stored in the list view of this + * map field. + */ + Message getMapEntryMessageDefaultInstance() { + return converter.getMessageDefaultInstance(); + } +} diff --git a/java/src/main/java/com/google/protobuf/MapFieldLite.java b/java/src/main/java/com/google/protobuf/MapFieldLite.java new file mode 100644 index 00000000..eea36d9e --- /dev/null +++ b/java/src/main/java/com/google/protobuf/MapFieldLite.java @@ -0,0 +1,182 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// 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.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Internal representation of map fields in generated lite-runtime messages. + * + * This class is a protobuf implementation detail. Users shouldn't use this + * class directly. + */ +public class MapFieldLite { + private Map mapData; + + private MapFieldLite(Map mapData) { + this.mapData = mapData; + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + private static final MapFieldLite EMPTY_MAP_FIELD = + new MapFieldLite(Collections.emptyMap()); + + /** Returns an singleton immutable empty MapFieldLite instance. */ + @SuppressWarnings({"unchecked", "cast"}) + public static MapFieldLite emptyMapField() { + return (MapFieldLite) EMPTY_MAP_FIELD; + } + + /** Creates a new MapFieldLite instance. */ + public static MapFieldLite newMapField() { + return new MapFieldLite(new HashMap()); + } + + /** Gets the content of this MapField as a read-only Map. */ + public Map getMap() { + return Collections.unmodifiableMap(mapData); + } + + /** Gets a mutable Map view of this MapField. */ + public Map getMutableMap() { + return mapData; + } + + public void mergeFrom(MapFieldLite other) { + mapData.putAll(copy(other.mapData)); + } + + public void clear() { + mapData.clear(); + } + + private static boolean equals(Object a, Object b) { + if (a instanceof byte[] && b instanceof byte[]) { + return Arrays.equals((byte[]) a, (byte[]) b); + } + return a.equals(b); + } + + /** + * Checks whether two {@link Map}s are equal. We don't use the default equals + * method of {@link Map} because it compares by identity not by content for + * byte arrays. + */ + static boolean equals(Map a, Map b) { + if (a == b) { + return true; + } + if (a.size() != a.size()) { + return false; + } + for (Map.Entry entry : a.entrySet()) { + if (!b.containsKey(entry.getKey())) { + return false; + } + if (!equals(entry.getValue(), b.get(entry.getKey()))) { + return false; + } + } + return true; + } + + /** + * Checks whether two map fields are equal. + */ + @SuppressWarnings("unchecked") + @Override + public boolean equals(Object object) { + if (!(object instanceof MapFieldLite)) { + return false; + } + MapFieldLite other = (MapFieldLite) object; + return equals(mapData, other.mapData); + } + + private static int calculateHashCodeForObject(Object a) { + if (a instanceof byte[]) { + return LiteralByteString.hashCode((byte[]) a); + } + if (a instanceof Internal.EnumLite) { + return Internal.hashEnum((Internal.EnumLite) a); + } + return a.hashCode(); + } + + /** + * Calculates the hash code for a {@link Map}. We don't use the default hash + * code method of {@link Map} because for byte arrays and protobuf enums it + * use {@link Object#hashCode()}. + */ + static int calculateHashCodeForMap(Map a) { + int result = 0; + for (Map.Entry entry : a.entrySet()) { + result += calculateHashCodeForObject(entry.getKey()) + ^ calculateHashCodeForObject(entry.getValue()); + } + return result; + } + + @Override + public int hashCode() { + return calculateHashCodeForMap(mapData); + } + + private static Object copy(Object object) { + if (object instanceof byte[]) { + byte[] data = (byte[]) object; + return Arrays.copyOf(data, data.length); + } + return object; + } + + /** + * Makes a deep copy of a {@link Map}. Immutable objects in the map will be + * shared (e.g., integers, strings, immutable messages) and mutable ones will + * have a copy (e.g., byte arrays, mutable messages). + */ + @SuppressWarnings("unchecked") + static Map copy(Map map) { + Map result = new HashMap(); + for (Map.Entry entry : map.entrySet()) { + result.put(entry.getKey(), (V) copy(entry.getValue())); + } + return result; + } + + /** Returns a deep copy of this map field. */ + public MapFieldLite copy() { + return new MapFieldLite(copy(mapData)); + } +} diff --git a/java/src/main/java/com/google/protobuf/Message.java b/java/src/main/java/com/google/protobuf/Message.java index 5673d3bb..5c75b58b 100644 --- a/java/src/main/java/com/google/protobuf/Message.java +++ b/java/src/main/java/com/google/protobuf/Message.java @@ -167,6 +167,25 @@ public interface Message extends MessageLite, MessageOrBuilder { */ Builder getFieldBuilder(Descriptors.FieldDescriptor field); + /** + * Get a nested builder instance for the given repeated field instance. + *

+ * Normally, we hold a reference to the immutable message object for the + * message type field. Some implementations(the generated message builders), + * however, can also hold a reference to the builder object (a nested + * builder) for the field. + *

+ * If the field is already backed up by a nested builder, the nested builder + * will be returned. Otherwise, a new field builder will be created and + * returned. The original message field (if exist) will be merged into the + * field builder, which will then be nested into its parent builder. + *

+ * NOTE: implementations that do not support nested builders will throw + * UnsupportedException. + */ + Builder getRepeatedFieldBuilder(Descriptors.FieldDescriptor field, + int index); + /** * Sets a field to the given value. The value must be of the correct type * for this field, i.e. the same type that diff --git a/java/src/main/java/com/google/protobuf/MessageReflection.java b/java/src/main/java/com/google/protobuf/MessageReflection.java index edb5b5c2..06e3c99b 100644 --- a/java/src/main/java/com/google/protobuf/MessageReflection.java +++ b/java/src/main/java/com/google/protobuf/MessageReflection.java @@ -752,13 +752,18 @@ class MessageReflection { if (field.getLiteType() == WireFormat.FieldType.ENUM) { while (input.getBytesUntilLimit() > 0) { final int rawValue = input.readEnum(); - final 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; + if (field.getFile().supportsUnknownEnumValue()) { + target.addRepeatedField(field, + field.getEnumType().findValueByNumberCreatingIfUnknown(rawValue)); + } else { + final 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; + } + target.addRepeatedField(field, value); } - target.addRepeatedField(field, value); } } else { while (input.getBytesUntilLimit() > 0) { @@ -783,12 +788,16 @@ class MessageReflection { } case ENUM: final 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; + if (field.getFile().supportsUnknownEnumValue()) { + value = field.getEnumType().findValueByNumberCreatingIfUnknown(rawValue); + } else { + 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: diff --git a/java/src/main/java/com/google/protobuf/RepeatedFieldBuilder.java b/java/src/main/java/com/google/protobuf/RepeatedFieldBuilder.java index 22760d3a..63535ac8 100644 --- a/java/src/main/java/com/google/protobuf/RepeatedFieldBuilder.java +++ b/java/src/main/java/com/google/protobuf/RepeatedFieldBuilder.java @@ -367,22 +367,28 @@ public class RepeatedFieldBuilder throw new NullPointerException(); } } + + // If we can inspect the size, we can more efficiently add messages. + int size = -1; if (values instanceof Collection) { @SuppressWarnings("unchecked") final Collection collection = (Collection) values; if (collection.size() == 0) { return this; } - ensureMutableMessageList(); - for (MType value : values) { - addMessage(value); - } - } else { - ensureMutableMessageList(); - for (MType value : values) { - addMessage(value); - } + size = collection.size(); + } + ensureMutableMessageList(); + + if (size >= 0 && messages instanceof ArrayList) { + ((ArrayList) messages) + .ensureCapacity(messages.size() + size); } + + for (MType value : values) { + addMessage(value); + } + onChanged(); incrementModCounts(); return this; diff --git a/java/src/main/java/com/google/protobuf/TextFormat.java b/java/src/main/java/com/google/protobuf/TextFormat.java index 57d0ca68..44674811 100644 --- a/java/src/main/java/com/google/protobuf/TextFormat.java +++ b/java/src/main/java/com/google/protobuf/TextFormat.java @@ -1211,6 +1211,7 @@ public final class TextFormat { private SingularOverwritePolicy singularOverwritePolicy = SingularOverwritePolicy.ALLOW_SINGULAR_OVERWRITES; + /** * Sets parser behavior when a non-repeated field appears more than once. */ @@ -1418,6 +1419,12 @@ public final class TextFormat { } else { consumeFieldValue(tokenizer, extensionRegistry, target, field, extension); } + + // For historical reasons, fields may optionally be separated by commas or + // semicolons. + if (!tokenizer.tryConsume(";")) { + tokenizer.tryConsume(","); + } } /** @@ -1656,10 +1663,9 @@ public final class TextFormat { case '\'': builder.append("\\\'"); break; case '"' : builder.append("\\\""); break; default: - // Note: Bytes with the high-order bit set should be escaped. Since - // bytes are signed, such bytes will compare less than 0x20, hence - // the following line is correct. - if (b >= 0x20) { + // Only ASCII characters between 0x20 (space) and 0x7e (tilde) are + // printable. Other byte values must be escaped. + if (b >= 0x20 && b <= 0x7e) { builder.append((char) b); } else { builder.append('\\'); diff --git a/java/src/main/java/com/google/protobuf/UnknownFieldSet.java b/java/src/main/java/com/google/protobuf/UnknownFieldSet.java index 99de3732..7cd2250e 100644 --- a/java/src/main/java/com/google/protobuf/UnknownFieldSet.java +++ b/java/src/main/java/com/google/protobuf/UnknownFieldSet.java @@ -431,6 +431,21 @@ public final class UnknownFieldSet implements MessageLite { return this; } + + /** + * Convenience method for merging a length-delimited field. + * + *

For use by generated code only. + */ + public Builder mergeLengthDelimitedField( + final int number, final ByteString value) { + if (number == 0) { + throw new IllegalArgumentException("Zero is not a valid field number."); + } + getFieldBuilder(number).addLengthDelimited(value); + return this; + } + /** Check if the given field number is present in the set. */ public boolean hasField(final int number) { if (number == 0) { diff --git a/java/src/main/java/com/google/protobuf/UnknownFieldSetLite.java b/java/src/main/java/com/google/protobuf/UnknownFieldSetLite.java new file mode 100644 index 00000000..7ea84022 --- /dev/null +++ b/java/src/main/java/com/google/protobuf/UnknownFieldSetLite.java @@ -0,0 +1,297 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// 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.io.IOException; + +/** + * {@code UnknownFieldSetLite} is used to keep track of fields which were seen + * when parsing a protocol message but whose field numbers or types are + * unrecognized. This most frequently occurs when new fields are added to a + * message type and then messages containing those fields are read by old + * software that was compiled before the new types were added. + * + *

For use by generated code only. + * + * @author dweis@google.com (Daniel Weis) + */ +public final class UnknownFieldSetLite { + + private static final UnknownFieldSetLite DEFAULT_INSTANCE = + new UnknownFieldSetLite(ByteString.EMPTY); + + /** + * Get an empty {@code UnknownFieldSetLite}. + * + *

For use by generated code only. + */ + public static UnknownFieldSetLite getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + /** + * Create a new {@link Builder}. + * + *

For use by generated code only. + */ + public static Builder newBuilder() { + return new Builder(); + } + + /** + * Returns an {@code UnknownFieldSetLite} that is the composite of {@code first} and + * {@code second}. + */ + static UnknownFieldSetLite concat(UnknownFieldSetLite first, UnknownFieldSetLite second) { + return new UnknownFieldSetLite(first.byteString.concat(second.byteString)); + } + + /** + * The internal representation of the unknown fields. + */ + private final ByteString byteString; + + /** + * Constructs the {@code UnknownFieldSetLite} as a thin wrapper around {@link ByteString}. + */ + private UnknownFieldSetLite(ByteString byteString) { + this.byteString = byteString; + } + + /** + * Serializes the set and writes it to {@code output}. + * + *

For use by generated code only. + */ + public void writeTo(CodedOutputStream output) throws IOException { + output.writeRawBytes(byteString); + } + + + /** + * Get the number of bytes required to encode this set. + * + *

For use by generated code only. + */ + public int getSerializedSize() { + return byteString.size(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj instanceof UnknownFieldSetLite) { + return byteString.equals(((UnknownFieldSetLite) obj).byteString); + } + + return false; + } + + @Override + public int hashCode() { + return byteString.hashCode(); + } + + /** + * Builder for {@link UnknownFieldSetLite}s. + * + *

Use {@link UnknownFieldSet#newBuilder()} to construct a {@code Builder}. + * + *

For use by generated code only. + */ + public static final class Builder { + + private ByteString.Output byteStringOutput; + private CodedOutputStream output; + private boolean built; + + /** + * Constructs a {@code Builder}. Lazily initialized by + * {@link #ensureInitializedButNotBuilt()}. + */ + private Builder() {} + + /** + * Ensures internal state is initialized for use. + */ + private void ensureInitializedButNotBuilt() { + if (built) { + throw new IllegalStateException("Do not reuse UnknownFieldSetLite Builders."); + } + + if (output == null && byteStringOutput == null) { + byteStringOutput = ByteString.newOutput(100 /* initialCapacity */); + output = CodedOutputStream.newInstance(byteStringOutput); + } + } + + /** + * Parse a single field from {@code input} and merge it into this set. + * + *

For use by generated code only. + * + * @param tag The field's tag number, which was already parsed. + * @return {@code false} if the tag is an end group tag. + */ + public boolean mergeFieldFrom(final int tag, final CodedInputStream input) + throws IOException { + ensureInitializedButNotBuilt(); + + final int fieldNumber = WireFormat.getTagFieldNumber(tag); + switch (WireFormat.getTagWireType(tag)) { + case WireFormat.WIRETYPE_VARINT: + output.writeUInt64(fieldNumber, input.readInt64()); + return true; + case WireFormat.WIRETYPE_FIXED32: + output.writeFixed32(fieldNumber, input.readFixed32()); + return true; + case WireFormat.WIRETYPE_FIXED64: + output.writeFixed64(fieldNumber, input.readFixed64()); + return true; + case WireFormat.WIRETYPE_LENGTH_DELIMITED: + output.writeBytes(fieldNumber, input.readBytes()); + return true; + case WireFormat.WIRETYPE_START_GROUP: + final Builder subBuilder = newBuilder(); + subBuilder.mergeFrom(input); + input.checkLastTagWas( + WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP)); + + output.writeTag(fieldNumber, WireFormat.WIRETYPE_START_GROUP); + subBuilder.build().writeTo(output); + output.writeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP); + return true; + case WireFormat.WIRETYPE_END_GROUP: + return false; + default: + throw InvalidProtocolBufferException.invalidWireType(); + } + } + + /** + * Convenience method for merging a new field containing a single varint + * value. This is used in particular when an unknown enum value is + * encountered. + * + *

For use by generated code only. + */ + public Builder mergeVarintField(int fieldNumber, int value) { + if (fieldNumber == 0) { + throw new IllegalArgumentException("Zero is not a valid field number."); + } + ensureInitializedButNotBuilt(); + try { + output.writeUInt64(fieldNumber, value); + } catch (IOException e) { + // Should never happen. + } + return this; + } + + /** + * Convenience method for merging a length-delimited field. + * + *

For use by generated code only. + */ + public Builder mergeLengthDelimitedField( + final int fieldNumber, final ByteString value) { + if (fieldNumber == 0) { + throw new IllegalArgumentException("Zero is not a valid field number."); + } + ensureInitializedButNotBuilt(); + try { + output.writeBytes(fieldNumber, value); + } catch (IOException e) { + // Should never happen. + } + return this; + } + + /** + * Build the {@link UnknownFieldSetLite} and return it. + * + *

Once {@code build()} has been called, the {@code Builder} will no + * longer be usable. Calling any method after {@code build()} will result + * in undefined behavior and can cause a {@code IllegalStateException} to be + * thrown. + * + *

For use by generated code only. + */ + public UnknownFieldSetLite build() { + if (built) { + throw new IllegalStateException("Do not reuse UnknownFieldSetLite Builders."); + } + + built = true; + + final UnknownFieldSetLite result; + // If we were never initialized, no data was written. + if (output == null) { + result = getDefaultInstance(); + } else { + try { + output.flush(); + } catch (IOException e) { + // Should never happen. + } + ByteString byteString = byteStringOutput.toByteString(); + if (byteString.isEmpty()) { + result = getDefaultInstance(); + } else { + result = new UnknownFieldSetLite(byteString); + } + } + + // Allow for garbage collection. + output = null; + byteStringOutput = null; + return result; + } + + /** + * Parse an entire message from {@code input} and merge its fields into + * this set. + */ + private Builder mergeFrom(final CodedInputStream input) throws IOException { + // Ensures initialization in mergeFieldFrom. + while (true) { + final int tag = input.readTag(); + if (tag == 0 || !mergeFieldFrom(tag, input)) { + break; + } + } + return this; + } + } +} diff --git a/java/src/test/java/com/google/protobuf/CodedInputStreamTest.java b/java/src/test/java/com/google/protobuf/CodedInputStreamTest.java index 3f656e25..18d8142c 100644 --- a/java/src/test/java/com/google/protobuf/CodedInputStreamTest.java +++ b/java/src/test/java/com/google/protobuf/CodedInputStreamTest.java @@ -455,19 +455,19 @@ public class CodedInputStreamTest extends TestCase { } public void testMaliciousRecursion() throws Exception { - ByteString data64 = makeRecursiveMessage(64).toByteString(); - ByteString data65 = makeRecursiveMessage(65).toByteString(); + ByteString data100 = makeRecursiveMessage(100).toByteString(); + ByteString data101 = makeRecursiveMessage(101).toByteString(); - assertMessageDepth(TestRecursiveMessage.parseFrom(data64), 64); + assertMessageDepth(TestRecursiveMessage.parseFrom(data100), 100); try { - TestRecursiveMessage.parseFrom(data65); + TestRecursiveMessage.parseFrom(data101); fail("Should have thrown an exception!"); } catch (InvalidProtocolBufferException e) { // success. } - CodedInputStream input = data64.newCodedInput(); + CodedInputStream input = data100.newCodedInput(); input.setRecursionLimit(8); try { TestRecursiveMessage.parseFrom(input); diff --git a/java/src/test/java/com/google/protobuf/DescriptorsTest.java b/java/src/test/java/com/google/protobuf/DescriptorsTest.java index 0092771b..05832a12 100644 --- a/java/src/test/java/com/google/protobuf/DescriptorsTest.java +++ b/java/src/test/java/com/google/protobuf/DescriptorsTest.java @@ -706,6 +706,12 @@ public class DescriptorsTest extends TestCase { assertTrue(TestMultipleExtensionRanges.getDescriptor().isExtensionNumber(4143)); } + public void testToString() { + assertEquals("protobuf_unittest.TestAllTypes.optional_uint64", + UnittestProto.TestAllTypes.getDescriptor().findFieldByNumber( + UnittestProto.TestAllTypes.OPTIONAL_UINT64_FIELD_NUMBER).toString()); + } + public void testPackedEnumField() throws Exception { FileDescriptorProto fileDescriptorProto = FileDescriptorProto.newBuilder() .setName("foo.proto") diff --git a/java/src/test/java/com/google/protobuf/FieldPresenceTest.java b/java/src/test/java/com/google/protobuf/FieldPresenceTest.java new file mode 100644 index 00000000..acf2b023 --- /dev/null +++ b/java/src/test/java/com/google/protobuf/FieldPresenceTest.java @@ -0,0 +1,363 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// 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 com.google.protobuf.Descriptors.Descriptor; +import com.google.protobuf.Descriptors.FieldDescriptor; +import com.google.protobuf.FieldPresenceTestProto.TestAllTypes; +import com.google.protobuf.FieldPresenceTestProto.TestOptionalFieldsOnly; +import com.google.protobuf.FieldPresenceTestProto.TestRepeatedFieldsOnly; +import protobuf_unittest.UnittestProto; + +import junit.framework.TestCase; + +/** + * Unit tests for protos that doesn't support field presence test for optional + * non-message fields. + */ +public class FieldPresenceTest extends TestCase { + private static boolean hasMethod(Class clazz, String name) { + try { + if (clazz.getMethod(name, new Class[]{}) != null) { + return true; + } else { + return false; + } + } catch (NoSuchMethodException e) { + return false; + } + } + + private static boolean isHasMethodRemoved( + Class classWithFieldPresence, + Class classWithoutFieldPresence, + String camelName) { + return hasMethod(classWithFieldPresence, "get" + camelName) + && hasMethod(classWithFieldPresence, "has" + camelName) + && hasMethod(classWithoutFieldPresence, "get" + camelName) + && !hasMethod(classWithoutFieldPresence, "has" + camelName); + } + + public void testHasMethod() { + // Optional non-message fields don't have a hasFoo() method generated. + assertTrue(isHasMethodRemoved( + UnittestProto.TestAllTypes.class, + TestAllTypes.class, + "OptionalInt32")); + assertTrue(isHasMethodRemoved( + UnittestProto.TestAllTypes.class, + TestAllTypes.class, + "OptionalString")); + assertTrue(isHasMethodRemoved( + UnittestProto.TestAllTypes.class, + TestAllTypes.class, + "OptionalBytes")); + assertTrue(isHasMethodRemoved( + UnittestProto.TestAllTypes.class, + TestAllTypes.class, + "OptionalNestedEnum")); + + assertTrue(isHasMethodRemoved( + UnittestProto.TestAllTypes.Builder.class, + TestAllTypes.Builder.class, + "OptionalInt32")); + assertTrue(isHasMethodRemoved( + UnittestProto.TestAllTypes.Builder.class, + TestAllTypes.Builder.class, + "OptionalString")); + assertTrue(isHasMethodRemoved( + UnittestProto.TestAllTypes.Builder.class, + TestAllTypes.Builder.class, + "OptionalBytes")); + assertTrue(isHasMethodRemoved( + UnittestProto.TestAllTypes.Builder.class, + TestAllTypes.Builder.class, + "OptionalNestedEnum")); + + // message fields still have the hasFoo() method generated. + assertFalse(TestAllTypes.newBuilder().build().hasOptionalNestedMessage()); + assertFalse(TestAllTypes.newBuilder().hasOptionalNestedMessage()); + + // oneof fields don't have hasFoo() methods (even for message types). + assertTrue(isHasMethodRemoved( + UnittestProto.TestAllTypes.class, + TestAllTypes.class, + "OneofUint32")); + assertTrue(isHasMethodRemoved( + UnittestProto.TestAllTypes.class, + TestAllTypes.class, + "OneofString")); + assertTrue(isHasMethodRemoved( + UnittestProto.TestAllTypes.class, + TestAllTypes.class, + "OneofBytes")); + assertTrue(isHasMethodRemoved( + UnittestProto.TestAllTypes.class, + TestAllTypes.class, + "OneofNestedMessage")); + + assertTrue(isHasMethodRemoved( + UnittestProto.TestAllTypes.Builder.class, + TestAllTypes.Builder.class, + "OneofUint32")); + assertTrue(isHasMethodRemoved( + UnittestProto.TestAllTypes.Builder.class, + TestAllTypes.Builder.class, + "OneofString")); + assertTrue(isHasMethodRemoved( + UnittestProto.TestAllTypes.Builder.class, + TestAllTypes.Builder.class, + "OneofBytes")); + assertTrue(isHasMethodRemoved( + UnittestProto.TestAllTypes.Builder.class, + TestAllTypes.Builder.class, + "OneofNestedMessage")); + } + + public void testFieldPresence() { + // Optional non-message fields set to their default value are treated the + // same way as not set. + + // Serialization will ignore such fields. + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + builder.setOptionalInt32(0); + builder.setOptionalString(""); + builder.setOptionalBytes(ByteString.EMPTY); + builder.setOptionalNestedEnum(TestAllTypes.NestedEnum.FOO); + TestAllTypes message = builder.build(); + assertEquals(0, message.getSerializedSize()); + + // mergeFrom() will ignore such fields. + TestAllTypes.Builder a = TestAllTypes.newBuilder(); + a.setOptionalInt32(1); + a.setOptionalString("x"); + a.setOptionalBytes(ByteString.copyFromUtf8("y")); + a.setOptionalNestedEnum(TestAllTypes.NestedEnum.BAR); + TestAllTypes.Builder b = TestAllTypes.newBuilder(); + b.setOptionalInt32(0); + b.setOptionalString(""); + b.setOptionalBytes(ByteString.EMPTY); + b.setOptionalNestedEnum(TestAllTypes.NestedEnum.FOO); + a.mergeFrom(b.build()); + message = a.build(); + assertEquals(1, message.getOptionalInt32()); + assertEquals("x", message.getOptionalString()); + assertEquals(ByteString.copyFromUtf8("y"), message.getOptionalBytes()); + assertEquals(TestAllTypes.NestedEnum.BAR, message.getOptionalNestedEnum()); + + // equals()/hashCode() should produce the same results. + TestAllTypes empty = TestAllTypes.newBuilder().build(); + message = builder.build(); + assertTrue(empty.equals(message)); + assertTrue(message.equals(empty)); + assertEquals(empty.hashCode(), message.hashCode()); + } + + public void testFieldPresenceByReflection() { + Descriptor descriptor = TestAllTypes.getDescriptor(); + FieldDescriptor optionalInt32Field = descriptor.findFieldByName("optional_int32"); + FieldDescriptor optionalStringField = descriptor.findFieldByName("optional_string"); + FieldDescriptor optionalBytesField = descriptor.findFieldByName("optional_bytes"); + FieldDescriptor optionalNestedEnumField = descriptor.findFieldByName("optional_nested_enum"); + + // Field not present. + TestAllTypes message = TestAllTypes.newBuilder().build(); + assertFalse(message.hasField(optionalInt32Field)); + assertFalse(message.hasField(optionalStringField)); + assertFalse(message.hasField(optionalBytesField)); + assertFalse(message.hasField(optionalNestedEnumField)); + assertEquals(0, message.getAllFields().size()); + + // Field set to default value is seen as not present. + message = TestAllTypes.newBuilder() + .setOptionalInt32(0) + .setOptionalString("") + .setOptionalBytes(ByteString.EMPTY) + .setOptionalNestedEnum(TestAllTypes.NestedEnum.FOO) + .build(); + assertFalse(message.hasField(optionalInt32Field)); + assertFalse(message.hasField(optionalStringField)); + assertFalse(message.hasField(optionalBytesField)); + assertFalse(message.hasField(optionalNestedEnumField)); + assertEquals(0, message.getAllFields().size()); + + // Field set to non-default value is seen as present. + message = TestAllTypes.newBuilder() + .setOptionalInt32(1) + .setOptionalString("x") + .setOptionalBytes(ByteString.copyFromUtf8("y")) + .setOptionalNestedEnum(TestAllTypes.NestedEnum.BAR) + .build(); + assertTrue(message.hasField(optionalInt32Field)); + assertTrue(message.hasField(optionalStringField)); + assertTrue(message.hasField(optionalBytesField)); + assertTrue(message.hasField(optionalNestedEnumField)); + assertEquals(4, message.getAllFields().size()); + } + + public void testMessageField() { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + assertFalse(builder.hasOptionalNestedMessage()); + assertFalse(builder.build().hasOptionalNestedMessage()); + + TestAllTypes.NestedMessage.Builder nestedBuilder = + builder.getOptionalNestedMessageBuilder(); + assertTrue(builder.hasOptionalNestedMessage()); + assertTrue(builder.build().hasOptionalNestedMessage()); + + nestedBuilder.setValue(1); + assertEquals(1, builder.build().getOptionalNestedMessage().getValue()); + + builder.clearOptionalNestedMessage(); + assertFalse(builder.hasOptionalNestedMessage()); + assertFalse(builder.build().hasOptionalNestedMessage()); + + // Unlike non-message fields, if we set a message field to its default value (i.e., + // default instance), the field should be seen as present. + builder.setOptionalNestedMessage(TestAllTypes.NestedMessage.getDefaultInstance()); + assertTrue(builder.hasOptionalNestedMessage()); + assertTrue(builder.build().hasOptionalNestedMessage()); + } + + public void testSerializeAndParse() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + builder.setOptionalInt32(1234); + builder.setOptionalString("hello"); + builder.setOptionalNestedMessage(TestAllTypes.NestedMessage.getDefaultInstance()); + // Set an oneof field to its default value and expect it to be serialized (i.e., + // an oneof field set to the default value should be treated as present). + builder.setOneofInt32(0); + ByteString data = builder.build().toByteString(); + + TestAllTypes message = TestAllTypes.parseFrom(data); + assertEquals(1234, message.getOptionalInt32()); + assertEquals("hello", message.getOptionalString()); + // Fields not set will have the default value. + assertEquals(ByteString.EMPTY, message.getOptionalBytes()); + assertEquals(TestAllTypes.NestedEnum.FOO, message.getOptionalNestedEnum()); + // The message field is set despite that it's set with a default instance. + assertTrue(message.hasOptionalNestedMessage()); + assertEquals(0, message.getOptionalNestedMessage().getValue()); + // The oneof field set to its default value is also present. + assertEquals( + TestAllTypes.OneofFieldCase.ONEOF_INT32, message.getOneofFieldCase()); + } + + // Regression test for b/16173397 + // Make sure we haven't screwed up the code generation for repeated fields. + public void testRepeatedFields() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + builder.setOptionalInt32(1234); + builder.setOptionalString("hello"); + builder.setOptionalNestedMessage(TestAllTypes.NestedMessage.getDefaultInstance()); + builder.addRepeatedInt32(4321); + builder.addRepeatedString("world"); + builder.addRepeatedNestedMessage(TestAllTypes.NestedMessage.getDefaultInstance()); + ByteString data = builder.build().toByteString(); + + TestOptionalFieldsOnly optionalOnlyMessage = TestOptionalFieldsOnly.parseFrom(data); + assertEquals(1234, optionalOnlyMessage.getOptionalInt32()); + assertEquals("hello", optionalOnlyMessage.getOptionalString()); + assertTrue(optionalOnlyMessage.hasOptionalNestedMessage()); + assertEquals(0, optionalOnlyMessage.getOptionalNestedMessage().getValue()); + + TestRepeatedFieldsOnly repeatedOnlyMessage = TestRepeatedFieldsOnly.parseFrom(data); + assertEquals(1, repeatedOnlyMessage.getRepeatedInt32Count()); + assertEquals(4321, repeatedOnlyMessage.getRepeatedInt32(0)); + assertEquals(1, repeatedOnlyMessage.getRepeatedStringCount()); + assertEquals("world", repeatedOnlyMessage.getRepeatedString(0)); + assertEquals(1, repeatedOnlyMessage.getRepeatedNestedMessageCount()); + assertEquals(0, repeatedOnlyMessage.getRepeatedNestedMessage(0).getValue()); + } + + public void testIsInitialized() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + + // Test optional proto2 message fields. + UnittestProto.TestRequired.Builder proto2Builder = + builder.getOptionalProto2MessageBuilder(); + assertFalse(builder.isInitialized()); + assertFalse(builder.buildPartial().isInitialized()); + + proto2Builder.setA(1).setB(2).setC(3); + assertTrue(builder.isInitialized()); + assertTrue(builder.buildPartial().isInitialized()); + + // Test oneof proto2 message fields. + proto2Builder = builder.getOneofProto2MessageBuilder(); + assertFalse(builder.isInitialized()); + assertFalse(builder.buildPartial().isInitialized()); + + proto2Builder.setA(1).setB(2).setC(3); + assertTrue(builder.isInitialized()); + assertTrue(builder.buildPartial().isInitialized()); + + // Test repeated proto2 message fields. + proto2Builder = builder.addRepeatedProto2MessageBuilder(); + assertFalse(builder.isInitialized()); + assertFalse(builder.buildPartial().isInitialized()); + + proto2Builder.setA(1).setB(2).setC(3); + assertTrue(builder.isInitialized()); + assertTrue(builder.buildPartial().isInitialized()); + } + + + // Test that unknown fields are dropped. + public void testUnknownFields() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + builder.setOptionalInt32(1234); + builder.addRepeatedInt32(5678); + TestAllTypes message = builder.build(); + ByteString data = message.toByteString(); + + TestOptionalFieldsOnly optionalOnlyMessage = + TestOptionalFieldsOnly.parseFrom(data); + // UnknownFieldSet should be empty. + assertEquals( + 0, optionalOnlyMessage.getUnknownFields().toByteString().size()); + assertEquals(1234, optionalOnlyMessage.getOptionalInt32()); + message = TestAllTypes.parseFrom(optionalOnlyMessage.toByteString()); + assertEquals(1234, message.getOptionalInt32()); + // The repeated field is discarded because it's unknown to the optional-only + // message. + assertEquals(0, message.getRepeatedInt32Count()); + + DynamicMessage dynamicOptionalOnlyMessage = + DynamicMessage.getDefaultInstance( + TestOptionalFieldsOnly.getDescriptor()) + .getParserForType().parseFrom(data); + assertEquals( + 0, dynamicOptionalOnlyMessage.getUnknownFields().toByteString().size()); + assertEquals(optionalOnlyMessage.toByteString(), + dynamicOptionalOnlyMessage.toByteString()); + } +} diff --git a/java/src/test/java/com/google/protobuf/GeneratedMessageTest.java b/java/src/test/java/com/google/protobuf/GeneratedMessageTest.java index 0b3482c3..41ed7bd0 100644 --- a/java/src/test/java/com/google/protobuf/GeneratedMessageTest.java +++ b/java/src/test/java/com/google/protobuf/GeneratedMessageTest.java @@ -157,15 +157,12 @@ public class GeneratedMessageTest extends TestCase { public void testProtosShareRepeatedArraysIfDidntChange() throws Exception { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); builder.addRepeatedInt32(100); - builder.addRepeatedImportEnum(UnittestImport.ImportEnum.IMPORT_BAR); builder.addRepeatedForeignMessage(ForeignMessage.getDefaultInstance()); TestAllTypes value1 = builder.build(); TestAllTypes value2 = value1.toBuilder().build(); assertSame(value1.getRepeatedInt32List(), value2.getRepeatedInt32List()); - assertSame(value1.getRepeatedImportEnumList(), - value2.getRepeatedImportEnumList()); assertSame(value1.getRepeatedForeignMessageList(), value2.getRepeatedForeignMessageList()); } @@ -1512,4 +1509,142 @@ public class GeneratedMessageTest extends TestCase { assertEquals(message2.getFooMessage().getQuxInt(), 234); } } + + public void testGetRepeatedFieldBuilder() { + Descriptor descriptor = TestAllTypes.getDescriptor(); + + FieldDescriptor fieldDescriptor = + descriptor.findFieldByName("repeated_nested_message"); + FieldDescriptor foreignFieldDescriptor = + descriptor.findFieldByName("repeated_foreign_message"); + FieldDescriptor importFieldDescriptor = + descriptor.findFieldByName("repeated_import_message"); + + // Mutate the message with new field builder + // Mutate nested message + TestAllTypes.Builder builder1 = TestAllTypes.newBuilder(); + Message.Builder fieldBuilder1 = builder1.newBuilderForField( + fieldDescriptor); + FieldDescriptor subFieldDescriptor1 = + fieldBuilder1.getDescriptorForType().findFieldByName("bb"); + fieldBuilder1.setField(subFieldDescriptor1, 1); + builder1.addRepeatedField(fieldDescriptor, fieldBuilder1.build()); + + // Mutate foreign message + Message.Builder foreignFieldBuilder1 = builder1.newBuilderForField( + foreignFieldDescriptor); + FieldDescriptor subForeignFieldDescriptor1 = + foreignFieldBuilder1.getDescriptorForType().findFieldByName("c"); + foreignFieldBuilder1.setField(subForeignFieldDescriptor1, 2); + builder1.addRepeatedField(foreignFieldDescriptor, + foreignFieldBuilder1.build()); + + // Mutate import message + Message.Builder importFieldBuilder1 = builder1.newBuilderForField( + importFieldDescriptor); + FieldDescriptor subImportFieldDescriptor1 = + importFieldBuilder1.getDescriptorForType().findFieldByName("d"); + importFieldBuilder1.setField(subImportFieldDescriptor1, 3); + builder1.addRepeatedField(importFieldDescriptor, + importFieldBuilder1.build()); + + Message newMessage1 = builder1.build(); + + // Mutate the message with existing field builder + // Mutate nested message + TestAllTypes.Builder builder2 = TestAllTypes.newBuilder(); + builder2.addRepeatedNestedMessageBuilder(); + Message.Builder fieldBuilder2 = builder2.getRepeatedFieldBuilder( + fieldDescriptor, 0); + FieldDescriptor subFieldDescriptor2 = + fieldBuilder2.getDescriptorForType().findFieldByName("bb"); + fieldBuilder2.setField(subFieldDescriptor2, 1); + + // Mutate foreign message + Message.Builder foreignFieldBuilder2 = builder2.newBuilderForField( + foreignFieldDescriptor); + FieldDescriptor subForeignFieldDescriptor2 = + foreignFieldBuilder2.getDescriptorForType().findFieldByName("c"); + foreignFieldBuilder2.setField(subForeignFieldDescriptor2, 2); + builder2.addRepeatedField(foreignFieldDescriptor, + foreignFieldBuilder2.build()); + + // Mutate import message + Message.Builder importFieldBuilder2 = builder2.newBuilderForField( + importFieldDescriptor); + FieldDescriptor subImportFieldDescriptor2 = + importFieldBuilder2.getDescriptorForType().findFieldByName("d"); + importFieldBuilder2.setField(subImportFieldDescriptor2, 3); + builder2.addRepeatedField(importFieldDescriptor, + importFieldBuilder2.build()); + + Message newMessage2 = builder2.build(); + + // These two messages should be equal. + assertEquals(newMessage1, newMessage2); + } + + public void testGetRepeatedFieldBuilderWithInitializedValue() { + Descriptor descriptor = TestAllTypes.getDescriptor(); + FieldDescriptor fieldDescriptor = + descriptor.findFieldByName("repeated_nested_message"); + + // Before setting field, builder is initialized by default value. + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + builder.addRepeatedNestedMessageBuilder(); + NestedMessage.Builder fieldBuilder = + (NestedMessage.Builder) builder.getRepeatedFieldBuilder(fieldDescriptor, 0); + assertEquals(0, fieldBuilder.getBb()); + + // Setting field value with new field builder instance. + builder = TestAllTypes.newBuilder(); + NestedMessage.Builder newFieldBuilder = + builder.addRepeatedNestedMessageBuilder(); + newFieldBuilder.setBb(2); + // Then get the field builder instance by getRepeatedFieldBuilder(). + fieldBuilder = + (NestedMessage.Builder) builder.getRepeatedFieldBuilder(fieldDescriptor, 0); + // It should contain new value. + assertEquals(2, fieldBuilder.getBb()); + // These two builder should be equal. + assertSame(fieldBuilder, newFieldBuilder); + } + + public void testGetRepeatedFieldBuilderNotSupportedException() { + Descriptor descriptor = TestAllTypes.getDescriptor(); + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + try { + builder.getRepeatedFieldBuilder(descriptor.findFieldByName("repeated_int32"), 0); + fail("Exception was not thrown"); + } catch (UnsupportedOperationException e) { + // We expect this exception. + } + try { + builder.getRepeatedFieldBuilder( + descriptor.findFieldByName("repeated_nested_enum"), 0); + fail("Exception was not thrown"); + } catch (UnsupportedOperationException e) { + // We expect this exception. + } + try { + builder.getRepeatedFieldBuilder(descriptor.findFieldByName("optional_int32"), 0); + fail("Exception was not thrown"); + } catch (UnsupportedOperationException e) { + // We expect this exception. + } + try { + builder.getRepeatedFieldBuilder( + descriptor.findFieldByName("optional_nested_enum"), 0); + fail("Exception was not thrown"); + } catch (UnsupportedOperationException e) { + // We expect this exception. + } + try { + builder.getRepeatedFieldBuilder( + descriptor.findFieldByName("optional_nested_message"), 0); + fail("Exception was not thrown"); + } catch (UnsupportedOperationException e) { + // We expect this exception. + } + } } diff --git a/java/src/test/java/com/google/protobuf/LazyMessageLiteTest.java b/java/src/test/java/com/google/protobuf/LazyMessageLiteTest.java index 00e3a843..9de794fe 100644 --- a/java/src/test/java/com/google/protobuf/LazyMessageLiteTest.java +++ b/java/src/test/java/com/google/protobuf/LazyMessageLiteTest.java @@ -36,8 +36,6 @@ import protobuf_unittest.LazyFieldsLite.LazyNestedInnerMessageLite; import junit.framework.TestCase; -import org.easymock.classextension.EasyMock; - import java.util.ArrayList; /** @@ -52,14 +50,10 @@ public class LazyMessageLiteTest extends TestCase { @Override protected void setUp() throws Exception { super.setUp(); - - originalLazyInnerMessageLiteParser = LazyInnerMessageLite.PARSER; } @Override protected void tearDown() throws Exception { - LazyInnerMessageLite.PARSER = originalLazyInnerMessageLiteParser; - super.tearDown(); } @@ -291,29 +285,4 @@ public class LazyMessageLiteTest extends TestCase { assertEquals(bytes, deserialized.toByteString()); } - - public void testLaziness() throws InvalidProtocolBufferException { - LazyInnerMessageLite inner = LazyInnerMessageLite.newBuilder() - .setNum(2) - .build(); - LazyMessageLite outer = LazyMessageLite.newBuilder() - .setNum(1) - .setInner(inner) - .setOneofInner(inner) - .build(); - ByteString bytes = outer.toByteString(); - - - // The parser for inner / oneofInner message shouldn't be used if - // getInner / getOneofInner is not called. - LazyInnerMessageLite.PARSER = EasyMock.createStrictMock(Parser.class); - - EasyMock.replay(LazyInnerMessageLite.PARSER); - - LazyMessageLite deserialized = LazyMessageLite.parseFrom(bytes); - assertEquals(1, deserialized.getNum()); - assertEquals(421, deserialized.getNumWithDefault()); - - EasyMock.verify(LazyInnerMessageLite.PARSER); - } } diff --git a/java/src/test/java/com/google/protobuf/LiteEqualsAndHashTest.java b/java/src/test/java/com/google/protobuf/LiteEqualsAndHashTest.java index 94f4fcf5..035917c8 100644 --- a/java/src/test/java/com/google/protobuf/LiteEqualsAndHashTest.java +++ b/java/src/test/java/com/google/protobuf/LiteEqualsAndHashTest.java @@ -82,4 +82,27 @@ public class LiteEqualsAndHashTest extends TestCase { BarPrime barPrime = BarPrime.newBuilder().setName("bar").build(); assertFalse(bar.equals(barPrime)); } + + public void testEqualsAndHashCodeWithUnknownFields() throws InvalidProtocolBufferException { + Foo fooWithOnlyValue = Foo.newBuilder() + .setValue(1) + .build(); + + Foo fooWithValueAndExtension = fooWithOnlyValue.toBuilder() + .setValue(1) + .setExtension(Bar.fooExt, Bar.newBuilder() + .setName("name") + .build()) + .build(); + + Foo fooWithValueAndUnknownFields = Foo.parseFrom(fooWithValueAndExtension.toByteArray()); + + assertEqualsAndHashCodeAreFalse(fooWithOnlyValue, fooWithValueAndUnknownFields); + assertEqualsAndHashCodeAreFalse(fooWithValueAndExtension, fooWithValueAndUnknownFields); + } + + private void assertEqualsAndHashCodeAreFalse(Object o1, Object o2) { + assertFalse(o1.equals(o2)); + assertFalse(o1.hashCode() == o2.hashCode()); + } } diff --git a/java/src/test/java/com/google/protobuf/MapForProto2LiteTest.java b/java/src/test/java/com/google/protobuf/MapForProto2LiteTest.java new file mode 100644 index 00000000..22dbeb76 --- /dev/null +++ b/java/src/test/java/com/google/protobuf/MapForProto2LiteTest.java @@ -0,0 +1,277 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// 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 map_lite_test.MapForProto2TestProto.TestMap; +import map_lite_test.MapForProto2TestProto.TestMap.MessageValue; +import map_lite_test.MapForProto2TestProto.TestUnknownEnumValue; + +import junit.framework.TestCase; + +/** + * Unit tests for map fields. + */ +public class MapForProto2LiteTest extends TestCase { + private void setMapValues(TestMap.Builder builder) { + builder.getMutableInt32ToInt32Field().put(1, 11); + builder.getMutableInt32ToInt32Field().put(2, 22); + builder.getMutableInt32ToInt32Field().put(3, 33); + + builder.getMutableInt32ToStringField().put(1, "11"); + builder.getMutableInt32ToStringField().put(2, "22"); + builder.getMutableInt32ToStringField().put(3, "33"); + + builder.getMutableInt32ToBytesField().put(1, TestUtil.toBytes("11")); + builder.getMutableInt32ToBytesField().put(2, TestUtil.toBytes("22")); + builder.getMutableInt32ToBytesField().put(3, TestUtil.toBytes("33")); + + builder.getMutableInt32ToEnumField().put(1, TestMap.EnumValue.FOO); + builder.getMutableInt32ToEnumField().put(2, TestMap.EnumValue.BAR); + builder.getMutableInt32ToEnumField().put(3, TestMap.EnumValue.BAZ); + + builder.getMutableInt32ToMessageField().put( + 1, MessageValue.newBuilder().setValue(11).build()); + builder.getMutableInt32ToMessageField().put( + 2, MessageValue.newBuilder().setValue(22).build()); + builder.getMutableInt32ToMessageField().put( + 3, MessageValue.newBuilder().setValue(33).build()); + + builder.getMutableStringToInt32Field().put("1", 11); + builder.getMutableStringToInt32Field().put("2", 22); + builder.getMutableStringToInt32Field().put("3", 33); + } + + private void assertMapValuesSet(TestMap message) { + assertEquals(3, message.getInt32ToInt32Field().size()); + assertEquals(11, message.getInt32ToInt32Field().get(1).intValue()); + assertEquals(22, message.getInt32ToInt32Field().get(2).intValue()); + assertEquals(33, message.getInt32ToInt32Field().get(3).intValue()); + + assertEquals(3, message.getInt32ToStringField().size()); + assertEquals("11", message.getInt32ToStringField().get(1)); + assertEquals("22", message.getInt32ToStringField().get(2)); + assertEquals("33", message.getInt32ToStringField().get(3)); + + assertEquals(3, message.getInt32ToBytesField().size()); + assertEquals(TestUtil.toBytes("11"), message.getInt32ToBytesField().get(1)); + assertEquals(TestUtil.toBytes("22"), message.getInt32ToBytesField().get(2)); + assertEquals(TestUtil.toBytes("33"), message.getInt32ToBytesField().get(3)); + + assertEquals(3, message.getInt32ToEnumField().size()); + assertEquals(TestMap.EnumValue.FOO, message.getInt32ToEnumField().get(1)); + assertEquals(TestMap.EnumValue.BAR, message.getInt32ToEnumField().get(2)); + assertEquals(TestMap.EnumValue.BAZ, message.getInt32ToEnumField().get(3)); + + assertEquals(3, message.getInt32ToMessageField().size()); + assertEquals(11, message.getInt32ToMessageField().get(1).getValue()); + assertEquals(22, message.getInt32ToMessageField().get(2).getValue()); + assertEquals(33, message.getInt32ToMessageField().get(3).getValue()); + + assertEquals(3, message.getStringToInt32Field().size()); + assertEquals(11, message.getStringToInt32Field().get("1").intValue()); + assertEquals(22, message.getStringToInt32Field().get("2").intValue()); + assertEquals(33, message.getStringToInt32Field().get("3").intValue()); + } + + private void updateMapValues(TestMap.Builder builder) { + builder.getMutableInt32ToInt32Field().put(1, 111); + builder.getMutableInt32ToInt32Field().remove(2); + builder.getMutableInt32ToInt32Field().put(4, 44); + + builder.getMutableInt32ToStringField().put(1, "111"); + builder.getMutableInt32ToStringField().remove(2); + builder.getMutableInt32ToStringField().put(4, "44"); + + builder.getMutableInt32ToBytesField().put(1, TestUtil.toBytes("111")); + builder.getMutableInt32ToBytesField().remove(2); + builder.getMutableInt32ToBytesField().put(4, TestUtil.toBytes("44")); + + builder.getMutableInt32ToEnumField().put(1, TestMap.EnumValue.BAR); + builder.getMutableInt32ToEnumField().remove(2); + builder.getMutableInt32ToEnumField().put(4, TestMap.EnumValue.QUX); + + builder.getMutableInt32ToMessageField().put( + 1, MessageValue.newBuilder().setValue(111).build()); + builder.getMutableInt32ToMessageField().remove(2); + builder.getMutableInt32ToMessageField().put( + 4, MessageValue.newBuilder().setValue(44).build()); + + builder.getMutableStringToInt32Field().put("1", 111); + builder.getMutableStringToInt32Field().remove("2"); + builder.getMutableStringToInt32Field().put("4", 44); + } + + private void assertMapValuesUpdated(TestMap message) { + assertEquals(3, message.getInt32ToInt32Field().size()); + assertEquals(111, message.getInt32ToInt32Field().get(1).intValue()); + assertEquals(33, message.getInt32ToInt32Field().get(3).intValue()); + assertEquals(44, message.getInt32ToInt32Field().get(4).intValue()); + + assertEquals(3, message.getInt32ToStringField().size()); + assertEquals("111", message.getInt32ToStringField().get(1)); + assertEquals("33", message.getInt32ToStringField().get(3)); + assertEquals("44", message.getInt32ToStringField().get(4)); + + assertEquals(3, message.getInt32ToBytesField().size()); + assertEquals(TestUtil.toBytes("111"), message.getInt32ToBytesField().get(1)); + assertEquals(TestUtil.toBytes("33"), message.getInt32ToBytesField().get(3)); + assertEquals(TestUtil.toBytes("44"), message.getInt32ToBytesField().get(4)); + + assertEquals(3, message.getInt32ToEnumField().size()); + assertEquals(TestMap.EnumValue.BAR, message.getInt32ToEnumField().get(1)); + assertEquals(TestMap.EnumValue.BAZ, message.getInt32ToEnumField().get(3)); + assertEquals(TestMap.EnumValue.QUX, message.getInt32ToEnumField().get(4)); + + assertEquals(3, message.getInt32ToMessageField().size()); + assertEquals(111, message.getInt32ToMessageField().get(1).getValue()); + assertEquals(33, message.getInt32ToMessageField().get(3).getValue()); + assertEquals(44, message.getInt32ToMessageField().get(4).getValue()); + + assertEquals(3, message.getStringToInt32Field().size()); + assertEquals(111, message.getStringToInt32Field().get("1").intValue()); + assertEquals(33, message.getStringToInt32Field().get("3").intValue()); + assertEquals(44, message.getStringToInt32Field().get("4").intValue()); + } + + private void assertMapValuesCleared(TestMap message) { + assertEquals(0, message.getInt32ToInt32Field().size()); + assertEquals(0, message.getInt32ToStringField().size()); + assertEquals(0, message.getInt32ToBytesField().size()); + assertEquals(0, message.getInt32ToEnumField().size()); + assertEquals(0, message.getInt32ToMessageField().size()); + assertEquals(0, message.getStringToInt32Field().size()); + } + + public void testGettersAndSetters() throws Exception { + TestMap.Builder builder = TestMap.newBuilder(); + TestMap message = builder.build(); + assertMapValuesCleared(message); + + builder = message.toBuilder(); + setMapValues(builder); + message = builder.build(); + assertMapValuesSet(message); + + builder = message.toBuilder(); + updateMapValues(builder); + message = builder.build(); + assertMapValuesUpdated(message); + + builder = message.toBuilder(); + builder.clear(); + message = builder.build(); + assertMapValuesCleared(message); + } + + public void testSerializeAndParse() throws Exception { + TestMap.Builder builder = TestMap.newBuilder(); + setMapValues(builder); + TestMap message = builder.build(); + assertEquals(message.getSerializedSize(), message.toByteString().size()); + message = TestMap.PARSER.parseFrom(message.toByteString()); + assertMapValuesSet(message); + + builder = message.toBuilder(); + updateMapValues(builder); + message = builder.build(); + assertEquals(message.getSerializedSize(), message.toByteString().size()); + message = TestMap.PARSER.parseFrom(message.toByteString()); + assertMapValuesUpdated(message); + + builder = message.toBuilder(); + builder.clear(); + message = builder.build(); + assertEquals(message.getSerializedSize(), message.toByteString().size()); + message = TestMap.PARSER.parseFrom(message.toByteString()); + assertMapValuesCleared(message); + } + + public void testMergeFrom() throws Exception { + TestMap.Builder builder = TestMap.newBuilder(); + setMapValues(builder); + TestMap message = builder.build(); + + TestMap.Builder other = TestMap.newBuilder(); + other.mergeFrom(message); + assertMapValuesSet(other.build()); + } + + public void testEqualsAndHashCode() throws Exception { + // Test that generated equals() and hashCode() will disregard the order + // of map entries when comparing/hashing map fields. + + // We can't control the order of elements in a HashMap. The best we can do + // here is to add elements in different order. + TestMap.Builder b1 = TestMap.newBuilder(); + b1.getMutableInt32ToInt32Field().put(1, 2); + b1.getMutableInt32ToInt32Field().put(3, 4); + b1.getMutableInt32ToInt32Field().put(5, 6); + TestMap m1 = b1.build(); + + TestMap.Builder b2 = TestMap.newBuilder(); + b2.getMutableInt32ToInt32Field().put(5, 6); + b2.getMutableInt32ToInt32Field().put(1, 2); + b2.getMutableInt32ToInt32Field().put(3, 4); + TestMap m2 = b2.build(); + + assertEquals(m1, m2); + assertEquals(m1.hashCode(), m2.hashCode()); + + // Make sure we did compare map fields. + b2.getMutableInt32ToInt32Field().put(1, 0); + m2 = b2.build(); + assertFalse(m1.equals(m2)); + // Don't check m1.hashCode() != m2.hashCode() because it's not guaranteed + // to be different. + } + + public void testUnknownEnumValues() throws Exception { + TestUnknownEnumValue.Builder builder = + TestUnknownEnumValue.newBuilder(); + builder.getMutableInt32ToInt32Field().put(1, 1); + builder.getMutableInt32ToInt32Field().put(2, 54321); + ByteString data = builder.build().toByteString(); + + TestMap message = TestMap.parseFrom(data); + // Entries with unknown enum values will be stored into UnknownFieldSet so + // there is only one entry in the map. + assertEquals(1, message.getInt32ToEnumField().size()); + assertEquals(TestMap.EnumValue.BAR, message.getInt32ToEnumField().get(1)); + // Serializing and parsing should preserve the unknown entry. + data = message.toByteString(); + TestUnknownEnumValue messageWithUnknownEnums = + TestUnknownEnumValue.parseFrom(data); + assertEquals(2, messageWithUnknownEnums.getInt32ToInt32Field().size()); + assertEquals(1, messageWithUnknownEnums.getInt32ToInt32Field().get(1).intValue()); + assertEquals(54321, messageWithUnknownEnums.getInt32ToInt32Field().get(2).intValue()); + } + +} diff --git a/java/src/test/java/com/google/protobuf/MapForProto2Test.java b/java/src/test/java/com/google/protobuf/MapForProto2Test.java new file mode 100644 index 00000000..d78c0d94 --- /dev/null +++ b/java/src/test/java/com/google/protobuf/MapForProto2Test.java @@ -0,0 +1,488 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// 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 com.google.protobuf.Descriptors.FieldDescriptor; +import map_test.MapForProto2TestProto.TestMap; +import map_test.MapForProto2TestProto.TestMap.MessageValue; +import map_test.MapForProto2TestProto.TestUnknownEnumValue; + +import junit.framework.TestCase; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Unit tests for map fields in proto2 protos. + */ +public class MapForProto2Test extends TestCase { + private void setMapValues(TestMap.Builder builder) { + builder.getMutableInt32ToInt32Field().put(1, 11); + builder.getMutableInt32ToInt32Field().put(2, 22); + builder.getMutableInt32ToInt32Field().put(3, 33); + + builder.getMutableInt32ToStringField().put(1, "11"); + builder.getMutableInt32ToStringField().put(2, "22"); + builder.getMutableInt32ToStringField().put(3, "33"); + + builder.getMutableInt32ToBytesField().put(1, TestUtil.toBytes("11")); + builder.getMutableInt32ToBytesField().put(2, TestUtil.toBytes("22")); + builder.getMutableInt32ToBytesField().put(3, TestUtil.toBytes("33")); + + builder.getMutableInt32ToEnumField().put(1, TestMap.EnumValue.FOO); + builder.getMutableInt32ToEnumField().put(2, TestMap.EnumValue.BAR); + builder.getMutableInt32ToEnumField().put(3, TestMap.EnumValue.BAZ); + + builder.getMutableInt32ToMessageField().put( + 1, MessageValue.newBuilder().setValue(11).build()); + builder.getMutableInt32ToMessageField().put( + 2, MessageValue.newBuilder().setValue(22).build()); + builder.getMutableInt32ToMessageField().put( + 3, MessageValue.newBuilder().setValue(33).build()); + + builder.getMutableStringToInt32Field().put("1", 11); + builder.getMutableStringToInt32Field().put("2", 22); + builder.getMutableStringToInt32Field().put("3", 33); + } + + private void assertMapValuesSet(TestMap message) { + assertEquals(3, message.getInt32ToInt32Field().size()); + assertEquals(11, message.getInt32ToInt32Field().get(1).intValue()); + assertEquals(22, message.getInt32ToInt32Field().get(2).intValue()); + assertEquals(33, message.getInt32ToInt32Field().get(3).intValue()); + + assertEquals(3, message.getInt32ToStringField().size()); + assertEquals("11", message.getInt32ToStringField().get(1)); + assertEquals("22", message.getInt32ToStringField().get(2)); + assertEquals("33", message.getInt32ToStringField().get(3)); + + assertEquals(3, message.getInt32ToBytesField().size()); + assertEquals(TestUtil.toBytes("11"), message.getInt32ToBytesField().get(1)); + assertEquals(TestUtil.toBytes("22"), message.getInt32ToBytesField().get(2)); + assertEquals(TestUtil.toBytes("33"), message.getInt32ToBytesField().get(3)); + + assertEquals(3, message.getInt32ToEnumField().size()); + assertEquals(TestMap.EnumValue.FOO, message.getInt32ToEnumField().get(1)); + assertEquals(TestMap.EnumValue.BAR, message.getInt32ToEnumField().get(2)); + assertEquals(TestMap.EnumValue.BAZ, message.getInt32ToEnumField().get(3)); + + assertEquals(3, message.getInt32ToMessageField().size()); + assertEquals(11, message.getInt32ToMessageField().get(1).getValue()); + assertEquals(22, message.getInt32ToMessageField().get(2).getValue()); + assertEquals(33, message.getInt32ToMessageField().get(3).getValue()); + + assertEquals(3, message.getStringToInt32Field().size()); + assertEquals(11, message.getStringToInt32Field().get("1").intValue()); + assertEquals(22, message.getStringToInt32Field().get("2").intValue()); + assertEquals(33, message.getStringToInt32Field().get("3").intValue()); + } + + private void updateMapValues(TestMap.Builder builder) { + builder.getMutableInt32ToInt32Field().put(1, 111); + builder.getMutableInt32ToInt32Field().remove(2); + builder.getMutableInt32ToInt32Field().put(4, 44); + + builder.getMutableInt32ToStringField().put(1, "111"); + builder.getMutableInt32ToStringField().remove(2); + builder.getMutableInt32ToStringField().put(4, "44"); + + builder.getMutableInt32ToBytesField().put(1, TestUtil.toBytes("111")); + builder.getMutableInt32ToBytesField().remove(2); + builder.getMutableInt32ToBytesField().put(4, TestUtil.toBytes("44")); + + builder.getMutableInt32ToEnumField().put(1, TestMap.EnumValue.BAR); + builder.getMutableInt32ToEnumField().remove(2); + builder.getMutableInt32ToEnumField().put(4, TestMap.EnumValue.QUX); + + builder.getMutableInt32ToMessageField().put( + 1, MessageValue.newBuilder().setValue(111).build()); + builder.getMutableInt32ToMessageField().remove(2); + builder.getMutableInt32ToMessageField().put( + 4, MessageValue.newBuilder().setValue(44).build()); + + builder.getMutableStringToInt32Field().put("1", 111); + builder.getMutableStringToInt32Field().remove("2"); + builder.getMutableStringToInt32Field().put("4", 44); + } + + private void assertMapValuesUpdated(TestMap message) { + assertEquals(3, message.getInt32ToInt32Field().size()); + assertEquals(111, message.getInt32ToInt32Field().get(1).intValue()); + assertEquals(33, message.getInt32ToInt32Field().get(3).intValue()); + assertEquals(44, message.getInt32ToInt32Field().get(4).intValue()); + + assertEquals(3, message.getInt32ToStringField().size()); + assertEquals("111", message.getInt32ToStringField().get(1)); + assertEquals("33", message.getInt32ToStringField().get(3)); + assertEquals("44", message.getInt32ToStringField().get(4)); + + assertEquals(3, message.getInt32ToBytesField().size()); + assertEquals(TestUtil.toBytes("111"), message.getInt32ToBytesField().get(1)); + assertEquals(TestUtil.toBytes("33"), message.getInt32ToBytesField().get(3)); + assertEquals(TestUtil.toBytes("44"), message.getInt32ToBytesField().get(4)); + + assertEquals(3, message.getInt32ToEnumField().size()); + assertEquals(TestMap.EnumValue.BAR, message.getInt32ToEnumField().get(1)); + assertEquals(TestMap.EnumValue.BAZ, message.getInt32ToEnumField().get(3)); + assertEquals(TestMap.EnumValue.QUX, message.getInt32ToEnumField().get(4)); + + assertEquals(3, message.getInt32ToMessageField().size()); + assertEquals(111, message.getInt32ToMessageField().get(1).getValue()); + assertEquals(33, message.getInt32ToMessageField().get(3).getValue()); + assertEquals(44, message.getInt32ToMessageField().get(4).getValue()); + + assertEquals(3, message.getStringToInt32Field().size()); + assertEquals(111, message.getStringToInt32Field().get("1").intValue()); + assertEquals(33, message.getStringToInt32Field().get("3").intValue()); + assertEquals(44, message.getStringToInt32Field().get("4").intValue()); + } + + private void assertMapValuesCleared(TestMap message) { + assertEquals(0, message.getInt32ToInt32Field().size()); + assertEquals(0, message.getInt32ToStringField().size()); + assertEquals(0, message.getInt32ToBytesField().size()); + assertEquals(0, message.getInt32ToEnumField().size()); + assertEquals(0, message.getInt32ToMessageField().size()); + assertEquals(0, message.getStringToInt32Field().size()); + } + + public void testGettersAndSetters() throws Exception { + TestMap.Builder builder = TestMap.newBuilder(); + TestMap message = builder.build(); + assertMapValuesCleared(message); + + builder = message.toBuilder(); + setMapValues(builder); + message = builder.build(); + assertMapValuesSet(message); + + builder = message.toBuilder(); + updateMapValues(builder); + message = builder.build(); + assertMapValuesUpdated(message); + + builder = message.toBuilder(); + builder.clear(); + message = builder.build(); + assertMapValuesCleared(message); + } + + public void testSerializeAndParse() throws Exception { + TestMap.Builder builder = TestMap.newBuilder(); + setMapValues(builder); + TestMap message = builder.build(); + assertEquals(message.getSerializedSize(), message.toByteString().size()); + message = TestMap.PARSER.parseFrom(message.toByteString()); + assertMapValuesSet(message); + + builder = message.toBuilder(); + updateMapValues(builder); + message = builder.build(); + assertEquals(message.getSerializedSize(), message.toByteString().size()); + message = TestMap.PARSER.parseFrom(message.toByteString()); + assertMapValuesUpdated(message); + + builder = message.toBuilder(); + builder.clear(); + message = builder.build(); + assertEquals(message.getSerializedSize(), message.toByteString().size()); + message = TestMap.PARSER.parseFrom(message.toByteString()); + assertMapValuesCleared(message); + } + + public void testMergeFrom() throws Exception { + TestMap.Builder builder = TestMap.newBuilder(); + setMapValues(builder); + TestMap message = builder.build(); + + TestMap.Builder other = TestMap.newBuilder(); + other.mergeFrom(message); + assertMapValuesSet(other.build()); + } + + public void testEqualsAndHashCode() throws Exception { + // Test that generated equals() and hashCode() will disregard the order + // of map entries when comparing/hashing map fields. + + // We can't control the order of elements in a HashMap. The best we can do + // here is to add elements in different order. + TestMap.Builder b1 = TestMap.newBuilder(); + b1.getMutableInt32ToInt32Field().put(1, 2); + b1.getMutableInt32ToInt32Field().put(3, 4); + b1.getMutableInt32ToInt32Field().put(5, 6); + TestMap m1 = b1.build(); + + TestMap.Builder b2 = TestMap.newBuilder(); + b2.getMutableInt32ToInt32Field().put(5, 6); + b2.getMutableInt32ToInt32Field().put(1, 2); + b2.getMutableInt32ToInt32Field().put(3, 4); + TestMap m2 = b2.build(); + + assertEquals(m1, m2); + assertEquals(m1.hashCode(), m2.hashCode()); + + // Make sure we did compare map fields. + b2.getMutableInt32ToInt32Field().put(1, 0); + m2 = b2.build(); + assertFalse(m1.equals(m2)); + // Don't check m1.hashCode() != m2.hashCode() because it's not guaranteed + // to be different. + } + + + // The following methods are used to test reflection API. + + private static FieldDescriptor f(String name) { + return TestMap.getDescriptor().findFieldByName(name); + } + + private static Object getFieldValue(Message mapEntry, String name) { + FieldDescriptor field = mapEntry.getDescriptorForType().findFieldByName(name); + return mapEntry.getField(field); + } + + private static Message.Builder setFieldValue( + Message.Builder mapEntry, String name, Object value) { + FieldDescriptor field = mapEntry.getDescriptorForType().findFieldByName(name); + mapEntry.setField(field, value); + return mapEntry; + } + + private static void assertHasMapValues(Message message, String name, Map values) { + FieldDescriptor field = f(name); + for (Object entry : (List) message.getField(field)) { + Message mapEntry = (Message) entry; + Object key = getFieldValue(mapEntry, "key"); + Object value = getFieldValue(mapEntry, "value"); + assertTrue(values.containsKey(key)); + assertEquals(value, values.get(key)); + } + assertEquals(values.size(), message.getRepeatedFieldCount(field)); + for (int i = 0; i < message.getRepeatedFieldCount(field); i++) { + Message mapEntry = (Message) message.getRepeatedField(field, i); + Object key = getFieldValue(mapEntry, "key"); + Object value = getFieldValue(mapEntry, "value"); + assertTrue(values.containsKey(key)); + assertEquals(value, values.get(key)); + } + } + + private static + Message newMapEntry(Message.Builder builder, String name, KeyType key, ValueType value) { + FieldDescriptor field = builder.getDescriptorForType().findFieldByName(name); + Message.Builder entryBuilder = builder.newBuilderForField(field); + FieldDescriptor keyField = entryBuilder.getDescriptorForType().findFieldByName("key"); + FieldDescriptor valueField = entryBuilder.getDescriptorForType().findFieldByName("value"); + entryBuilder.setField(keyField, key); + entryBuilder.setField(valueField, value); + return entryBuilder.build(); + } + + private static void setMapValues(Message.Builder builder, String name, Map values) { + List entryList = new ArrayList(); + for (Map.Entry entry : values.entrySet()) { + entryList.add(newMapEntry(builder, name, entry.getKey(), entry.getValue())); + } + FieldDescriptor field = builder.getDescriptorForType().findFieldByName(name); + builder.setField(field, entryList); + } + + private static + Map mapForValues( + KeyType key1, ValueType value1, KeyType key2, ValueType value2) { + Map map = new HashMap(); + map.put(key1, value1); + map.put(key2, value2); + return map; + } + + public void testReflectionApi() throws Exception { + // In reflection API, map fields are just repeated message fields. + TestMap.Builder builder = TestMap.newBuilder(); + builder.getMutableInt32ToInt32Field().put(1, 2); + builder.getMutableInt32ToInt32Field().put(3, 4); + builder.getMutableInt32ToMessageField().put( + 11, MessageValue.newBuilder().setValue(22).build()); + builder.getMutableInt32ToMessageField().put( + 33, MessageValue.newBuilder().setValue(44).build()); + TestMap message = builder.build(); + + // Test getField(), getRepeatedFieldCount(), getRepeatedField(). + assertHasMapValues(message, "int32_to_int32_field", + mapForValues(1, 2, 3, 4)); + assertHasMapValues(message, "int32_to_message_field", + mapForValues( + 11, MessageValue.newBuilder().setValue(22).build(), + 33, MessageValue.newBuilder().setValue(44).build())); + + // Test clearField() + builder.clearField(f("int32_to_int32_field")); + builder.clearField(f("int32_to_message_field")); + message = builder.build(); + assertEquals(0, message.getInt32ToInt32Field().size()); + assertEquals(0, message.getInt32ToMessageField().size()); + + // Test setField() + setMapValues(builder, "int32_to_int32_field", + mapForValues(11, 22, 33, 44)); + setMapValues(builder, "int32_to_message_field", + mapForValues( + 111, MessageValue.newBuilder().setValue(222).build(), + 333, MessageValue.newBuilder().setValue(444).build())); + message = builder.build(); + assertEquals(22, message.getInt32ToInt32Field().get(11).intValue()); + assertEquals(44, message.getInt32ToInt32Field().get(33).intValue()); + assertEquals(222, message.getInt32ToMessageField().get(111).getValue()); + assertEquals(444, message.getInt32ToMessageField().get(333).getValue()); + + // Test addRepeatedField + builder.addRepeatedField(f("int32_to_int32_field"), + newMapEntry(builder, "int32_to_int32_field", 55, 66)); + builder.addRepeatedField(f("int32_to_message_field"), + newMapEntry(builder, "int32_to_message_field", 555, + MessageValue.newBuilder().setValue(666).build())); + message = builder.build(); + assertEquals(66, message.getInt32ToInt32Field().get(55).intValue()); + assertEquals(666, message.getInt32ToMessageField().get(555).getValue()); + + // Test addRepeatedField (overriding existing values) + builder.addRepeatedField(f("int32_to_int32_field"), + newMapEntry(builder, "int32_to_int32_field", 55, 55)); + builder.addRepeatedField(f("int32_to_message_field"), + newMapEntry(builder, "int32_to_message_field", 555, + MessageValue.newBuilder().setValue(555).build())); + message = builder.build(); + assertEquals(55, message.getInt32ToInt32Field().get(55).intValue()); + assertEquals(555, message.getInt32ToMessageField().get(555).getValue()); + + // Test setRepeatedField + for (int i = 0; i < builder.getRepeatedFieldCount(f("int32_to_int32_field")); i++) { + Message mapEntry = (Message) builder.getRepeatedField(f("int32_to_int32_field"), i); + int oldKey = ((Integer) getFieldValue(mapEntry, "key")).intValue(); + int oldValue = ((Integer) getFieldValue(mapEntry, "value")).intValue(); + // Swap key with value for each entry. + Message.Builder mapEntryBuilder = mapEntry.toBuilder(); + setFieldValue(mapEntryBuilder, "key", oldValue); + setFieldValue(mapEntryBuilder, "value", oldKey); + builder.setRepeatedField(f("int32_to_int32_field"), i, mapEntryBuilder.build()); + } + message = builder.build(); + assertEquals(11, message.getInt32ToInt32Field().get(22).intValue()); + assertEquals(33, message.getInt32ToInt32Field().get(44).intValue()); + assertEquals(55, message.getInt32ToInt32Field().get(55).intValue()); + } + + public void testTextFormat() throws Exception { + TestMap.Builder builder = TestMap.newBuilder(); + setMapValues(builder); + TestMap message = builder.build(); + + String textData = TextFormat.printToString(message); + + builder = TestMap.newBuilder(); + TextFormat.merge(textData, builder); + message = builder.build(); + + assertMapValuesSet(message); + } + + public void testDynamicMessage() throws Exception { + TestMap.Builder builder = TestMap.newBuilder(); + setMapValues(builder); + TestMap message = builder.build(); + + Message dynamicDefaultInstance = + DynamicMessage.getDefaultInstance(TestMap.getDescriptor()); + Message dynamicMessage = dynamicDefaultInstance + .newBuilderForType().mergeFrom(message.toByteString()).build(); + + assertEquals(message, dynamicMessage); + assertEquals(message.hashCode(), dynamicMessage.hashCode()); + } + + public void testReflectionEqualsAndHashCode() throws Exception { + // Test that generated equals() and hashCode() will disregard the order + // of map entries when comparing/hashing map fields. + + // We use DynamicMessage to test reflection based equals()/hashCode(). + Message dynamicDefaultInstance = + DynamicMessage.getDefaultInstance(TestMap.getDescriptor()); + FieldDescriptor field = f("int32_to_int32_field"); + + Message.Builder b1 = dynamicDefaultInstance.newBuilderForType(); + b1.addRepeatedField(field, newMapEntry(b1, "int32_to_int32_field", 1, 2)); + b1.addRepeatedField(field, newMapEntry(b1, "int32_to_int32_field", 3, 4)); + b1.addRepeatedField(field, newMapEntry(b1, "int32_to_int32_field", 5, 6)); + Message m1 = b1.build(); + + Message.Builder b2 = dynamicDefaultInstance.newBuilderForType(); + b2.addRepeatedField(field, newMapEntry(b2, "int32_to_int32_field", 5, 6)); + b2.addRepeatedField(field, newMapEntry(b2, "int32_to_int32_field", 1, 2)); + b2.addRepeatedField(field, newMapEntry(b2, "int32_to_int32_field", 3, 4)); + Message m2 = b2.build(); + + assertEquals(m1, m2); + assertEquals(m1.hashCode(), m2.hashCode()); + + // Make sure we did compare map fields. + b2.setRepeatedField(field, 0, newMapEntry(b1, "int32_to_int32_field", 0, 0)); + m2 = b2.build(); + assertFalse(m1.equals(m2)); + // Don't check m1.hashCode() != m2.hashCode() because it's not guaranteed + // to be different. + } + + public void testUnknownEnumValues() throws Exception { + TestUnknownEnumValue.Builder builder = + TestUnknownEnumValue.newBuilder(); + builder.getMutableInt32ToInt32Field().put(1, 1); + builder.getMutableInt32ToInt32Field().put(2, 54321); + ByteString data = builder.build().toByteString(); + + TestMap message = TestMap.parseFrom(data); + // Entries with unknown enum values will be stored into UnknownFieldSet so + // there is only one entry in the map. + assertEquals(1, message.getInt32ToEnumField().size()); + assertEquals(TestMap.EnumValue.BAR, message.getInt32ToEnumField().get(1)); + // UnknownFieldSet should not be empty. + assertFalse(message.getUnknownFields().asMap().isEmpty()); + // Serializing and parsing should preserve the unknown entry. + data = message.toByteString(); + TestUnknownEnumValue messageWithUnknownEnums = + TestUnknownEnumValue.parseFrom(data); + assertEquals(2, messageWithUnknownEnums.getInt32ToInt32Field().size()); + assertEquals(1, messageWithUnknownEnums.getInt32ToInt32Field().get(1).intValue()); + assertEquals(54321, messageWithUnknownEnums.getInt32ToInt32Field().get(2).intValue()); + } + +} diff --git a/java/src/test/java/com/google/protobuf/MapTest.java b/java/src/test/java/com/google/protobuf/MapTest.java new file mode 100644 index 00000000..542a20e7 --- /dev/null +++ b/java/src/test/java/com/google/protobuf/MapTest.java @@ -0,0 +1,569 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// 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 com.google.protobuf.Descriptors.Descriptor; +import com.google.protobuf.Descriptors.EnumDescriptor; +import com.google.protobuf.Descriptors.EnumValueDescriptor; +import com.google.protobuf.Descriptors.FieldDescriptor; +import map_test.MapTestProto.TestMap; +import map_test.MapTestProto.TestMap.MessageValue; +import map_test.MapTestProto.TestOnChangeEventPropagation; + +import junit.framework.TestCase; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Unit tests for map fields. + */ +public class MapTest extends TestCase { + private void setMapValues(TestMap.Builder builder) { + builder.getMutableInt32ToInt32Field().put(1, 11); + builder.getMutableInt32ToInt32Field().put(2, 22); + builder.getMutableInt32ToInt32Field().put(3, 33); + + builder.getMutableInt32ToStringField().put(1, "11"); + builder.getMutableInt32ToStringField().put(2, "22"); + builder.getMutableInt32ToStringField().put(3, "33"); + + builder.getMutableInt32ToBytesField().put(1, TestUtil.toBytes("11")); + builder.getMutableInt32ToBytesField().put(2, TestUtil.toBytes("22")); + builder.getMutableInt32ToBytesField().put(3, TestUtil.toBytes("33")); + + builder.getMutableInt32ToEnumField().put(1, TestMap.EnumValue.FOO); + builder.getMutableInt32ToEnumField().put(2, TestMap.EnumValue.BAR); + builder.getMutableInt32ToEnumField().put(3, TestMap.EnumValue.BAZ); + + builder.getMutableInt32ToMessageField().put( + 1, MessageValue.newBuilder().setValue(11).build()); + builder.getMutableInt32ToMessageField().put( + 2, MessageValue.newBuilder().setValue(22).build()); + builder.getMutableInt32ToMessageField().put( + 3, MessageValue.newBuilder().setValue(33).build()); + + builder.getMutableStringToInt32Field().put("1", 11); + builder.getMutableStringToInt32Field().put("2", 22); + builder.getMutableStringToInt32Field().put("3", 33); + } + + private void assertMapValuesSet(TestMap message) { + assertEquals(3, message.getInt32ToInt32Field().size()); + assertEquals(11, message.getInt32ToInt32Field().get(1).intValue()); + assertEquals(22, message.getInt32ToInt32Field().get(2).intValue()); + assertEquals(33, message.getInt32ToInt32Field().get(3).intValue()); + + assertEquals(3, message.getInt32ToStringField().size()); + assertEquals("11", message.getInt32ToStringField().get(1)); + assertEquals("22", message.getInt32ToStringField().get(2)); + assertEquals("33", message.getInt32ToStringField().get(3)); + + assertEquals(3, message.getInt32ToBytesField().size()); + assertEquals(TestUtil.toBytes("11"), message.getInt32ToBytesField().get(1)); + assertEquals(TestUtil.toBytes("22"), message.getInt32ToBytesField().get(2)); + assertEquals(TestUtil.toBytes("33"), message.getInt32ToBytesField().get(3)); + + assertEquals(3, message.getInt32ToEnumField().size()); + assertEquals(TestMap.EnumValue.FOO, message.getInt32ToEnumField().get(1)); + assertEquals(TestMap.EnumValue.BAR, message.getInt32ToEnumField().get(2)); + assertEquals(TestMap.EnumValue.BAZ, message.getInt32ToEnumField().get(3)); + + assertEquals(3, message.getInt32ToMessageField().size()); + assertEquals(11, message.getInt32ToMessageField().get(1).getValue()); + assertEquals(22, message.getInt32ToMessageField().get(2).getValue()); + assertEquals(33, message.getInt32ToMessageField().get(3).getValue()); + + assertEquals(3, message.getStringToInt32Field().size()); + assertEquals(11, message.getStringToInt32Field().get("1").intValue()); + assertEquals(22, message.getStringToInt32Field().get("2").intValue()); + assertEquals(33, message.getStringToInt32Field().get("3").intValue()); + } + + private void updateMapValues(TestMap.Builder builder) { + builder.getMutableInt32ToInt32Field().put(1, 111); + builder.getMutableInt32ToInt32Field().remove(2); + builder.getMutableInt32ToInt32Field().put(4, 44); + + builder.getMutableInt32ToStringField().put(1, "111"); + builder.getMutableInt32ToStringField().remove(2); + builder.getMutableInt32ToStringField().put(4, "44"); + + builder.getMutableInt32ToBytesField().put(1, TestUtil.toBytes("111")); + builder.getMutableInt32ToBytesField().remove(2); + builder.getMutableInt32ToBytesField().put(4, TestUtil.toBytes("44")); + + builder.getMutableInt32ToEnumField().put(1, TestMap.EnumValue.BAR); + builder.getMutableInt32ToEnumField().remove(2); + builder.getMutableInt32ToEnumField().put(4, TestMap.EnumValue.QUX); + + builder.getMutableInt32ToMessageField().put( + 1, MessageValue.newBuilder().setValue(111).build()); + builder.getMutableInt32ToMessageField().remove(2); + builder.getMutableInt32ToMessageField().put( + 4, MessageValue.newBuilder().setValue(44).build()); + + builder.getMutableStringToInt32Field().put("1", 111); + builder.getMutableStringToInt32Field().remove("2"); + builder.getMutableStringToInt32Field().put("4", 44); + } + + private void assertMapValuesUpdated(TestMap message) { + assertEquals(3, message.getInt32ToInt32Field().size()); + assertEquals(111, message.getInt32ToInt32Field().get(1).intValue()); + assertEquals(33, message.getInt32ToInt32Field().get(3).intValue()); + assertEquals(44, message.getInt32ToInt32Field().get(4).intValue()); + + assertEquals(3, message.getInt32ToStringField().size()); + assertEquals("111", message.getInt32ToStringField().get(1)); + assertEquals("33", message.getInt32ToStringField().get(3)); + assertEquals("44", message.getInt32ToStringField().get(4)); + + assertEquals(3, message.getInt32ToBytesField().size()); + assertEquals(TestUtil.toBytes("111"), message.getInt32ToBytesField().get(1)); + assertEquals(TestUtil.toBytes("33"), message.getInt32ToBytesField().get(3)); + assertEquals(TestUtil.toBytes("44"), message.getInt32ToBytesField().get(4)); + + assertEquals(3, message.getInt32ToEnumField().size()); + assertEquals(TestMap.EnumValue.BAR, message.getInt32ToEnumField().get(1)); + assertEquals(TestMap.EnumValue.BAZ, message.getInt32ToEnumField().get(3)); + assertEquals(TestMap.EnumValue.QUX, message.getInt32ToEnumField().get(4)); + + assertEquals(3, message.getInt32ToMessageField().size()); + assertEquals(111, message.getInt32ToMessageField().get(1).getValue()); + assertEquals(33, message.getInt32ToMessageField().get(3).getValue()); + assertEquals(44, message.getInt32ToMessageField().get(4).getValue()); + + assertEquals(3, message.getStringToInt32Field().size()); + assertEquals(111, message.getStringToInt32Field().get("1").intValue()); + assertEquals(33, message.getStringToInt32Field().get("3").intValue()); + assertEquals(44, message.getStringToInt32Field().get("4").intValue()); + } + + private void assertMapValuesCleared(TestMap message) { + assertEquals(0, message.getInt32ToInt32Field().size()); + assertEquals(0, message.getInt32ToStringField().size()); + assertEquals(0, message.getInt32ToBytesField().size()); + assertEquals(0, message.getInt32ToEnumField().size()); + assertEquals(0, message.getInt32ToMessageField().size()); + assertEquals(0, message.getStringToInt32Field().size()); + } + + public void testGettersAndSetters() throws Exception { + TestMap.Builder builder = TestMap.newBuilder(); + TestMap message = builder.build(); + assertMapValuesCleared(message); + + builder = message.toBuilder(); + setMapValues(builder); + message = builder.build(); + assertMapValuesSet(message); + + builder = message.toBuilder(); + updateMapValues(builder); + message = builder.build(); + assertMapValuesUpdated(message); + + builder = message.toBuilder(); + builder.clear(); + message = builder.build(); + assertMapValuesCleared(message); + } + + public void testSerializeAndParse() throws Exception { + TestMap.Builder builder = TestMap.newBuilder(); + setMapValues(builder); + TestMap message = builder.build(); + assertEquals(message.getSerializedSize(), message.toByteString().size()); + message = TestMap.PARSER.parseFrom(message.toByteString()); + assertMapValuesSet(message); + + builder = message.toBuilder(); + updateMapValues(builder); + message = builder.build(); + assertEquals(message.getSerializedSize(), message.toByteString().size()); + message = TestMap.PARSER.parseFrom(message.toByteString()); + assertMapValuesUpdated(message); + + builder = message.toBuilder(); + builder.clear(); + message = builder.build(); + assertEquals(message.getSerializedSize(), message.toByteString().size()); + message = TestMap.PARSER.parseFrom(message.toByteString()); + assertMapValuesCleared(message); + } + + public void testMergeFrom() throws Exception { + TestMap.Builder builder = TestMap.newBuilder(); + setMapValues(builder); + TestMap message = builder.build(); + + TestMap.Builder other = TestMap.newBuilder(); + other.mergeFrom(message); + assertMapValuesSet(other.build()); + } + + public void testEqualsAndHashCode() throws Exception { + // Test that generated equals() and hashCode() will disregard the order + // of map entries when comparing/hashing map fields. + + // We can't control the order of elements in a HashMap. The best we can do + // here is to add elements in different order. + TestMap.Builder b1 = TestMap.newBuilder(); + b1.getMutableInt32ToInt32Field().put(1, 2); + b1.getMutableInt32ToInt32Field().put(3, 4); + b1.getMutableInt32ToInt32Field().put(5, 6); + TestMap m1 = b1.build(); + + TestMap.Builder b2 = TestMap.newBuilder(); + b2.getMutableInt32ToInt32Field().put(5, 6); + b2.getMutableInt32ToInt32Field().put(1, 2); + b2.getMutableInt32ToInt32Field().put(3, 4); + TestMap m2 = b2.build(); + + assertEquals(m1, m2); + assertEquals(m1.hashCode(), m2.hashCode()); + + // Make sure we did compare map fields. + b2.getMutableInt32ToInt32Field().put(1, 0); + m2 = b2.build(); + assertFalse(m1.equals(m2)); + // Don't check m1.hashCode() != m2.hashCode() because it's not guaranteed + // to be different. + } + + + public void testNestedBuilderOnChangeEventPropagation() { + TestOnChangeEventPropagation.Builder parent = + TestOnChangeEventPropagation.newBuilder(); + parent.getOptionalMessageBuilder().getMutableInt32ToInt32Field().put(1, 2); + TestOnChangeEventPropagation message = parent.build(); + assertEquals(2, message.getOptionalMessage().getInt32ToInt32Field().get(1).intValue()); + + // Make a change using nested builder. + parent.getOptionalMessageBuilder().getMutableInt32ToInt32Field().put(1, 3); + + // Should be able to observe the change. + message = parent.build(); + assertEquals(3, message.getOptionalMessage().getInt32ToInt32Field().get(1).intValue()); + + // Make another change using mergeFrom() + TestMap.Builder other = TestMap.newBuilder(); + other.getMutableInt32ToInt32Field().put(1, 4); + parent.getOptionalMessageBuilder().mergeFrom(other.build()); + + // Should be able to observe the change. + message = parent.build(); + assertEquals(4, message.getOptionalMessage().getInt32ToInt32Field().get(1).intValue()); + + // Make yet another change by clearing the nested builder. + parent.getOptionalMessageBuilder().clear(); + + // Should be able to observe the change. + message = parent.build(); + assertEquals(0, message.getOptionalMessage().getInt32ToInt32Field().size()); + } + + // The following methods are used to test reflection API. + + private static FieldDescriptor f(String name) { + return TestMap.getDescriptor().findFieldByName(name); + } + + private static Object getFieldValue(Message mapEntry, String name) { + FieldDescriptor field = mapEntry.getDescriptorForType().findFieldByName(name); + return mapEntry.getField(field); + } + + private static Message.Builder setFieldValue( + Message.Builder mapEntry, String name, Object value) { + FieldDescriptor field = mapEntry.getDescriptorForType().findFieldByName(name); + mapEntry.setField(field, value); + return mapEntry; + } + + private static void assertHasMapValues(Message message, String name, Map values) { + FieldDescriptor field = f(name); + for (Object entry : (List) message.getField(field)) { + Message mapEntry = (Message) entry; + Object key = getFieldValue(mapEntry, "key"); + Object value = getFieldValue(mapEntry, "value"); + assertTrue(values.containsKey(key)); + assertEquals(value, values.get(key)); + } + assertEquals(values.size(), message.getRepeatedFieldCount(field)); + for (int i = 0; i < message.getRepeatedFieldCount(field); i++) { + Message mapEntry = (Message) message.getRepeatedField(field, i); + Object key = getFieldValue(mapEntry, "key"); + Object value = getFieldValue(mapEntry, "value"); + assertTrue(values.containsKey(key)); + assertEquals(value, values.get(key)); + } + } + + private static + Message newMapEntry(Message.Builder builder, String name, KeyType key, ValueType value) { + FieldDescriptor field = builder.getDescriptorForType().findFieldByName(name); + Message.Builder entryBuilder = builder.newBuilderForField(field); + FieldDescriptor keyField = entryBuilder.getDescriptorForType().findFieldByName("key"); + FieldDescriptor valueField = entryBuilder.getDescriptorForType().findFieldByName("value"); + entryBuilder.setField(keyField, key); + entryBuilder.setField(valueField, value); + return entryBuilder.build(); + } + + private static void setMapValues(Message.Builder builder, String name, Map values) { + List entryList = new ArrayList(); + for (Map.Entry entry : values.entrySet()) { + entryList.add(newMapEntry(builder, name, entry.getKey(), entry.getValue())); + } + FieldDescriptor field = builder.getDescriptorForType().findFieldByName(name); + builder.setField(field, entryList); + } + + private static + Map mapForValues( + KeyType key1, ValueType value1, KeyType key2, ValueType value2) { + Map map = new HashMap(); + map.put(key1, value1); + map.put(key2, value2); + return map; + } + + public void testReflectionApi() throws Exception { + // In reflection API, map fields are just repeated message fields. + TestMap.Builder builder = TestMap.newBuilder(); + builder.getMutableInt32ToInt32Field().put(1, 2); + builder.getMutableInt32ToInt32Field().put(3, 4); + builder.getMutableInt32ToMessageField().put( + 11, MessageValue.newBuilder().setValue(22).build()); + builder.getMutableInt32ToMessageField().put( + 33, MessageValue.newBuilder().setValue(44).build()); + TestMap message = builder.build(); + + // Test getField(), getRepeatedFieldCount(), getRepeatedField(). + assertHasMapValues(message, "int32_to_int32_field", + mapForValues(1, 2, 3, 4)); + assertHasMapValues(message, "int32_to_message_field", + mapForValues( + 11, MessageValue.newBuilder().setValue(22).build(), + 33, MessageValue.newBuilder().setValue(44).build())); + + // Test clearField() + builder.clearField(f("int32_to_int32_field")); + builder.clearField(f("int32_to_message_field")); + message = builder.build(); + assertEquals(0, message.getInt32ToInt32Field().size()); + assertEquals(0, message.getInt32ToMessageField().size()); + + // Test setField() + setMapValues(builder, "int32_to_int32_field", + mapForValues(11, 22, 33, 44)); + setMapValues(builder, "int32_to_message_field", + mapForValues( + 111, MessageValue.newBuilder().setValue(222).build(), + 333, MessageValue.newBuilder().setValue(444).build())); + message = builder.build(); + assertEquals(22, message.getInt32ToInt32Field().get(11).intValue()); + assertEquals(44, message.getInt32ToInt32Field().get(33).intValue()); + assertEquals(222, message.getInt32ToMessageField().get(111).getValue()); + assertEquals(444, message.getInt32ToMessageField().get(333).getValue()); + + // Test addRepeatedField + builder.addRepeatedField(f("int32_to_int32_field"), + newMapEntry(builder, "int32_to_int32_field", 55, 66)); + builder.addRepeatedField(f("int32_to_message_field"), + newMapEntry(builder, "int32_to_message_field", 555, + MessageValue.newBuilder().setValue(666).build())); + message = builder.build(); + assertEquals(66, message.getInt32ToInt32Field().get(55).intValue()); + assertEquals(666, message.getInt32ToMessageField().get(555).getValue()); + + // Test addRepeatedField (overriding existing values) + builder.addRepeatedField(f("int32_to_int32_field"), + newMapEntry(builder, "int32_to_int32_field", 55, 55)); + builder.addRepeatedField(f("int32_to_message_field"), + newMapEntry(builder, "int32_to_message_field", 555, + MessageValue.newBuilder().setValue(555).build())); + message = builder.build(); + assertEquals(55, message.getInt32ToInt32Field().get(55).intValue()); + assertEquals(555, message.getInt32ToMessageField().get(555).getValue()); + + // Test setRepeatedField + for (int i = 0; i < builder.getRepeatedFieldCount(f("int32_to_int32_field")); i++) { + Message mapEntry = (Message) builder.getRepeatedField(f("int32_to_int32_field"), i); + int oldKey = ((Integer) getFieldValue(mapEntry, "key")).intValue(); + int oldValue = ((Integer) getFieldValue(mapEntry, "value")).intValue(); + // Swap key with value for each entry. + Message.Builder mapEntryBuilder = mapEntry.toBuilder(); + setFieldValue(mapEntryBuilder, "key", oldValue); + setFieldValue(mapEntryBuilder, "value", oldKey); + builder.setRepeatedField(f("int32_to_int32_field"), i, mapEntryBuilder.build()); + } + message = builder.build(); + assertEquals(11, message.getInt32ToInt32Field().get(22).intValue()); + assertEquals(33, message.getInt32ToInt32Field().get(44).intValue()); + assertEquals(55, message.getInt32ToInt32Field().get(55).intValue()); + } + + public void testTextFormat() throws Exception { + TestMap.Builder builder = TestMap.newBuilder(); + setMapValues(builder); + TestMap message = builder.build(); + + String textData = TextFormat.printToString(message); + + builder = TestMap.newBuilder(); + TextFormat.merge(textData, builder); + message = builder.build(); + + assertMapValuesSet(message); + } + + public void testDynamicMessage() throws Exception { + TestMap.Builder builder = TestMap.newBuilder(); + setMapValues(builder); + TestMap message = builder.build(); + + Message dynamicDefaultInstance = + DynamicMessage.getDefaultInstance(TestMap.getDescriptor()); + Message dynamicMessage = dynamicDefaultInstance + .newBuilderForType().mergeFrom(message.toByteString()).build(); + + assertEquals(message, dynamicMessage); + assertEquals(message.hashCode(), dynamicMessage.hashCode()); + } + + public void testReflectionEqualsAndHashCode() throws Exception { + // Test that generated equals() and hashCode() will disregard the order + // of map entries when comparing/hashing map fields. + + // We use DynamicMessage to test reflection based equals()/hashCode(). + Message dynamicDefaultInstance = + DynamicMessage.getDefaultInstance(TestMap.getDescriptor()); + FieldDescriptor field = f("int32_to_int32_field"); + + Message.Builder b1 = dynamicDefaultInstance.newBuilderForType(); + b1.addRepeatedField(field, newMapEntry(b1, "int32_to_int32_field", 1, 2)); + b1.addRepeatedField(field, newMapEntry(b1, "int32_to_int32_field", 3, 4)); + b1.addRepeatedField(field, newMapEntry(b1, "int32_to_int32_field", 5, 6)); + Message m1 = b1.build(); + + Message.Builder b2 = dynamicDefaultInstance.newBuilderForType(); + b2.addRepeatedField(field, newMapEntry(b2, "int32_to_int32_field", 5, 6)); + b2.addRepeatedField(field, newMapEntry(b2, "int32_to_int32_field", 1, 2)); + b2.addRepeatedField(field, newMapEntry(b2, "int32_to_int32_field", 3, 4)); + Message m2 = b2.build(); + + assertEquals(m1, m2); + assertEquals(m1.hashCode(), m2.hashCode()); + + // Make sure we did compare map fields. + b2.setRepeatedField(field, 0, newMapEntry(b1, "int32_to_int32_field", 0, 0)); + m2 = b2.build(); + assertFalse(m1.equals(m2)); + // Don't check m1.hashCode() != m2.hashCode() because it's not guaranteed + // to be different. + } + + public void testUnknownEnumValues() throws Exception { + TestMap.Builder builder = TestMap.newBuilder(); + builder.getMutableInt32ToEnumFieldValue().put(0, 0); + builder.getMutableInt32ToEnumFieldValue().put(1, 1); + builder.getMutableInt32ToEnumFieldValue().put(2, 1000); // unknown value. + TestMap message = builder.build(); + + assertEquals(TestMap.EnumValue.FOO, + message.getInt32ToEnumField().get(0)); + assertEquals(TestMap.EnumValue.BAR, + message.getInt32ToEnumField().get(1)); + assertEquals(TestMap.EnumValue.UNRECOGNIZED, + message.getInt32ToEnumField().get(2)); + assertEquals(1000, message.getInt32ToEnumFieldValue().get(2).intValue()); + + // Unknown enum values should be preserved after: + // 1. Serialization and parsing. + // 2. toBuild(). + // 3. mergeFrom(). + message = TestMap.parseFrom(message.toByteString()); + assertEquals(1000, message.getInt32ToEnumFieldValue().get(2).intValue()); + builder = message.toBuilder(); + assertEquals(1000, builder.getInt32ToEnumFieldValue().get(2).intValue()); + builder = TestMap.newBuilder().mergeFrom(message); + assertEquals(1000, builder.getInt32ToEnumFieldValue().get(2).intValue()); + + // hashCode()/equals() should take unknown enum values into account. + builder.getMutableInt32ToEnumFieldValue().put(2, 1001); + TestMap message2 = builder.build(); + assertFalse(message.hashCode() == message2.hashCode()); + assertFalse(message.equals(message2)); + // Unknown values will be converted to UNRECOGNIZED so the resulted enum map + // should be the same. + assertTrue(message.getInt32ToEnumField().equals(message2.getInt32ToEnumField())); + } + + public void testUnknownEnumValuesInReflectionApi() throws Exception { + Descriptor descriptor = TestMap.getDescriptor(); + EnumDescriptor enumDescriptor = TestMap.EnumValue.getDescriptor(); + FieldDescriptor field = descriptor.findFieldByName("int32_to_enum_field"); + + Map data = new HashMap(); + data.put(0, 0); + data.put(1, 1); + data.put(2, 1000); // unknown value. + + TestMap.Builder builder = TestMap.newBuilder(); + for (Map.Entry entry : data.entrySet()) { + builder.getMutableInt32ToEnumFieldValue().put(entry.getKey(), entry.getValue()); + } + + // Try to read unknown enum values using reflection API. + for (int i = 0; i < builder.getRepeatedFieldCount(field); i++) { + Message mapEntry = (Message) builder.getRepeatedField(field, i); + int key = ((Integer) getFieldValue(mapEntry, "key")).intValue(); + int value = ((EnumValueDescriptor) getFieldValue(mapEntry, "value")).getNumber(); + assertEquals(data.get(key).intValue(), value); + Message.Builder mapEntryBuilder = mapEntry.toBuilder(); + // Increase the value by 1. + setFieldValue(mapEntryBuilder, "value", + enumDescriptor.findValueByNumberCreatingIfUnknown(value + 1)); + builder.setRepeatedField(field, i, mapEntryBuilder.build()); + } + + // Verify that enum values have been successfully updated. + TestMap message = builder.build(); + for (Map.Entry entry : message.getInt32ToEnumFieldValue().entrySet()) { + assertEquals(data.get(entry.getKey()) + 1, entry.getValue().intValue()); + } + } +} diff --git a/java/src/test/java/com/google/protobuf/TestBadIdentifiers.java b/java/src/test/java/com/google/protobuf/TestBadIdentifiers.java index 614ac7fe..2c60fe0e 100644 --- a/java/src/test/java/com/google/protobuf/TestBadIdentifiers.java +++ b/java/src/test/java/com/google/protobuf/TestBadIdentifiers.java @@ -72,7 +72,7 @@ public class TestBadIdentifiers extends TestCase { assertEquals(0, message.getMessageField5Count()); assertEquals(0, message.getInt32FieldCount11()); - assertEquals(1, message.getEnumFieldCount12().getNumber()); + assertEquals(0, message.getEnumFieldCount12().getNumber()); assertEquals("", message.getStringFieldCount13()); assertEquals(ByteString.EMPTY, message.getBytesFieldCount14()); assertEquals(0, message.getMessageFieldCount15().getSerializedSize()); diff --git a/java/src/test/java/com/google/protobuf/TextFormatTest.java b/java/src/test/java/com/google/protobuf/TextFormatTest.java index 152bdadc..82f9582f 100644 --- a/java/src/test/java/com/google/protobuf/TextFormatTest.java +++ b/java/src/test/java/com/google/protobuf/TextFormatTest.java @@ -73,7 +73,7 @@ public class TextFormatTest extends TestCase { private static String exoticText = "repeated_int32: -1\n" + "repeated_int32: -2147483648\n" + - "repeated_int64: -1\n" + + "repeated_int64: -1,\n" + "repeated_int64: -9223372036854775808\n" + "repeated_uint32: 4294967295\n" + "repeated_uint32: 2147483648\n" + @@ -101,7 +101,7 @@ public class TextFormatTest extends TestCase { private static String canonicalExoticText = exoticText.replace(": .", ": 0.").replace(": -.", ": -0.") // short-form double - .replace("23e", "23E").replace("E+", "E").replace("0.23E17", "2.3E16"); + .replace("23e", "23E").replace("E+", "E").replace("0.23E17", "2.3E16").replace(",", ""); private String messageSetText = "[protobuf_unittest.TestMessageSetExtension1] {\n" + @@ -119,6 +119,7 @@ public class TextFormatTest extends TestCase { " i: 456\n" + "}\n"; + private final TextFormat.Parser parserWithOverwriteForbidden = TextFormat.Parser.newBuilder() .setSingularOverwritePolicy( @@ -460,6 +461,7 @@ public class TextFormatTest extends TestCase { } } + private void assertParseErrorWithOverwriteForbidden(String error, String text) { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); @@ -553,10 +555,10 @@ public class TextFormatTest extends TestCase { public void testEscape() throws Exception { // Escape sequences. - assertEquals("\\000\\001\\a\\b\\f\\n\\r\\t\\v\\\\\\'\\\"", - TextFormat.escapeBytes(bytes("\0\001\007\b\f\n\r\t\013\\\'\""))); - assertEquals("\\000\\001\\a\\b\\f\\n\\r\\t\\v\\\\\\'\\\"", - TextFormat.escapeText("\0\001\007\b\f\n\r\t\013\\\'\"")); + assertEquals("\\000\\001\\a\\b\\f\\n\\r\\t\\v\\\\\\'\\\"\\177", + TextFormat.escapeBytes(bytes("\0\001\007\b\f\n\r\t\013\\\'\"\177"))); + assertEquals("\\000\\001\\a\\b\\f\\n\\r\\t\\v\\\\\\'\\\"\\177", + TextFormat.escapeText("\0\001\007\b\f\n\r\t\013\\\'\"\177")); assertEquals(bytes("\0\001\007\b\f\n\r\t\013\\\'\""), TextFormat.unescapeBytes("\\000\\001\\a\\b\\f\\n\\r\\t\\v\\\\\\'\\\"")); assertEquals("\0\001\007\b\f\n\r\t\013\\\'\"", @@ -900,6 +902,7 @@ public class TextFormatTest extends TestCase { .build())); } + public void testParseNonRepeatedFields() throws Exception { assertParseSuccessWithOverwriteForbidden( "repeated_int32: 1\n" + diff --git a/java/src/test/java/com/google/protobuf/UnknownEnumValueTest.java b/java/src/test/java/com/google/protobuf/UnknownEnumValueTest.java new file mode 100644 index 00000000..8f45976f --- /dev/null +++ b/java/src/test/java/com/google/protobuf/UnknownEnumValueTest.java @@ -0,0 +1,255 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// 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 com.google.protobuf.Descriptors.Descriptor; +import com.google.protobuf.Descriptors.EnumDescriptor; +import com.google.protobuf.Descriptors.EnumValueDescriptor; +import com.google.protobuf.Descriptors.FieldDescriptor; +import com.google.protobuf.FieldPresenceTestProto.TestAllTypes; +import com.google.protobuf.TextFormat.ParseException; + +import junit.framework.TestCase; + +/** + * Unit tests for protos that keep unknown enum values rather than discard + * them as unknown fields. + */ +public class UnknownEnumValueTest extends TestCase { + public void testUnknownEnumValues() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + builder.setOptionalNestedEnumValue(4321); + builder.addRepeatedNestedEnumValue(5432); + builder.addPackedNestedEnumValue(6543); + TestAllTypes message = builder.build(); + assertEquals(4321, message.getOptionalNestedEnumValue()); + assertEquals(5432, message.getRepeatedNestedEnumValue(0)); + assertEquals(5432, message.getRepeatedNestedEnumValueList().get(0).intValue()); + assertEquals(6543, message.getPackedNestedEnumValue(0)); + // Returns UNRECOGNIZED if an enum type is requested. + assertEquals(TestAllTypes.NestedEnum.UNRECOGNIZED, message.getOptionalNestedEnum()); + assertEquals(TestAllTypes.NestedEnum.UNRECOGNIZED, message.getRepeatedNestedEnum(0)); + assertEquals(TestAllTypes.NestedEnum.UNRECOGNIZED, message.getRepeatedNestedEnumList().get(0)); + assertEquals(TestAllTypes.NestedEnum.UNRECOGNIZED, message.getPackedNestedEnum(0)); + + // Test serialization and parsing. + ByteString data = message.toByteString(); + message = TestAllTypes.parseFrom(data); + assertEquals(4321, message.getOptionalNestedEnumValue()); + assertEquals(5432, message.getRepeatedNestedEnumValue(0)); + assertEquals(5432, message.getRepeatedNestedEnumValueList().get(0).intValue()); + assertEquals(6543, message.getPackedNestedEnumValue(0)); + // Returns UNRECOGNIZED if an enum type is requested. + assertEquals(TestAllTypes.NestedEnum.UNRECOGNIZED, message.getOptionalNestedEnum()); + assertEquals(TestAllTypes.NestedEnum.UNRECOGNIZED, message.getRepeatedNestedEnum(0)); + assertEquals(TestAllTypes.NestedEnum.UNRECOGNIZED, message.getRepeatedNestedEnumList().get(0)); + assertEquals(TestAllTypes.NestedEnum.UNRECOGNIZED, message.getPackedNestedEnum(0)); + + // Test toBuilder(). + builder = message.toBuilder(); + assertEquals(4321, builder.getOptionalNestedEnumValue()); + assertEquals(5432, builder.getRepeatedNestedEnumValue(0)); + assertEquals(5432, builder.getRepeatedNestedEnumValueList().get(0).intValue()); + assertEquals(6543, builder.getPackedNestedEnumValue(0)); + // Returns UNRECOGNIZED if an enum type is requested. + assertEquals(TestAllTypes.NestedEnum.UNRECOGNIZED, builder.getOptionalNestedEnum()); + assertEquals(TestAllTypes.NestedEnum.UNRECOGNIZED, builder.getRepeatedNestedEnum(0)); + assertEquals(TestAllTypes.NestedEnum.UNRECOGNIZED, builder.getRepeatedNestedEnumList().get(0)); + assertEquals(TestAllTypes.NestedEnum.UNRECOGNIZED, builder.getPackedNestedEnum(0)); + + // Test mergeFrom(). + builder = TestAllTypes.newBuilder().mergeFrom(message); + assertEquals(4321, builder.getOptionalNestedEnumValue()); + assertEquals(5432, builder.getRepeatedNestedEnumValue(0)); + assertEquals(5432, builder.getRepeatedNestedEnumValueList().get(0).intValue()); + assertEquals(6543, builder.getPackedNestedEnumValue(0)); + // Returns UNRECOGNIZED if an enum type is requested. + assertEquals(TestAllTypes.NestedEnum.UNRECOGNIZED, builder.getOptionalNestedEnum()); + assertEquals(TestAllTypes.NestedEnum.UNRECOGNIZED, builder.getRepeatedNestedEnum(0)); + assertEquals(TestAllTypes.NestedEnum.UNRECOGNIZED, builder.getRepeatedNestedEnumList().get(0)); + assertEquals(TestAllTypes.NestedEnum.UNRECOGNIZED, builder.getPackedNestedEnum(0)); + + // Test equals() and hashCode() + TestAllTypes sameMessage = builder.build(); + assertEquals(message, sameMessage); + assertEquals(message.hashCode(), sameMessage.hashCode()); + + // Getting the numeric value of UNRECOGNIZED will throw an exception. + try { + TestAllTypes.NestedEnum.UNRECOGNIZED.getNumber(); + fail("Exception is expected."); + } catch (IllegalArgumentException e) { + // Expected. + } + + // Setting an enum field to an UNRECOGNIZED value will throw an exception. + try { + builder.setOptionalNestedEnum(builder.getOptionalNestedEnum()); + fail("Exception is expected."); + } catch (IllegalArgumentException e) { + // Expected. + } + try { + builder.addRepeatedNestedEnum(builder.getOptionalNestedEnum()); + fail("Exception is expected."); + } catch (IllegalArgumentException e) { + // Expected. + } + } + + public void testUnknownEnumValueInReflectionApi() throws Exception { + Descriptor descriptor = TestAllTypes.getDescriptor(); + FieldDescriptor optionalNestedEnumField = descriptor.findFieldByName("optional_nested_enum"); + FieldDescriptor repeatedNestedEnumField = descriptor.findFieldByName("repeated_nested_enum"); + FieldDescriptor packedNestedEnumField = descriptor.findFieldByName("packed_nested_enum"); + EnumDescriptor enumType = TestAllTypes.NestedEnum.getDescriptor(); + + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + builder.setField(optionalNestedEnumField, + enumType.findValueByNumberCreatingIfUnknown(4321)); + builder.addRepeatedField(repeatedNestedEnumField, + enumType.findValueByNumberCreatingIfUnknown(5432)); + builder.addRepeatedField(packedNestedEnumField, + enumType.findValueByNumberCreatingIfUnknown(6543)); + TestAllTypes message = builder.build(); + + // Getters will return unknown enum values as EnumValueDescriptor. + EnumValueDescriptor unknown4321 = + (EnumValueDescriptor) message.getField(optionalNestedEnumField); + EnumValueDescriptor unknown5432 = + (EnumValueDescriptor) message.getRepeatedField(repeatedNestedEnumField, 0); + EnumValueDescriptor unknown6543 = + (EnumValueDescriptor) message.getRepeatedField(packedNestedEnumField, 0); + assertEquals(4321, unknown4321.getNumber()); + assertEquals(5432, unknown5432.getNumber()); + assertEquals(6543, unknown6543.getNumber()); + + // Unknown EnumValueDescriptor will map to UNRECOGNIZED. + assertEquals( + TestAllTypes.NestedEnum.valueOf(unknown4321), + TestAllTypes.NestedEnum.UNRECOGNIZED); + assertEquals( + TestAllTypes.NestedEnum.valueOf(unknown5432), + TestAllTypes.NestedEnum.UNRECOGNIZED); + assertEquals( + TestAllTypes.NestedEnum.valueOf(unknown6543), + TestAllTypes.NestedEnum.UNRECOGNIZED); + + // Setters also accept unknown EnumValueDescriptor. + builder.setField(optionalNestedEnumField, unknown6543); + builder.setRepeatedField(repeatedNestedEnumField, 0, unknown4321); + builder.setRepeatedField(packedNestedEnumField, 0, unknown5432); + message = builder.build(); + // Like other descriptors, unknown EnumValueDescriptor can be compared by + // object identity. + assertTrue(unknown6543 == message.getField(optionalNestedEnumField)); + assertTrue(unknown4321 == message.getRepeatedField(repeatedNestedEnumField, 0)); + assertTrue(unknown5432 == message.getRepeatedField(packedNestedEnumField, 0)); + } + + public void testUnknownEnumValueWithDynamicMessage() throws Exception { + Descriptor descriptor = TestAllTypes.getDescriptor(); + FieldDescriptor optionalNestedEnumField = descriptor.findFieldByName("optional_nested_enum"); + FieldDescriptor repeatedNestedEnumField = descriptor.findFieldByName("repeated_nested_enum"); + FieldDescriptor packedNestedEnumField = descriptor.findFieldByName("packed_nested_enum"); + EnumDescriptor enumType = TestAllTypes.NestedEnum.getDescriptor(); + + Message dynamicMessageDefaultInstance = DynamicMessage.getDefaultInstance(descriptor); + + Message.Builder builder = dynamicMessageDefaultInstance.newBuilderForType(); + builder.setField(optionalNestedEnumField, + enumType.findValueByNumberCreatingIfUnknown(4321)); + builder.addRepeatedField(repeatedNestedEnumField, + enumType.findValueByNumberCreatingIfUnknown(5432)); + builder.addRepeatedField(packedNestedEnumField, + enumType.findValueByNumberCreatingIfUnknown(6543)); + Message message = builder.build(); + assertEquals(4321, + ((EnumValueDescriptor) message.getField(optionalNestedEnumField)).getNumber()); + assertEquals(5432, + ((EnumValueDescriptor) message.getRepeatedField(repeatedNestedEnumField, 0)).getNumber()); + assertEquals(6543, + ((EnumValueDescriptor) message.getRepeatedField(packedNestedEnumField, 0)).getNumber()); + + // Test reflection based serialization/parsing implementation. + ByteString data = message.toByteString(); + message = dynamicMessageDefaultInstance + .newBuilderForType() + .mergeFrom(data) + .build(); + assertEquals(4321, + ((EnumValueDescriptor) message.getField(optionalNestedEnumField)).getNumber()); + assertEquals(5432, + ((EnumValueDescriptor) message.getRepeatedField(repeatedNestedEnumField, 0)).getNumber()); + assertEquals(6543, + ((EnumValueDescriptor) message.getRepeatedField(packedNestedEnumField, 0)).getNumber()); + + // Test reflection based equals()/hashCode(). + builder = dynamicMessageDefaultInstance.newBuilderForType(); + builder.setField(optionalNestedEnumField, + enumType.findValueByNumberCreatingIfUnknown(4321)); + builder.addRepeatedField(repeatedNestedEnumField, + enumType.findValueByNumberCreatingIfUnknown(5432)); + builder.addRepeatedField(packedNestedEnumField, + enumType.findValueByNumberCreatingIfUnknown(6543)); + Message sameMessage = builder.build(); + assertEquals(message, sameMessage); + assertEquals(message.hashCode(), sameMessage.hashCode()); + builder.setField(optionalNestedEnumField, + enumType.findValueByNumberCreatingIfUnknown(0)); + Message differentMessage = builder.build(); + assertFalse(message.equals(differentMessage)); + } + + public void testUnknownEnumValuesInTextFormat() { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + builder.setOptionalNestedEnumValue(4321); + builder.addRepeatedNestedEnumValue(5432); + builder.addPackedNestedEnumValue(6543); + TestAllTypes message = builder.build(); + + // We can print a message with unknown enum values. + String textData = TextFormat.printToString(message); + assertEquals( + "optional_nested_enum: UNKNOWN_ENUM_VALUE_NestedEnum_4321\n" + + "repeated_nested_enum: UNKNOWN_ENUM_VALUE_NestedEnum_5432\n" + + "packed_nested_enum: UNKNOWN_ENUM_VALUE_NestedEnum_6543\n", textData); + + // Parsing unknown enum values will fail just like parsing other kinds of + // unknown fields. + try { + TextFormat.merge(textData, builder); + fail(); + } catch (ParseException e) { + // expected. + } + } +} diff --git a/java/src/test/java/com/google/protobuf/UnknownFieldSetLiteTest.java b/java/src/test/java/com/google/protobuf/UnknownFieldSetLiteTest.java new file mode 100644 index 00000000..6372b7a7 --- /dev/null +++ b/java/src/test/java/com/google/protobuf/UnknownFieldSetLiteTest.java @@ -0,0 +1,317 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// 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 protobuf_unittest.lite_equals_and_hash.LiteEqualsAndHash; +import protobuf_unittest.lite_equals_and_hash.LiteEqualsAndHash.Bar; +import protobuf_unittest.lite_equals_and_hash.LiteEqualsAndHash.Foo; + +import junit.framework.TestCase; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +/** + * Tests for {@link UnknownFieldSetLite}. + * + * @author dweis@google.com (Daniel Weis) + */ +public class UnknownFieldSetLiteTest extends TestCase { + + public void testNoDataIsDefaultInstance() { + assertSame( + UnknownFieldSetLite.getDefaultInstance(), + UnknownFieldSetLite.newBuilder() + .build()); + } + + public void testDefaultInstance() { + UnknownFieldSetLite unknownFields = UnknownFieldSetLite.getDefaultInstance(); + + assertEquals(0, unknownFields.getSerializedSize()); + assertEquals(ByteString.EMPTY, toByteString(unknownFields)); + } + + public void testMergeFieldFrom() throws IOException { + Foo foo = Foo.newBuilder() + .setValue(2) + .build(); + + CodedInputStream input = CodedInputStream.newInstance(foo.toByteArray()); + + UnknownFieldSetLite.Builder builder = UnknownFieldSetLite.newBuilder(); + builder.mergeFieldFrom(input.readTag(), input); + + assertEquals(foo.toByteString(), toByteString(builder.build())); + } + + public void testSerializedSize() throws IOException { + Foo foo = Foo.newBuilder() + .setValue(2) + .build(); + + CodedInputStream input = CodedInputStream.newInstance(foo.toByteArray()); + + UnknownFieldSetLite.Builder builder = UnknownFieldSetLite.newBuilder(); + builder.mergeFieldFrom(input.readTag(), input); + + assertEquals(foo.toByteString().size(), builder.build().getSerializedSize()); + } + + public void testMergeVarintField() throws IOException { + UnknownFieldSetLite.Builder builder = UnknownFieldSetLite.newBuilder(); + builder.mergeVarintField(10, 2); + + CodedInputStream input = + CodedInputStream.newInstance(toByteString(builder.build()).toByteArray()); + + int tag = input.readTag(); + assertEquals(10, WireFormat.getTagFieldNumber(tag)); + assertEquals(WireFormat.WIRETYPE_VARINT, WireFormat.getTagWireType(tag)); + assertEquals(2, input.readUInt64()); + assertTrue(input.isAtEnd()); + } + + public void testMergeVarintField_negative() throws IOException { + UnknownFieldSetLite.Builder builder = UnknownFieldSetLite.newBuilder(); + builder.mergeVarintField(10, -6); + + CodedInputStream input = + CodedInputStream.newInstance(toByteString(builder.build()).toByteArray()); + + int tag = input.readTag(); + assertEquals(10, WireFormat.getTagFieldNumber(tag)); + assertEquals(WireFormat.WIRETYPE_VARINT, WireFormat.getTagWireType(tag)); + assertEquals(-6, input.readUInt64()); + assertTrue(input.isAtEnd()); + } + + public void testEqualsAndHashCode() { + UnknownFieldSetLite.Builder builder1 = UnknownFieldSetLite.newBuilder(); + builder1.mergeVarintField(10, 2); + UnknownFieldSetLite unknownFields1 = builder1.build(); + + UnknownFieldSetLite.Builder builder2 = UnknownFieldSetLite.newBuilder(); + builder2.mergeVarintField(10, 2); + UnknownFieldSetLite unknownFields2 = builder2.build(); + + assertEquals(unknownFields1, unknownFields2); + assertEquals(unknownFields1.hashCode(), unknownFields2.hashCode()); + assertFalse(unknownFields1.equals(UnknownFieldSetLite.getDefaultInstance())); + assertFalse(unknownFields1.hashCode() == UnknownFieldSetLite.getDefaultInstance().hashCode()); + } + + public void testConcat() throws IOException { + UnknownFieldSetLite.Builder builder = UnknownFieldSetLite.newBuilder(); + builder.mergeVarintField(10, 2); + UnknownFieldSetLite unknownFields = builder.build(); + + unknownFields = UnknownFieldSetLite.concat(unknownFields, unknownFields); + + CodedInputStream input = + CodedInputStream.newInstance(toByteString(unknownFields).toByteArray()); + + int tag = input.readTag(); + assertEquals(10, WireFormat.getTagFieldNumber(tag)); + assertEquals(WireFormat.WIRETYPE_VARINT, WireFormat.getTagWireType(tag)); + assertEquals(2, input.readUInt64()); + assertFalse(input.isAtEnd()); + input.readTag(); + assertEquals(10, WireFormat.getTagFieldNumber(tag)); + assertEquals(WireFormat.WIRETYPE_VARINT, WireFormat.getTagWireType(tag)); + assertEquals(2, input.readUInt64()); + assertTrue(input.isAtEnd()); + } + + public void testConcat_empty() { + UnknownFieldSetLite unknownFields = UnknownFieldSetLite.concat( + UnknownFieldSetLite.getDefaultInstance(), UnknownFieldSetLite.getDefaultInstance()); + + assertEquals(0, unknownFields.getSerializedSize()); + assertEquals(ByteString.EMPTY, toByteString(unknownFields)); + } + + public void testBuilderReuse() throws IOException { + UnknownFieldSetLite.Builder builder = UnknownFieldSetLite.newBuilder(); + builder.mergeVarintField(10, 2); + builder.build(); + + try { + builder.build(); + fail(); + } catch (IllegalStateException e) { + // Expected. + } + + try { + builder.mergeFieldFrom(0, CodedInputStream.newInstance(new byte[0])); + fail(); + } catch (IllegalStateException e) { + // Expected. + } + + try { + builder.mergeVarintField(5, 1); + fail(); + } catch (IllegalStateException e) { + // Expected. + } + } + + public void testBuilderReuse_empty() { + UnknownFieldSetLite.Builder builder = UnknownFieldSetLite.newBuilder(); + builder.build(); + + try { + builder.build(); + fail(); + } catch (IllegalStateException e) { + // Expected. + } + } + + public void testRoundTrips() throws InvalidProtocolBufferException { + Foo foo = Foo.newBuilder() + .setValue(1) + .setExtension(Bar.fooExt, Bar.newBuilder() + .setName("name") + .build()) + .setExtension(LiteEqualsAndHash.varint, 22) + .setExtension(LiteEqualsAndHash.fixed32, 44) + .setExtension(LiteEqualsAndHash.fixed64, 66L) + .setExtension(LiteEqualsAndHash.myGroup, LiteEqualsAndHash.MyGroup.newBuilder() + .setGroupValue("value") + .build()) + .build(); + + Foo copy = Foo.parseFrom(foo.toByteArray()); + + assertEquals(foo.getSerializedSize(), copy.getSerializedSize()); + assertFalse(foo.equals(copy)); + + Foo secondCopy = Foo.parseFrom(foo.toByteArray()); + assertEquals(copy, secondCopy); + + ExtensionRegistryLite extensionRegistry = ExtensionRegistryLite.newInstance(); + LiteEqualsAndHash.registerAllExtensions(extensionRegistry); + Foo copyOfCopy = Foo.parseFrom(copy.toByteArray(), extensionRegistry); + + assertEquals(foo, copyOfCopy); + } + + public void testMalformedBytes() { + try { + Foo.parseFrom("this is a malformed protocol buffer".getBytes(StandardCharsets.UTF_8)); + fail(); + } catch (InvalidProtocolBufferException e) { + // Expected. + } + } + + public void testMissingStartGroupTag() throws IOException { + ByteString.Output byteStringOutput = ByteString.newOutput(); + CodedOutputStream output = CodedOutputStream.newInstance(byteStringOutput); + output.writeGroupNoTag(Foo.newBuilder().setValue(11).build()); + output.writeTag(100, WireFormat.WIRETYPE_END_GROUP); + output.flush(); + + try { + Foo.parseFrom(byteStringOutput.toByteString()); + fail(); + } catch (InvalidProtocolBufferException e) { + // Expected. + } + } + + public void testMissingEndGroupTag() throws IOException { + ByteString.Output byteStringOutput = ByteString.newOutput(); + CodedOutputStream output = CodedOutputStream.newInstance(byteStringOutput); + output.writeTag(100, WireFormat.WIRETYPE_START_GROUP); + output.writeGroupNoTag(Foo.newBuilder().setValue(11).build()); + output.flush(); + + try { + Foo.parseFrom(byteStringOutput.toByteString()); + fail(); + } catch (InvalidProtocolBufferException e) { + // Expected. + } + } + + public void testMismatchingGroupTags() throws IOException { + ByteString.Output byteStringOutput = ByteString.newOutput(); + CodedOutputStream output = CodedOutputStream.newInstance(byteStringOutput); + output.writeTag(100, WireFormat.WIRETYPE_START_GROUP); + output.writeGroupNoTag(Foo.newBuilder().setValue(11).build()); + output.writeTag(101, WireFormat.WIRETYPE_END_GROUP); + output.flush(); + + try { + Foo.parseFrom(byteStringOutput.toByteString()); + fail(); + } catch (InvalidProtocolBufferException e) { + // Expected. + } + } + + public void testTruncatedInput() { + Foo foo = Foo.newBuilder() + .setValue(1) + .setExtension(Bar.fooExt, Bar.newBuilder() + .setName("name") + .build()) + .setExtension(LiteEqualsAndHash.varint, 22) + .setExtension(LiteEqualsAndHash.myGroup, LiteEqualsAndHash.MyGroup.newBuilder() + .setGroupValue("value") + .build()) + .build(); + + try { + Foo.parseFrom(foo.toByteString().substring(0, foo.toByteString().size() - 10)); + fail(); + } catch (InvalidProtocolBufferException e) { + // Expected. + } + } + + private ByteString toByteString(UnknownFieldSetLite unknownFields) { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + CodedOutputStream output = CodedOutputStream.newInstance(byteArrayOutputStream); + try { + unknownFields.writeTo(output); + output.flush(); + } catch (IOException e) { + throw new RuntimeException(e); + } + return ByteString.copyFrom(byteArrayOutputStream.toByteArray()); + } +} diff --git a/java/src/test/java/com/google/protobuf/field_presence_test.proto b/java/src/test/java/com/google/protobuf/field_presence_test.proto new file mode 100644 index 00000000..8d8ea8ef --- /dev/null +++ b/java/src/test/java/com/google/protobuf/field_presence_test.proto @@ -0,0 +1,93 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// 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. + +syntax = "proto3"; + +package field_presence_test; + +import "google/protobuf/unittest.proto"; + +option java_package = "com.google.protobuf"; +option java_outer_classname = "FieldPresenceTestProto"; +option java_generate_equals_and_hash = true; + +message TestAllTypes { + enum NestedEnum { + FOO = 0; + BAR = 1; + BAZ = 2; + } + message NestedMessage { + optional int32 value = 1; + } + + optional int32 optional_int32 = 1; + optional string optional_string = 2; + optional bytes optional_bytes = 3; + optional NestedEnum optional_nested_enum = 4; + optional NestedMessage optional_nested_message = 5; + optional protobuf_unittest.TestRequired optional_proto2_message = 6; + + oneof oneof_field { + int32 oneof_int32 = 11; + uint32 oneof_uint32 = 12; + string oneof_string = 13; + bytes oneof_bytes = 14; + NestedEnum oneof_nested_enum = 15; + NestedMessage oneof_nested_message = 16; + protobuf_unittest.TestRequired oneof_proto2_message = 17; + } + + repeated int32 repeated_int32 = 21; + repeated string repeated_string = 22; + repeated bytes repeated_bytes = 23; + repeated NestedEnum repeated_nested_enum = 24; + repeated NestedMessage repeated_nested_message = 25; + repeated protobuf_unittest.TestRequired repeated_proto2_message = 26; + repeated NestedEnum packed_nested_enum = 27 [packed = true]; +} + +message TestOptionalFieldsOnly { + optional int32 optional_int32 = 1; + optional string optional_string = 2; + optional bytes optional_bytes = 3; + optional TestAllTypes.NestedEnum optional_nested_enum = 4; + optional TestAllTypes.NestedMessage optional_nested_message = 5; + optional protobuf_unittest.TestRequired optional_proto2_message = 6; +} + +message TestRepeatedFieldsOnly { + repeated int32 repeated_int32 = 21; + repeated string repeated_string = 22; + repeated bytes repeated_bytes = 23; + repeated TestAllTypes.NestedEnum repeated_nested_enum = 24; + repeated TestAllTypes.NestedMessage repeated_nested_message = 25; + repeated protobuf_unittest.TestRequired repeated_proto2_message = 26; +} diff --git a/java/src/test/java/com/google/protobuf/lazy_fields_lite.proto b/java/src/test/java/com/google/protobuf/lazy_fields_lite.proto index baed4e10..015dc267 100644 --- a/java/src/test/java/com/google/protobuf/lazy_fields_lite.proto +++ b/java/src/test/java/com/google/protobuf/lazy_fields_lite.proto @@ -32,6 +32,7 @@ // // A proto file with lazy fields +syntax = "proto2"; package protobuf_unittest; diff --git a/java/src/test/java/com/google/protobuf/lite_equals_and_hash.proto b/java/src/test/java/com/google/protobuf/lite_equals_and_hash.proto index 68615672..86837250 100644 --- a/java/src/test/java/com/google/protobuf/lite_equals_and_hash.proto +++ b/java/src/test/java/com/google/protobuf/lite_equals_and_hash.proto @@ -30,6 +30,7 @@ // Author: pbogle@google.com (Phil Bogle) +syntax = "proto2"; package protobuf_unittest.lite_equals_and_hash; @@ -41,9 +42,15 @@ option optimize_for = LITE_RUNTIME; message Foo { optional int32 value = 1; repeated Bar bar = 2; + + extensions 100 to max; } message Bar { + extend Foo { + optional Bar foo_ext = 100; + } + optional string name = 1; } @@ -53,3 +60,13 @@ message BarPrime { message Empty { } + +extend Foo { + optional int32 varint = 101; + optional fixed32 fixed32 = 102; + optional fixed64 fixed64 = 103; + optional group MyGroup = 104 { + optional string group_value = 1; + } +} + diff --git a/java/src/test/java/com/google/protobuf/map_for_proto2_lite_test.proto b/java/src/test/java/com/google/protobuf/map_for_proto2_lite_test.proto new file mode 100644 index 00000000..4f23a4a8 --- /dev/null +++ b/java/src/test/java/com/google/protobuf/map_for_proto2_lite_test.proto @@ -0,0 +1,63 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// 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. + +syntax = "proto2"; + + +option java_outer_classname = "MapForProto2TestProto"; +option java_generate_equals_and_hash = true; + +message TestMap { + message MessageValue { + optional int32 value = 1; + } + enum EnumValue { + FOO = 0; + BAR = 1; + BAZ = 2; + QUX = 3; + } + + map int32_to_int32_field = 1; + map int32_to_string_field = 2; + map int32_to_bytes_field = 3; + map int32_to_enum_field = 4; + map int32_to_message_field = 5; + map string_to_int32_field = 6; +} + +message TestUnknownEnumValue { + // Wire-compatible with TestMap.int32_to_enum_field so we can test the + // parsing behavior of TestMap regarding unknown enum values. + map int32_to_int32_field = 4; +} +package map_for_proto2_lite_test; +option java_package = "map_lite_test"; +option optimize_for = LITE_RUNTIME; diff --git a/java/src/test/java/com/google/protobuf/map_for_proto2_test.proto b/java/src/test/java/com/google/protobuf/map_for_proto2_test.proto new file mode 100644 index 00000000..36dfbd10 --- /dev/null +++ b/java/src/test/java/com/google/protobuf/map_for_proto2_test.proto @@ -0,0 +1,62 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// 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. + +syntax = "proto2"; + +package map_for_proto2_test; + +option java_package = "map_test"; +option java_outer_classname = "MapForProto2TestProto"; +option java_generate_equals_and_hash = true; + +message TestMap { + message MessageValue { + optional int32 value = 1; + } + enum EnumValue { + FOO = 0; + BAR = 1; + BAZ = 2; + QUX = 3; + } + + map int32_to_int32_field = 1; + map int32_to_string_field = 2; + map int32_to_bytes_field = 3; + map int32_to_enum_field = 4; + map int32_to_message_field = 5; + map string_to_int32_field = 6; +} + +message TestUnknownEnumValue { + // Wire-compatible with TestMap.int32_to_enum_field so we can test the + // parsing behavior of TestMap regarding unknown enum values. + map int32_to_int32_field = 4; +} diff --git a/java/src/test/java/com/google/protobuf/map_test.proto b/java/src/test/java/com/google/protobuf/map_test.proto new file mode 100644 index 00000000..105ee3f8 --- /dev/null +++ b/java/src/test/java/com/google/protobuf/map_test.proto @@ -0,0 +1,63 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// 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. + +syntax = "proto3"; + +package map_test; + +option java_package = "map_test"; +option java_outer_classname = "MapTestProto"; +option java_generate_equals_and_hash = true; + +message TestMap { + message MessageValue { + optional int32 value = 1; + } + enum EnumValue { + FOO = 0; + BAR = 1; + BAZ = 2; + QUX = 3; + } + + map int32_to_int32_field = 1; + map int32_to_string_field = 2; + map int32_to_bytes_field = 3; + map int32_to_enum_field = 4; + map int32_to_message_field = 5; + map string_to_int32_field = 6; +} + +// Used to test that a nested bulider containing map fields will properly +// propagate the onChange event and mark its parent dirty when a change +// is made to a map field. +message TestOnChangeEventPropagation { + optional TestMap optional_message = 1; +} diff --git a/java/src/test/java/com/google/protobuf/multiple_files_test.proto b/java/src/test/java/com/google/protobuf/multiple_files_test.proto index 7ec89a05..92790506 100644 --- a/java/src/test/java/com/google/protobuf/multiple_files_test.proto +++ b/java/src/test/java/com/google/protobuf/multiple_files_test.proto @@ -32,6 +32,7 @@ // // A proto file which tests the java_multiple_files option. +syntax = "proto2"; // Some generic_services option(s) added automatically. // See: http://go/proto2-generic-services-default diff --git a/java/src/test/java/com/google/protobuf/nested_builders_test.proto b/java/src/test/java/com/google/protobuf/nested_builders_test.proto index 8ab4a433..a5dd66d8 100644 --- a/java/src/test/java/com/google/protobuf/nested_builders_test.proto +++ b/java/src/test/java/com/google/protobuf/nested_builders_test.proto @@ -30,6 +30,7 @@ // Author: jonp@google.com (Jon Perlow) // +syntax = "proto2"; package protobuf_unittest; diff --git a/java/src/test/java/com/google/protobuf/nested_extension.proto b/java/src/test/java/com/google/protobuf/nested_extension.proto index 86492867..704e649e 100644 --- a/java/src/test/java/com/google/protobuf/nested_extension.proto +++ b/java/src/test/java/com/google/protobuf/nested_extension.proto @@ -33,6 +33,7 @@ // A proto file with nested extensions. Note that this must be defined in // a separate file to properly test the initialization of the outer class. +syntax = "proto2"; import "com/google/protobuf/non_nested_extension.proto"; diff --git a/java/src/test/java/com/google/protobuf/nested_extension_lite.proto b/java/src/test/java/com/google/protobuf/nested_extension_lite.proto index 9c686829..a95c38b2 100644 --- a/java/src/test/java/com/google/protobuf/nested_extension_lite.proto +++ b/java/src/test/java/com/google/protobuf/nested_extension_lite.proto @@ -34,6 +34,7 @@ // this must be defined in a separate file to properly test the initialization // of the outer class. +syntax = "proto2"; package protobuf_unittest; diff --git a/java/src/test/java/com/google/protobuf/non_nested_extension.proto b/java/src/test/java/com/google/protobuf/non_nested_extension.proto index cb2f8b00..31fac552 100644 --- a/java/src/test/java/com/google/protobuf/non_nested_extension.proto +++ b/java/src/test/java/com/google/protobuf/non_nested_extension.proto @@ -32,6 +32,7 @@ // // A proto file with extensions. +syntax = "proto2"; package protobuf_unittest; diff --git a/java/src/test/java/com/google/protobuf/non_nested_extension_lite.proto b/java/src/test/java/com/google/protobuf/non_nested_extension_lite.proto index c1f744b8..37c369ed 100644 --- a/java/src/test/java/com/google/protobuf/non_nested_extension_lite.proto +++ b/java/src/test/java/com/google/protobuf/non_nested_extension_lite.proto @@ -32,6 +32,7 @@ // // A proto file with extensions for a MessageLite messages. +syntax = "proto2"; package protobuf_unittest; diff --git a/java/src/test/java/com/google/protobuf/outer_class_name_test.proto b/java/src/test/java/com/google/protobuf/outer_class_name_test.proto index c5114f89..42083681 100644 --- a/java/src/test/java/com/google/protobuf/outer_class_name_test.proto +++ b/java/src/test/java/com/google/protobuf/outer_class_name_test.proto @@ -28,6 +28,8 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +syntax = "proto2"; + package protobuf_unittest; diff --git a/java/src/test/java/com/google/protobuf/outer_class_name_test2.proto b/java/src/test/java/com/google/protobuf/outer_class_name_test2.proto index 741cd281..3e5956b0 100644 --- a/java/src/test/java/com/google/protobuf/outer_class_name_test2.proto +++ b/java/src/test/java/com/google/protobuf/outer_class_name_test2.proto @@ -28,6 +28,8 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +syntax = "proto2"; + package protobuf_unittest; diff --git a/java/src/test/java/com/google/protobuf/outer_class_name_test3.proto b/java/src/test/java/com/google/protobuf/outer_class_name_test3.proto index 5d5d4ac9..74a8ba3c 100644 --- a/java/src/test/java/com/google/protobuf/outer_class_name_test3.proto +++ b/java/src/test/java/com/google/protobuf/outer_class_name_test3.proto @@ -28,6 +28,8 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +syntax = "proto2"; + package protobuf_unittest; diff --git a/java/src/test/java/com/google/protobuf/test_bad_identifiers.proto b/java/src/test/java/com/google/protobuf/test_bad_identifiers.proto index 202e8c91..67035fd5 100644 --- a/java/src/test/java/com/google/protobuf/test_bad_identifiers.proto +++ b/java/src/test/java/com/google/protobuf/test_bad_identifiers.proto @@ -33,6 +33,7 @@ // This file tests that various identifiers work as field and type names even // though the same identifiers are used internally by the java code generator. +syntax = "proto2"; // Some generic_services option(s) added automatically. // See: http://go/proto2-generic-services-default @@ -59,12 +60,14 @@ message Descriptor { } optional NestedDescriptor nested_descriptor = 2; enum NestedEnum { + UNKNOWN = 0; FOO = 1; } } message Parser { enum ParserEnum { + UNKNOWN = 0; PARSER = 1; } optional ParserEnum parser = 1; @@ -72,6 +75,7 @@ message Parser { message Deprecated { enum TestEnum { + UNKNOWN = 0; FOO = 1; // Test if @Deprecated annotation conflicts with Deprecated message name. @@ -118,6 +122,7 @@ service TestConflictingMethodNames { message TestConflictingFieldNames { enum TestEnum { + UNKNOWN = 0; FOO = 1; } message TestMessage { @@ -142,16 +147,23 @@ message TestConflictingFieldNames { // This field conflicts with "int32_field" as they both generate // the method getInt32FieldList(). - required int32 int32_field_list = 31; + required int32 int32_field_list = 31; // NO_PROTO3 - extensions 1000 to max; + extensions 1000 to max; // NO_PROTO3 repeated int64 int64_field = 41; - extend TestConflictingFieldNames { + extend TestConflictingFieldNames { // NO_PROTO3 // We don't generate accessors for extensions so the following extension // fields don't conflict with the repeated field "int64_field". - optional int64 int64_field_count = 1001; - optional int64 int64_field_list = 1002; - } + optional int64 int64_field_count = 1001; // NO_PROTO3 + optional int64 int64_field_list = 1002; // NO_PROTO3 + } // NO_PROTO3 } +message TestMapField { + message MapField {} + message Pair {} + message Message {} + + map map_field = 1; +} diff --git a/java/src/test/java/com/google/protobuf/test_check_utf8.proto b/java/src/test/java/com/google/protobuf/test_check_utf8.proto index 206946d8..119c1dcb 100644 --- a/java/src/test/java/com/google/protobuf/test_check_utf8.proto +++ b/java/src/test/java/com/google/protobuf/test_check_utf8.proto @@ -31,6 +31,7 @@ // Author: Jacob Butcher (jbaum@google.com) // // Test file option java_string_check_utf8. +syntax = "proto2"; package proto2_test_check_utf8; diff --git a/java/src/test/java/com/google/protobuf/test_check_utf8_size.proto b/java/src/test/java/com/google/protobuf/test_check_utf8_size.proto index fa057a88..f06d76d6 100644 --- a/java/src/test/java/com/google/protobuf/test_check_utf8_size.proto +++ b/java/src/test/java/com/google/protobuf/test_check_utf8_size.proto @@ -31,6 +31,7 @@ // Author: Jacob Butcher (jbaum@google.com) // // Test file option java_string_check_utf8. +syntax = "proto2"; package proto2_test_check_utf8_size; diff --git a/java/src/test/java/com/google/protobuf/test_custom_options.proto b/java/src/test/java/com/google/protobuf/test_custom_options.proto index f6a5ecd4..f8efd455 100644 --- a/java/src/test/java/com/google/protobuf/test_custom_options.proto +++ b/java/src/test/java/com/google/protobuf/test_custom_options.proto @@ -32,6 +32,7 @@ // // Test that custom options defined in a proto file's dependencies are properly // initialized. +syntax = "proto2"; package protobuf_unittest; diff --git a/java/src/test/java/com/google/protobuf/test_extra_interfaces.proto b/java/src/test/java/com/google/protobuf/test_extra_interfaces.proto index 72e05a36..645f57b4 100644 --- a/java/src/test/java/com/google/protobuf/test_extra_interfaces.proto +++ b/java/src/test/java/com/google/protobuf/test_extra_interfaces.proto @@ -29,6 +29,7 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Author: Darick Tong (darick@google.com) +syntax = "proto2"; package protobuf_unittest; -- cgit v1.2.3