diff options
author | Feng Xiao <xiaofeng@google.com> | 2016-01-13 11:22:18 -0800 |
---|---|---|
committer | Feng Xiao <xiaofeng@google.com> | 2016-01-13 11:22:18 -0800 |
commit | b6ef4015d7eccc6f63dab3dc46e76f9f9f2217ab (patch) | |
tree | 5bc2d51dfe16fc483b2dad95a7c7279698543cae /java/core | |
parent | 73c003c309235485c0e53f2075242567c88a72bc (diff) | |
parent | 49efe9d7db877022e76375df2d4daadab98619b6 (diff) | |
download | protobuf-b6ef4015d7eccc6f63dab3dc46e76f9f9f2217ab.tar.gz protobuf-b6ef4015d7eccc6f63dab3dc46e76f9f9f2217ab.tar.bz2 protobuf-b6ef4015d7eccc6f63dab3dc46e76f9f9f2217ab.zip |
Merge pull request #1121 from nmittler/restructure
Restructuring protobuf to multiple modules
Diffstat (limited to 'java/core')
142 files changed, 55866 insertions, 0 deletions
diff --git a/java/core/generate-sources-build.xml b/java/core/generate-sources-build.xml new file mode 100644 index 00000000..0996e5ff --- /dev/null +++ b/java/core/generate-sources-build.xml @@ -0,0 +1,20 @@ +<project name="generate-sources"> + <echo message="Running protoc ..."/> + <mkdir dir="${generated.sources.dir}"/> + <exec executable="${protoc}"> + <arg value="--java_out=${generated.sources.dir}"/> + <arg value="--proto_path=${protobuf.source.dir}"/> + <arg value="${protobuf.source.dir}/google/protobuf/any.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/api.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/descriptor.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/duration.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/empty.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/field_mask.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/source_context.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/struct.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/timestamp.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/type.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/wrappers.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/compiler/plugin.proto"/> + </exec> +</project>
\ No newline at end of file diff --git a/java/core/generate-test-sources-build.xml b/java/core/generate-test-sources-build.xml new file mode 100644 index 00000000..ab415db6 --- /dev/null +++ b/java/core/generate-test-sources-build.xml @@ -0,0 +1,43 @@ +<project name="generate-test-sources"> + <mkdir dir="${generated.testsources.dir}"/> + <exec executable="${protoc}"> + <arg value="--java_out=${generated.testsources.dir}"/> + <arg value="--proto_path=${protobuf.source.dir}"/> + <arg value="--proto_path=${test.proto.dir}"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_import.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_import_public.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_mset.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_mset_wire_format.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_optimize_for.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_custom_options.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_lite.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_import_lite.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_import_public_lite.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_lite_imports_nonlite.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_enormous_descriptor.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_no_generic_services.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_well_known_types.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/lazy_fields_lite.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/lite_equals_and_hash.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/multiple_files_test.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/nested_builders_test.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/nested_extension.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/nested_extension_lite.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/non_nested_extension.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/non_nested_extension_lite.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/outer_class_name_test.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/outer_class_name_test2.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/outer_class_name_test3.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/test_bad_identifiers.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/test_check_utf8.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/test_check_utf8_size.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/test_custom_options.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/any_test.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/field_presence_test.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/map_for_proto2_lite_test.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/map_for_proto2_test.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/map_test.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/map_initialization_order_test.proto"/> + </exec> +</project>
\ No newline at end of file diff --git a/java/core/pom.xml b/java/core/pom.xml new file mode 100644 index 00000000..74d5ead3 --- /dev/null +++ b/java/core/pom.xml @@ -0,0 +1,118 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>com.google.protobuf</groupId> + <artifactId>protobuf-parent</artifactId> + <version>3.0.0-beta-2</version> + </parent> + + <artifactId>protobuf-java</artifactId> + <packaging>bundle</packaging> + + <name>Protocol Buffers [Core]</name> + <description> + Core Protocol Buffers library. Protocol Buffers are a way of encoding structured data in an + efficient yet extensible format. + </description> + + <dependencies> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + </dependency> + <dependency> + <groupId>org.easymock</groupId> + <artifactId>easymock</artifactId> + </dependency> + <dependency> + <groupId>org.easymock</groupId> + <artifactId>easymockclassextension</artifactId> + </dependency> + </dependencies> + + <build> + <!-- Include core protos in the bundle as resources --> + <resources> + <resource> + <directory>${protobuf.source.dir}</directory> + <includes> + <include>google/protobuf/any.proto</include> + <include>google/protobuf/api.proto</include> + <include>google/protobuf/descriptor.proto</include> + <include>google/protobuf/duration.proto</include> + <include>google/protobuf/empty.proto</include> + <include>google/protobuf/field_mask.proto</include> + <include>google/protobuf/source_context.proto</include> + <include>google/protobuf/struct.proto</include> + <include>google/protobuf/timestamp.proto</include> + <include>google/protobuf/type.proto</include> + <include>google/protobuf/wrappers.proto</include> + <include>google/protobuf/compiler/plugin.proto</include> + </includes> + </resource> + </resources> + + <plugins> + <!-- Use Antrun plugin to generate sources with protoc --> + <plugin> + <artifactId>maven-antrun-plugin</artifactId> + <executions> + <!-- Generate core protos --> + <execution> + <id>generate-sources</id> + <phase>generate-sources</phase> + <configuration> + <target> + <ant antfile="generate-sources-build.xml"/> + </target> + </configuration> + <goals> + <goal>run</goal> + </goals> + </execution> + + <!-- Generate the test protos --> + <execution> + <id>generate-test-sources</id> + <phase>generate-test-sources</phase> + <configuration> + <target> + <ant antfile="generate-test-sources-build.xml"/> + </target> + </configuration> + <goals> + <goal>run</goal> + </goals> + </execution> + </executions> + </plugin> + + <!-- Add the generated sources to the build --> + <plugin> + <artifactId>maven-compiler-plugin</artifactId> + <configuration> + <generatedSourcesDirectory>${generated.sources.dir}</generatedSourcesDirectory> + <generatedTestSourcesDirectory>${generated.testsources.dir}</generatedTestSourcesDirectory> + </configuration> + </plugin> + + <!-- OSGI bundle configuration --> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <extensions>true</extensions> + <configuration> + <instructions> + <Bundle-DocURL>https://developers.google.com/protocol-buffers/</Bundle-DocURL> + <Bundle-SymbolicName>com.google.protobuf</Bundle-SymbolicName> + <Export-Package>com.google.protobuf;version=${project.version}</Export-Package> + </instructions> + </configuration> + </plugin> + </plugins> + </build> + +</project> diff --git a/java/core/src/main/java/com/google/protobuf/AbstractMessage.java b/java/core/src/main/java/com/google/protobuf/AbstractMessage.java new file mode 100644 index 00000000..9f418f2b --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/AbstractMessage.java @@ -0,0 +1,533 @@ +// 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.EnumValueDescriptor; +import com.google.protobuf.Descriptors.FieldDescriptor; +import com.google.protobuf.Descriptors.OneofDescriptor; +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; + +/** + * A partial implementation of the {@link Message} interface which implements + * as many methods of that interface as possible in terms of other methods. + * + * @author kenton@google.com Kenton Varda + */ +public abstract class AbstractMessage extends AbstractMessageLite + implements Message { + public boolean isInitialized() { + return MessageReflection.isInitialized(this); + } + + + public List<String> findInitializationErrors() { + return MessageReflection.findMissingFields(this); + } + + public String getInitializationErrorString() { + return MessageReflection.delimitWithCommas(findInitializationErrors()); + } + + /** TODO(jieluo): Clear it when all subclasses have implemented this method. */ + @Override + public boolean hasOneof(OneofDescriptor oneof) { + throw new UnsupportedOperationException("hasOneof() is not implemented."); + } + + /** TODO(jieluo): Clear it when all subclasses have implemented this method. */ + @Override + public FieldDescriptor getOneofFieldDescriptor(OneofDescriptor oneof) { + throw new UnsupportedOperationException( + "getOneofFieldDescriptor() is not implemented."); + } + + @Override + public final String toString() { + return TextFormat.printToString(this); + } + + public void writeTo(final CodedOutputStream output) throws IOException { + MessageReflection.writeMessageTo(this, getAllFields(), output, false); + } + + protected int memoizedSize = -1; + + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) { + return size; + } + + memoizedSize = MessageReflection.getSerializedSize(this, getAllFields()); + return memoizedSize; + } + + @Override + public boolean equals(final Object other) { + if (other == this) { + return true; + } + if (!(other instanceof Message)) { + return false; + } + final Message otherMessage = (Message) other; + if (getDescriptorForType() != otherMessage.getDescriptorForType()) { + return false; + } + return compareFields(getAllFields(), otherMessage.getAllFields()) && + getUnknownFields().equals(otherMessage.getUnknownFields()); + } + + @Override + public int hashCode() { + int hash = memoizedHashCode; + if (hash == 0) { + hash = 41; + hash = (19 * hash) + getDescriptorForType().hashCode(); + hash = hashFields(hash, getAllFields()); + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + } + return hash; + } + + private static ByteString toByteString(Object value) { + if (value instanceof byte[]) { + return ByteString.copyFrom((byte[]) value); + } else { + return (ByteString) value; + } + } + + /** + * Compares two bytes fields. The parameters must be either a byte array or a + * ByteString object. They can be of different type though. + */ + private static boolean compareBytes(Object a, Object b) { + if (a instanceof byte[] && b instanceof byte[]) { + return Arrays.equals((byte[])a, (byte[])b); + } + 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"); + Object fieldValue = entry.getField(value); + if (fieldValue instanceof EnumValueDescriptor) { + fieldValue = ((EnumValueDescriptor) fieldValue).getNumber(); + } + result.put(entry.getField(key), fieldValue); + while (iterator.hasNext()) { + entry = (Message) iterator.next(); + fieldValue = entry.getField(value); + if (fieldValue instanceof EnumValueDescriptor) { + fieldValue = ((EnumValueDescriptor) fieldValue).getNumber(); + } + result.put(entry.getField(key), fieldValue); + } + 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)} + * and {@link AbstractMutableMessage#equals(Object)}. It takes special care + * of bytes fields because immutable messages and mutable messages use + * different Java type to reprensent a bytes field and this method should be + * able to compare immutable messages, mutable messages and also an immutable + * message to a mutable message. + */ + static boolean compareFields(Map<FieldDescriptor, Object> a, + Map<FieldDescriptor, Object> b) { + if (a.size() != b.size()) { + return false; + } + for (FieldDescriptor descriptor : a.keySet()) { + if (!b.containsKey(descriptor)) { + return false; + } + Object value1 = a.get(descriptor); + Object value2 = b.get(descriptor); + if (descriptor.getType() == FieldDescriptor.Type.BYTES) { + if (descriptor.isRepeated()) { + List list1 = (List) value1; + List list2 = (List) value2; + if (list1.size() != list2.size()) { + return false; + } + for (int i = 0; i < list1.size(); i++) { + if (!compareBytes(list1.get(i), list2.get(i))) { + return false; + } + } + } else { + // Compares a singular bytes field. + if (!compareBytes(value1, value2)) { + return false; + } + } + } else if (descriptor.isMapField()) { + if (!compareMapField(value1, value2)) { + return false; + } + } else { + // Compare non-bytes fields. + if (!value1.equals(value2)) { + return false; + } + } + } + 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") + protected static int hashFields(int hash, Map<FieldDescriptor, Object> map) { + for (Map.Entry<FieldDescriptor, Object> entry : map.entrySet()) { + FieldDescriptor field = entry.getKey(); + Object value = entry.getValue(); + hash = (37 * hash) + field.getNumber(); + 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<? extends EnumLite> list = (List<? extends EnumLite>) value; + hash = (53 * hash) + Internal.hashEnumList(list); + } else { + hash = (53 * hash) + Internal.hashEnum((EnumLite) value); + } + } + return hash; + } + + /** + * Package private helper method for AbstractParser to create + * UninitializedMessageException with missing field information. + */ + @Override + UninitializedMessageException newUninitializedMessageException() { + return Builder.newUninitializedMessageException(this); + } + + // ================================================================= + + /** + * A partial implementation of the {@link Message.Builder} interface which + * implements as many methods of that interface as possible in terms of + * other methods. + */ + @SuppressWarnings("unchecked") + public static abstract class Builder<BuilderType extends Builder> + extends AbstractMessageLite.Builder<BuilderType> + implements Message.Builder { + // The compiler produces an error if this is not declared explicitly. + @Override + public abstract BuilderType clone(); + + /** TODO(jieluo): Clear it when all subclasses have implemented this method. */ + @Override + public boolean hasOneof(OneofDescriptor oneof) { + throw new UnsupportedOperationException("hasOneof() is not implemented."); + } + + /** TODO(jieluo): Clear it when all subclasses have implemented this method. */ + @Override + public FieldDescriptor getOneofFieldDescriptor(OneofDescriptor oneof) { + throw new UnsupportedOperationException( + "getOneofFieldDescriptor() is not implemented."); + } + + /** TODO(jieluo): Clear it when all subclasses have implemented this method. */ + @Override + public BuilderType clearOneof(OneofDescriptor oneof) { + throw new UnsupportedOperationException("clearOneof() is not implemented."); + } + + public BuilderType clear() { + for (final Map.Entry<FieldDescriptor, Object> entry : + getAllFields().entrySet()) { + clearField(entry.getKey()); + } + return (BuilderType) this; + } + + public List<String> findInitializationErrors() { + return MessageReflection.findMissingFields(this); + } + + public String getInitializationErrorString() { + return MessageReflection.delimitWithCommas(findInitializationErrors()); + } + + public BuilderType mergeFrom(final Message other) { + if (other.getDescriptorForType() != getDescriptorForType()) { + throw new IllegalArgumentException( + "mergeFrom(Message) can only merge messages of the same type."); + } + + // Note: We don't attempt to verify that other's fields have valid + // types. Doing so would be a losing battle. We'd have to verify + // all sub-messages as well, and we'd have to make copies of all of + // them to insure that they don't change after verification (since + // the Message interface itself cannot enforce immutability of + // implementations). + // TODO(kenton): Provide a function somewhere called makeDeepCopy() + // which allows people to make secure deep copies of messages. + + for (final Map.Entry<FieldDescriptor, Object> entry : + other.getAllFields().entrySet()) { + final FieldDescriptor field = entry.getKey(); + if (field.isRepeated()) { + for (final Object element : (List)entry.getValue()) { + addRepeatedField(field, element); + } + } else if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + final Message existingValue = (Message)getField(field); + if (existingValue == existingValue.getDefaultInstanceForType()) { + setField(field, entry.getValue()); + } else { + setField(field, + existingValue.newBuilderForType() + .mergeFrom(existingValue) + .mergeFrom((Message)entry.getValue()) + .build()); + } + } else { + setField(field, entry.getValue()); + } + } + + mergeUnknownFields(other.getUnknownFields()); + + return (BuilderType) this; + } + + @Override + public BuilderType mergeFrom(final CodedInputStream input) + throws IOException { + return mergeFrom(input, ExtensionRegistry.getEmptyRegistry()); + } + + @Override + public BuilderType mergeFrom( + final CodedInputStream input, + final ExtensionRegistryLite extensionRegistry) + throws IOException { + final UnknownFieldSet.Builder unknownFields = + UnknownFieldSet.newBuilder(getUnknownFields()); + while (true) { + final int tag = input.readTag(); + if (tag == 0) { + break; + } + + MessageReflection.BuilderAdapter builderAdapter = + new MessageReflection.BuilderAdapter(this); + if (!MessageReflection.mergeFieldFrom(input, unknownFields, + extensionRegistry, + getDescriptorForType(), + builderAdapter, + tag)) { + // end group tag + break; + } + } + setUnknownFields(unknownFields.build()); + return (BuilderType) this; + } + + public BuilderType mergeUnknownFields(final UnknownFieldSet unknownFields) { + setUnknownFields( + UnknownFieldSet.newBuilder(getUnknownFields()) + .mergeFrom(unknownFields) + .build()); + return (BuilderType) this; + } + + public Message.Builder getFieldBuilder(final FieldDescriptor field) { + throw new UnsupportedOperationException( + "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); + } + + /** + * Construct an UninitializedMessageException reporting missing fields in + * the given message. + */ + protected static UninitializedMessageException + newUninitializedMessageException(Message message) { + return new UninitializedMessageException( + MessageReflection.findMissingFields(message)); + } + + // =============================================================== + // The following definitions seem to be required in order to make javac + // not produce weird errors like: + // + // java/com/google/protobuf/DynamicMessage.java:203: types + // com.google.protobuf.AbstractMessage.Builder< + // com.google.protobuf.DynamicMessage.Builder> and + // com.google.protobuf.AbstractMessage.Builder< + // com.google.protobuf.DynamicMessage.Builder> are incompatible; both + // define mergeFrom(com.google.protobuf.ByteString), but with unrelated + // return types. + // + // Strangely, these lines are only needed if javac is invoked separately + // on AbstractMessage.java and AbstractMessageLite.java. If javac is + // invoked on both simultaneously, it works. (Or maybe the important + // point is whether or not DynamicMessage.java is compiled together with + // AbstractMessageLite.java -- not sure.) I suspect this is a compiler + // bug. + + @Override + public BuilderType mergeFrom(final ByteString data) + throws InvalidProtocolBufferException { + return super.mergeFrom(data); + } + + @Override + public BuilderType mergeFrom( + final ByteString data, + final ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + return super.mergeFrom(data, extensionRegistry); + } + + @Override + public BuilderType mergeFrom(final byte[] data) + throws InvalidProtocolBufferException { + return super.mergeFrom(data); + } + + @Override + public BuilderType mergeFrom( + final byte[] data, final int off, final int len) + throws InvalidProtocolBufferException { + return super.mergeFrom(data, off, len); + } + + @Override + public BuilderType mergeFrom( + final byte[] data, + final ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + return super.mergeFrom(data, extensionRegistry); + } + + @Override + public BuilderType mergeFrom( + final byte[] data, final int off, final int len, + final ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + return super.mergeFrom(data, off, len, extensionRegistry); + } + + @Override + public BuilderType mergeFrom(final InputStream input) + throws IOException { + return super.mergeFrom(input); + } + + @Override + public BuilderType mergeFrom( + final InputStream input, + final ExtensionRegistryLite extensionRegistry) + throws IOException { + return super.mergeFrom(input, extensionRegistry); + } + + @Override + public boolean mergeDelimitedFrom(final InputStream input) + throws IOException { + return super.mergeDelimitedFrom(input); + } + + @Override + public boolean mergeDelimitedFrom( + final InputStream input, + final ExtensionRegistryLite extensionRegistry) + throws IOException { + return super.mergeDelimitedFrom(input, extensionRegistry); + } + } +} diff --git a/java/core/src/main/java/com/google/protobuf/AbstractMessageLite.java b/java/core/src/main/java/com/google/protobuf/AbstractMessageLite.java new file mode 100644 index 00000000..12384983 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/AbstractMessageLite.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 java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Collection; + +/** + * A partial implementation of the {@link MessageLite} interface which + * implements as many methods of that interface as possible in terms of other + * methods. + * + * @author kenton@google.com Kenton Varda + */ +public abstract class AbstractMessageLite implements MessageLite { + protected int memoizedHashCode = 0; + + public ByteString toByteString() { + try { + final ByteString.CodedBuilder out = + ByteString.newCodedBuilder(getSerializedSize()); + writeTo(out.getCodedOutput()); + return out.build(); + } catch (IOException e) { + throw new RuntimeException( + "Serializing to a ByteString threw an IOException (should " + + "never happen).", e); + } + } + + public byte[] toByteArray() { + try { + final byte[] result = new byte[getSerializedSize()]; + final CodedOutputStream output = CodedOutputStream.newInstance(result); + writeTo(output); + output.checkNoSpaceLeft(); + return result; + } catch (IOException e) { + throw new RuntimeException( + "Serializing to a byte array threw an IOException " + + "(should never happen).", e); + } + } + + public void writeTo(final OutputStream output) throws IOException { + final int bufferSize = + CodedOutputStream.computePreferredBufferSize(getSerializedSize()); + final CodedOutputStream codedOutput = + CodedOutputStream.newInstance(output, bufferSize); + writeTo(codedOutput); + codedOutput.flush(); + } + + public void writeDelimitedTo(final OutputStream output) throws IOException { + final int serialized = getSerializedSize(); + final int bufferSize = CodedOutputStream.computePreferredBufferSize( + CodedOutputStream.computeRawVarint32Size(serialized) + serialized); + final CodedOutputStream codedOutput = + CodedOutputStream.newInstance(output, bufferSize); + codedOutput.writeRawVarint32(serialized); + writeTo(codedOutput); + codedOutput.flush(); + } + + + /** + * Package private helper method for AbstractParser to create + * UninitializedMessageException. + */ + UninitializedMessageException newUninitializedMessageException() { + return new UninitializedMessageException(this); + } + + protected static void checkByteStringIsUtf8(ByteString byteString) + throws IllegalArgumentException { + if (!byteString.isValidUtf8()) { + throw new IllegalArgumentException("Byte string is not UTF-8."); + } + } + + protected static <T> void addAll(final Iterable<T> values, + final Collection<? super T> list) { + Builder.addAll(values, list); + } + + /** + * A partial implementation of the {@link Message.Builder} interface which + * implements as many methods of that interface as possible in terms of + * other methods. + */ + @SuppressWarnings("unchecked") + public static abstract class Builder<BuilderType extends Builder> + implements MessageLite.Builder { + // The compiler produces an error if this is not declared explicitly. + @Override + public abstract BuilderType clone(); + + public BuilderType mergeFrom(final CodedInputStream input) + throws IOException { + return mergeFrom(input, ExtensionRegistryLite.getEmptyRegistry()); + } + + // Re-defined here for return type covariance. + public abstract BuilderType mergeFrom( + final CodedInputStream input, + final ExtensionRegistryLite extensionRegistry) + throws IOException; + + public BuilderType mergeFrom(final ByteString data) + throws InvalidProtocolBufferException { + try { + final CodedInputStream input = data.newCodedInput(); + mergeFrom(input); + input.checkLastTagWas(0); + return (BuilderType) this; + } catch (InvalidProtocolBufferException e) { + throw e; + } catch (IOException e) { + throw new RuntimeException( + "Reading from a ByteString threw an IOException (should " + + "never happen).", e); + } + } + + public BuilderType mergeFrom( + final ByteString data, + final ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + try { + final CodedInputStream input = data.newCodedInput(); + mergeFrom(input, extensionRegistry); + input.checkLastTagWas(0); + return (BuilderType) this; + } catch (InvalidProtocolBufferException e) { + throw e; + } catch (IOException e) { + throw new RuntimeException( + "Reading from a ByteString threw an IOException (should " + + "never happen).", e); + } + } + + public BuilderType mergeFrom(final byte[] data) + throws InvalidProtocolBufferException { + return mergeFrom(data, 0, data.length); + } + + public BuilderType mergeFrom(final byte[] data, final int off, + final int len) + throws InvalidProtocolBufferException { + try { + final CodedInputStream input = + CodedInputStream.newInstance(data, off, len); + mergeFrom(input); + input.checkLastTagWas(0); + return (BuilderType) this; + } catch (InvalidProtocolBufferException e) { + throw e; + } catch (IOException e) { + throw new RuntimeException( + "Reading from a byte array threw an IOException (should " + + "never happen).", e); + } + } + + public BuilderType mergeFrom( + final byte[] data, + final ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + return mergeFrom(data, 0, data.length, extensionRegistry); + } + + public BuilderType mergeFrom( + final byte[] data, final int off, final int len, + final ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + try { + final CodedInputStream input = + CodedInputStream.newInstance(data, off, len); + mergeFrom(input, extensionRegistry); + input.checkLastTagWas(0); + return (BuilderType) this; + } catch (InvalidProtocolBufferException e) { + throw e; + } catch (IOException e) { + throw new RuntimeException( + "Reading from a byte array threw an IOException (should " + + "never happen).", e); + } + } + + public BuilderType mergeFrom(final InputStream input) throws IOException { + final CodedInputStream codedInput = CodedInputStream.newInstance(input); + mergeFrom(codedInput); + codedInput.checkLastTagWas(0); + return (BuilderType) this; + } + + public BuilderType mergeFrom( + final InputStream input, + final ExtensionRegistryLite extensionRegistry) + throws IOException { + final CodedInputStream codedInput = CodedInputStream.newInstance(input); + mergeFrom(codedInput, extensionRegistry); + codedInput.checkLastTagWas(0); + return (BuilderType) this; + } + + /** + * An InputStream implementations which reads from some other InputStream + * but is limited to a particular number of bytes. Used by + * mergeDelimitedFrom(). This is intentionally package-private so that + * UnknownFieldSet can share it. + */ + static final class LimitedInputStream extends FilterInputStream { + private int limit; + + LimitedInputStream(InputStream in, int limit) { + super(in); + this.limit = limit; + } + + @Override + public int available() throws IOException { + return Math.min(super.available(), limit); + } + + @Override + public int read() throws IOException { + if (limit <= 0) { + return -1; + } + final int result = super.read(); + if (result >= 0) { + --limit; + } + return result; + } + + @Override + public int read(final byte[] b, final int off, int len) + throws IOException { + if (limit <= 0) { + return -1; + } + len = Math.min(len, limit); + final int result = super.read(b, off, len); + if (result >= 0) { + limit -= result; + } + return result; + } + + @Override + public long skip(final long n) throws IOException { + final long result = super.skip(Math.min(n, limit)); + if (result >= 0) { + limit -= result; + } + return result; + } + } + + public boolean mergeDelimitedFrom( + final InputStream input, + final ExtensionRegistryLite extensionRegistry) + throws IOException { + final int firstByte = input.read(); + if (firstByte == -1) { + return false; + } + final int size = CodedInputStream.readRawVarint32(firstByte, input); + final InputStream limitedInput = new LimitedInputStream(input, size); + mergeFrom(limitedInput, extensionRegistry); + return true; + } + + public boolean mergeDelimitedFrom(final InputStream input) + throws IOException { + return mergeDelimitedFrom(input, + ExtensionRegistryLite.getEmptyRegistry()); + } + + /** + * Construct an UninitializedMessageException reporting missing fields in + * the given message. + */ + protected static UninitializedMessageException + newUninitializedMessageException(MessageLite message) { + return new UninitializedMessageException(message); + } + + /** + * Adds the {@code values} to the {@code list}. This is a helper method + * used by generated code. Users should ignore it. + * + * @throws NullPointerException if {@code values} or any of the elements of + * {@code values} is null. When that happens, some elements of + * {@code values} may have already been added to the result {@code list}. + */ + protected static <T> void addAll(final Iterable<T> values, + final Collection<? super T> list) { + if (values == null) { + throw new NullPointerException(); + } + if (values instanceof LazyStringList) { + // For StringOrByteStringLists, check the underlying elements to avoid + // forcing conversions of ByteStrings to Strings. + checkForNullValues(((LazyStringList) values).getUnderlyingElements()); + list.addAll((Collection<T>) values); + } else if (values instanceof Collection) { + checkForNullValues(values); + list.addAll((Collection<T>) values); + } else { + for (final T value : values) { + if (value == null) { + throw new NullPointerException(); + } + list.add(value); + } + } + } + + private static void checkForNullValues(final Iterable<?> values) { + for (final Object value : values) { + if (value == null) { + throw new NullPointerException(); + } + } + } + } +} diff --git a/java/core/src/main/java/com/google/protobuf/AbstractParser.java b/java/core/src/main/java/com/google/protobuf/AbstractParser.java new file mode 100644 index 00000000..1a4c6311 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/AbstractParser.java @@ -0,0 +1,253 @@ +// 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.AbstractMessageLite.Builder.LimitedInputStream; + +import java.io.IOException; +import java.io.InputStream; + +/** + * A partial implementation of the {@link Parser} interface which implements + * as many methods of that interface as possible in terms of other methods. + * + * Note: This class implements all the convenience methods in the + * {@link Parser} interface. See {@link Parser} for related javadocs. + * Subclasses need to implement + * {@link Parser#parsePartialFrom(CodedInputStream, ExtensionRegistryLite)} + * + * @author liujisi@google.com (Pherl Liu) + */ +public abstract class AbstractParser<MessageType extends MessageLite> + implements Parser<MessageType> { + /** + * Creates an UninitializedMessageException for MessageType. + */ + private UninitializedMessageException + newUninitializedMessageException(MessageType message) { + if (message instanceof AbstractMessageLite) { + return ((AbstractMessageLite) message).newUninitializedMessageException(); + } + return new UninitializedMessageException(message); + } + + /** + * Helper method to check if message is initialized. + * + * @throws InvalidProtocolBufferException if it is not initialized. + * @return The message to check. + */ + private MessageType checkMessageInitialized(MessageType message) + throws InvalidProtocolBufferException { + if (message != null && !message.isInitialized()) { + throw newUninitializedMessageException(message) + .asInvalidProtocolBufferException() + .setUnfinishedMessage(message); + } + return message; + } + + private static final ExtensionRegistryLite EMPTY_REGISTRY + = ExtensionRegistryLite.getEmptyRegistry(); + + public MessageType parsePartialFrom(CodedInputStream input) + throws InvalidProtocolBufferException { + return parsePartialFrom(input, EMPTY_REGISTRY); + } + + public MessageType parseFrom(CodedInputStream input, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + return checkMessageInitialized( + parsePartialFrom(input, extensionRegistry)); + } + + public MessageType parseFrom(CodedInputStream input) + throws InvalidProtocolBufferException { + return parseFrom(input, EMPTY_REGISTRY); + } + + public MessageType parsePartialFrom(ByteString data, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + MessageType message; + try { + CodedInputStream input = data.newCodedInput(); + message = parsePartialFrom(input, extensionRegistry); + try { + input.checkLastTagWas(0); + } catch (InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(message); + } + return message; + } catch (InvalidProtocolBufferException e) { + throw e; + } + } + + public MessageType parsePartialFrom(ByteString data) + throws InvalidProtocolBufferException { + return parsePartialFrom(data, EMPTY_REGISTRY); + } + + public MessageType parseFrom(ByteString data, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + return checkMessageInitialized(parsePartialFrom(data, extensionRegistry)); + } + + public MessageType parseFrom(ByteString data) + throws InvalidProtocolBufferException { + return parseFrom(data, EMPTY_REGISTRY); + } + + public MessageType parsePartialFrom(byte[] data, int off, int len, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + try { + CodedInputStream input = CodedInputStream.newInstance(data, off, len); + MessageType message = parsePartialFrom(input, extensionRegistry); + try { + input.checkLastTagWas(0); + } catch (InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(message); + } + return message; + } catch (InvalidProtocolBufferException e) { + throw e; + } + } + + public MessageType parsePartialFrom(byte[] data, int off, int len) + throws InvalidProtocolBufferException { + return parsePartialFrom(data, off, len, EMPTY_REGISTRY); + } + + public MessageType parsePartialFrom(byte[] data, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + return parsePartialFrom(data, 0, data.length, extensionRegistry); + } + + public MessageType parsePartialFrom(byte[] data) + throws InvalidProtocolBufferException { + return parsePartialFrom(data, 0, data.length, EMPTY_REGISTRY); + } + + public MessageType parseFrom(byte[] data, int off, int len, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + return checkMessageInitialized( + parsePartialFrom(data, off, len, extensionRegistry)); + } + + public MessageType parseFrom(byte[] data, int off, int len) + throws InvalidProtocolBufferException { + return parseFrom(data, off, len, EMPTY_REGISTRY); + } + + public MessageType parseFrom(byte[] data, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + return parseFrom(data, 0, data.length, extensionRegistry); + } + + public MessageType parseFrom(byte[] data) + throws InvalidProtocolBufferException { + return parseFrom(data, EMPTY_REGISTRY); + } + + public MessageType parsePartialFrom(InputStream input, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + CodedInputStream codedInput = CodedInputStream.newInstance(input); + MessageType message = parsePartialFrom(codedInput, extensionRegistry); + try { + codedInput.checkLastTagWas(0); + } catch (InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(message); + } + return message; + } + + public MessageType parsePartialFrom(InputStream input) + throws InvalidProtocolBufferException { + return parsePartialFrom(input, EMPTY_REGISTRY); + } + + public MessageType parseFrom(InputStream input, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + return checkMessageInitialized( + parsePartialFrom(input, extensionRegistry)); + } + + public MessageType parseFrom(InputStream input) + throws InvalidProtocolBufferException { + return parseFrom(input, EMPTY_REGISTRY); + } + + public MessageType parsePartialDelimitedFrom( + InputStream input, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + int size; + try { + int firstByte = input.read(); + if (firstByte == -1) { + return null; + } + size = CodedInputStream.readRawVarint32(firstByte, input); + } catch (IOException e) { + throw new InvalidProtocolBufferException(e.getMessage()); + } + InputStream limitedInput = new LimitedInputStream(input, size); + return parsePartialFrom(limitedInput, extensionRegistry); + } + + public MessageType parsePartialDelimitedFrom(InputStream input) + throws InvalidProtocolBufferException { + return parsePartialDelimitedFrom(input, EMPTY_REGISTRY); + } + + public MessageType parseDelimitedFrom( + InputStream input, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + return checkMessageInitialized( + parsePartialDelimitedFrom(input, extensionRegistry)); + } + + public MessageType parseDelimitedFrom(InputStream input) + throws InvalidProtocolBufferException { + return parseDelimitedFrom(input, EMPTY_REGISTRY); + } +} diff --git a/java/core/src/main/java/com/google/protobuf/AbstractProtobufList.java b/java/core/src/main/java/com/google/protobuf/AbstractProtobufList.java new file mode 100644 index 00000000..bb6446b2 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/AbstractProtobufList.java @@ -0,0 +1,136 @@ +// 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.Internal.ProtobufList; + +import java.util.AbstractList; +import java.util.Collection; + +/** + * An abstract implementation of {@link ProtobufList} which manages mutability semantics. All mutate + * methods are check if the list is mutable before proceeding. Subclasses must invoke + * {@link #ensureIsMutable()} manually when overriding those methods. + */ +abstract class AbstractProtobufList<E> extends AbstractList<E> implements ProtobufList<E> { + + /** + * Whether or not this list is modifiable. + */ + private boolean isMutable; + + /** + * Constructs a mutable list by default. + */ + AbstractProtobufList() { + isMutable = true; + } + + @Override + public boolean add(E e) { + ensureIsMutable(); + return super.add(e); + } + + @Override + public void add(int index, E element) { + ensureIsMutable(); + super.add(index, element); + } + + @Override + public boolean addAll(Collection<? extends E> c) { + ensureIsMutable(); + return super.addAll(c); + } + + @Override + public boolean addAll(int index, Collection<? extends E> c) { + ensureIsMutable(); + return super.addAll(index, c); + } + + @Override + public void clear() { + ensureIsMutable(); + super.clear(); + } + + @Override + public boolean isModifiable() { + return isMutable; + } + + @Override + public final void makeImmutable() { + isMutable = false; + } + + @Override + public E remove(int index) { + ensureIsMutable(); + return super.remove(index); + } + + @Override + public boolean remove(Object o) { + ensureIsMutable(); + return super.remove(o); + } + + @Override + public boolean removeAll(Collection<?> c) { + ensureIsMutable(); + return super.removeAll(c); + } + + @Override + public boolean retainAll(Collection<?> c) { + ensureIsMutable(); + return super.retainAll(c); + } + + @Override + public E set(int index, E element) { + ensureIsMutable(); + return super.set(index, element); + } + + /** + * Throws an {@link UnsupportedOperationException} if the list is immutable. Subclasses are + * responsible for invoking this method on mutate operations. + */ + protected void ensureIsMutable() { + if (!isMutable) { + throw new UnsupportedOperationException(); + } + } +} diff --git a/java/core/src/main/java/com/google/protobuf/BlockingRpcChannel.java b/java/core/src/main/java/com/google/protobuf/BlockingRpcChannel.java new file mode 100644 index 00000000..d535efb9 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/BlockingRpcChannel.java @@ -0,0 +1,51 @@ +// 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; + +/** + * <p>Abstract interface for a blocking RPC channel. {@code BlockingRpcChannel} + * is the blocking equivalent to {@link RpcChannel}. + * + * @author kenton@google.com Kenton Varda + * @author cpovirk@google.com Chris Povirk + */ +public interface BlockingRpcChannel { + /** + * Call the given method of the remote service and blocks until it returns. + * {@code callBlockingMethod()} is the blocking equivalent to + * {@link RpcChannel#callMethod}. + */ + Message callBlockingMethod( + Descriptors.MethodDescriptor method, + RpcController controller, + Message request, + Message responsePrototype) throws ServiceException; +} diff --git a/java/core/src/main/java/com/google/protobuf/BlockingService.java b/java/core/src/main/java/com/google/protobuf/BlockingService.java new file mode 100644 index 00000000..d01f0b8f --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/BlockingService.java @@ -0,0 +1,64 @@ +// 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; + +/** + * Blocking equivalent to {@link Service}. + * + * @author kenton@google.com Kenton Varda + * @author cpovirk@google.com Chris Povirk + */ +public interface BlockingService { + /** + * Equivalent to {@link Service#getDescriptorForType}. + */ + Descriptors.ServiceDescriptor getDescriptorForType(); + + /** + * Equivalent to {@link Service#callMethod}, except that + * {@code callBlockingMethod()} returns the result of the RPC or throws a + * {@link ServiceException} if there is a failure, rather than passing the + * information to a callback. + */ + Message callBlockingMethod(Descriptors.MethodDescriptor method, + RpcController controller, + Message request) throws ServiceException; + + /** + * Equivalent to {@link Service#getRequestPrototype}. + */ + Message getRequestPrototype(Descriptors.MethodDescriptor method); + + /** + * Equivalent to {@link Service#getResponsePrototype}. + */ + Message getResponsePrototype(Descriptors.MethodDescriptor method); +} diff --git a/java/core/src/main/java/com/google/protobuf/BooleanArrayList.java b/java/core/src/main/java/com/google/protobuf/BooleanArrayList.java new file mode 100644 index 00000000..70e042f5 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/BooleanArrayList.java @@ -0,0 +1,251 @@ +// 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.Internal.BooleanList; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.RandomAccess; + +/** + * An implementation of {@link BooleanList} on top of a primitive array. + * + * @author dweis@google.com (Daniel Weis) + */ +final class BooleanArrayList + extends AbstractProtobufList<Boolean> implements BooleanList, RandomAccess { + + private static final int DEFAULT_CAPACITY = 10; + + private static final BooleanArrayList EMPTY_LIST = new BooleanArrayList(); + static { + EMPTY_LIST.makeImmutable(); + } + + public static BooleanArrayList emptyList() { + return EMPTY_LIST; + } + + /** + * The backing store for the list. + */ + private boolean[] array; + + /** + * The size of the list distinct from the length of the array. That is, it is the number of + * elements set in the list. + */ + private int size; + + /** + * Constructs a new mutable {@code BooleanArrayList} with default capacity. + */ + BooleanArrayList() { + this(DEFAULT_CAPACITY); + } + + /** + * Constructs a new mutable {@code BooleanArrayList} with the provided capacity. + */ + BooleanArrayList(int capacity) { + array = new boolean[capacity]; + size = 0; + } + + /** + * Constructs a new mutable {@code BooleanArrayList} containing the same elements as + * {@code other}. + */ + BooleanArrayList(List<Boolean> other) { + if (other instanceof BooleanArrayList) { + BooleanArrayList list = (BooleanArrayList) other; + array = list.array.clone(); + size = list.size; + } else { + size = other.size(); + array = new boolean[size]; + for (int i = 0; i < size; i++) { + array[i] = other.get(i); + } + } + } + + @Override + public Boolean get(int index) { + return getBoolean(index); + } + + @Override + public boolean getBoolean(int index) { + ensureIndexInRange(index); + return array[index]; + } + + @Override + public int size() { + return size; + } + + @Override + public Boolean set(int index, Boolean element) { + return setBoolean(index, element); + } + + @Override + public boolean setBoolean(int index, boolean element) { + ensureIsMutable(); + ensureIndexInRange(index); + boolean previousValue = array[index]; + array[index] = element; + return previousValue; + } + + @Override + public void add(int index, Boolean element) { + addBoolean(index, element); + } + + /** + * Like {@link #add(Boolean)} but more efficient in that it doesn't box the element. + */ + @Override + public void addBoolean(boolean element) { + addBoolean(size, element); + } + + /** + * Like {@link #add(int, Boolean)} but more efficient in that it doesn't box the element. + */ + private void addBoolean(int index, boolean element) { + ensureIsMutable(); + if (index < 0 || index > size) { + throw new IndexOutOfBoundsException(makeOutOfBoundsExceptionMessage(index)); + } + + if (size < array.length) { + // Shift everything over to make room + System.arraycopy(array, index, array, index + 1, size - index); + } else { + // Resize to 1.5x the size + int length = ((size * 3) / 2) + 1; + boolean[] newArray = new boolean[length]; + + // Copy the first part directly + System.arraycopy(array, 0, newArray, 0, index); + + // Copy the rest shifted over by one to make room + System.arraycopy(array, index, newArray, index + 1, size - index); + array = newArray; + } + + array[index] = element; + size++; + modCount++; + } + + @Override + public boolean addAll(Collection<? extends Boolean> collection) { + ensureIsMutable(); + + if (collection == null) { + throw new NullPointerException(); + } + + // We specialize when adding another BooleanArrayList to avoid boxing elements. + if (!(collection instanceof BooleanArrayList)) { + return super.addAll(collection); + } + + BooleanArrayList list = (BooleanArrayList) collection; + if (list.size == 0) { + return false; + } + + int overflow = Integer.MAX_VALUE - size; + if (overflow < list.size) { + // We can't actually represent a list this large. + throw new OutOfMemoryError(); + } + + int newSize = size + list.size; + if (newSize > array.length) { + array = Arrays.copyOf(array, newSize); + } + + System.arraycopy(list.array, 0, array, size, list.size); + size = newSize; + modCount++; + return true; + } + + @Override + public boolean remove(Object o) { + ensureIsMutable(); + for (int i = 0; i < size; i++) { + if (o.equals(array[i])) { + System.arraycopy(array, i + 1, array, i, size - i); + size--; + modCount++; + return true; + } + } + return false; + } + + @Override + public Boolean remove(int index) { + ensureIsMutable(); + ensureIndexInRange(index); + boolean value = array[index]; + System.arraycopy(array, index + 1, array, index, size - index); + size--; + modCount++; + return value; + } + + /** + * Ensures that the provided {@code index} is within the range of {@code [0, size]}. Throws an + * {@link IndexOutOfBoundsException} if it is not. + * + * @param index the index to verify is in range + */ + private void ensureIndexInRange(int index) { + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException(makeOutOfBoundsExceptionMessage(index)); + } + } + + private String makeOutOfBoundsExceptionMessage(int index) { + return "Index:" + index + ", Size:" + size; + } +} diff --git a/java/core/src/main/java/com/google/protobuf/BoundedByteString.java b/java/core/src/main/java/com/google/protobuf/BoundedByteString.java new file mode 100644 index 00000000..934c9030 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/BoundedByteString.java @@ -0,0 +1,124 @@ +// 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; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; + +/** + * This class is used to represent the substring of a {@link ByteString} over a + * single byte array. In terms of the public API of {@link ByteString}, you end + * up here by calling {@link ByteString#copyFrom(byte[])} followed by {@link + * ByteString#substring(int, int)}. + * + * <p>This class contains most of the overhead involved in creating a substring + * from a {@link LiteralByteString}. The overhead involves some range-checking + * and two extra fields. + * + * @author carlanton@google.com (Carl Haverl) + */ +final class BoundedByteString extends LiteralByteString { + + private final int bytesOffset; + private final int bytesLength; + + /** + * Creates a {@code BoundedByteString} backed by the sub-range of given array, + * without copying. + * + * @param bytes array to wrap + * @param offset index to first byte to use in bytes + * @param length number of bytes to use from bytes + * @throws IllegalArgumentException if {@code offset < 0}, {@code length < 0}, + * or if {@code offset + length > + * bytes.length}. + */ + BoundedByteString(byte[] bytes, int offset, int length) { + super(bytes); + checkRange(offset, offset + length, bytes.length); + + this.bytesOffset = offset; + this.bytesLength = length; + } + + /** + * Gets the byte at the given index. + * Throws {@link ArrayIndexOutOfBoundsException} + * for backwards-compatibility reasons although it would more properly be + * {@link IndexOutOfBoundsException}. + * + * @param index index of byte + * @return the value + * @throws ArrayIndexOutOfBoundsException {@code index} is < 0 or >= size + */ + @Override + public byte byteAt(int index) { + // We must check the index ourselves as we cannot rely on Java array index + // checking for substrings. + checkIndex(index, size()); + return bytes[bytesOffset + index]; + } + + @Override + public int size() { + return bytesLength; + } + + @Override + protected int getOffsetIntoBytes() { + return bytesOffset; + } + + // ================================================================= + // ByteString -> byte[] + + @Override + protected void copyToInternal(byte[] target, int sourceOffset, int targetOffset, + int numberToCopy) { + System.arraycopy(bytes, getOffsetIntoBytes() + sourceOffset, target, + targetOffset, numberToCopy); + } + + // ================================================================= + // Serializable + + private static final long serialVersionUID = 1L; + + Object writeReplace() { + return new LiteralByteString(toByteArray()); + } + + private void readObject(@SuppressWarnings("unused") ObjectInputStream in) throws IOException { + throw new InvalidObjectException( + "BoundedByteStream instances are not to be serialized directly"); + } +} diff --git a/java/core/src/main/java/com/google/protobuf/ByteString.java b/java/core/src/main/java/com/google/protobuf/ByteString.java new file mode 100644 index 00000000..68f20d51 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/ByteString.java @@ -0,0 +1,1152 @@ +// 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.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.nio.charset.UnsupportedCharsetException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +/** + * Immutable sequence of bytes. Substring is supported by sharing the reference + * to the immutable underlying bytes, as with {@link String}. Concatenation is + * likewise supported without copying (long strings) by building a tree of + * pieces in {@link RopeByteString}. + * <p> + * Like {@link String}, the contents of a {@link ByteString} can never be + * observed to change, not even in the presence of a data race or incorrect + * API usage in the client code. + * + * @author crazybob@google.com Bob Lee + * @author kenton@google.com Kenton Varda + * @author carlanton@google.com Carl Haverl + * @author martinrb@google.com Martin Buchholz + */ +public abstract class ByteString implements Iterable<Byte>, Serializable { + + /** + * When two strings to be concatenated have a combined length shorter than + * this, we just copy their bytes on {@link #concat(ByteString)}. + * The trade-off is copy size versus the overhead of creating tree nodes + * in {@link RopeByteString}. + */ + static final int CONCATENATE_BY_COPY_SIZE = 128; + + /** + * When copying an InputStream into a ByteString with .readFrom(), + * the chunks in the underlying rope start at 256 bytes, but double + * each iteration up to 8192 bytes. + */ + static final int MIN_READ_FROM_CHUNK_SIZE = 0x100; // 256b + static final int MAX_READ_FROM_CHUNK_SIZE = 0x2000; // 8k + + /** + * Empty {@code ByteString}. + */ + public static final ByteString EMPTY = new LiteralByteString(new byte[0]); + + /** + * Cached hash value. Intentionally accessed via a data race, which + * is safe because of the Java Memory Model's "no out-of-thin-air values" + * guarantees for ints. A value of 0 implies that the hash has not been set. + */ + private int hash = 0; + + // This constructor is here to prevent subclassing outside of this package, + ByteString() {} + + /** + * Gets the byte at the given index. This method should be used only for + * random access to individual bytes. To access bytes sequentially, use the + * {@link ByteIterator} returned by {@link #iterator()}, and call {@link + * #substring(int, int)} first if necessary. + * + * @param index index of byte + * @return the value + * @throws ArrayIndexOutOfBoundsException {@code index < 0 or index >= size} + */ + public abstract byte byteAt(int index); + + /** + * Return a {@link ByteString.ByteIterator} over the bytes in the ByteString. + * To avoid auto-boxing, you may get the iterator manually and call + * {@link ByteIterator#nextByte()}. + * + * @return the iterator + */ + @Override + public final ByteIterator iterator() { + return new ByteIterator() { + private int position = 0; + private final int limit = size(); + + @Override + public boolean hasNext() { + return position < limit; + } + + @Override + public Byte next() { + // Boxing calls Byte.valueOf(byte), which does not instantiate. + return nextByte(); + } + + @Override + public byte nextByte() { + try { + return byteAt(position++); + } catch (ArrayIndexOutOfBoundsException e) { + throw new NoSuchElementException(e.getMessage()); + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + /** + * This interface extends {@code Iterator<Byte>}, so that we can return an + * unboxed {@code byte}. + */ + public interface ByteIterator extends Iterator<Byte> { + /** + * An alternative to {@link Iterator#next()} that returns an + * unboxed primitive {@code byte}. + * + * @return the next {@code byte} in the iteration + * @throws NoSuchElementException if the iteration has no more elements + */ + byte nextByte(); + } + + /** + * Gets the number of bytes. + * + * @return size in bytes + */ + public abstract int size(); + + /** + * Returns {@code true} if the size is {@code 0}, {@code false} otherwise. + * + * @return true if this is zero bytes long + */ + public final boolean isEmpty() { + return size() == 0; + } + + // ================================================================= + // ByteString -> substring + + /** + * Return the substring from {@code beginIndex}, inclusive, to the end of the + * string. + * + * @param beginIndex start at this index + * @return substring sharing underlying data + * @throws IndexOutOfBoundsException if {@code beginIndex < 0} or + * {@code beginIndex > size()}. + */ + public final ByteString substring(int beginIndex) { + return substring(beginIndex, size()); + } + + /** + * Return the substring from {@code beginIndex}, inclusive, to {@code + * endIndex}, exclusive. + * + * @param beginIndex start at this index + * @param endIndex the last character is the one before this index + * @return substring sharing underlying data + * @throws IndexOutOfBoundsException if {@code beginIndex < 0}, + * {@code endIndex > size()}, or {@code beginIndex > endIndex}. + */ + public abstract ByteString substring(int beginIndex, int endIndex); + + /** + * Tests if this bytestring starts with the specified prefix. + * Similar to {@link String#startsWith(String)} + * + * @param prefix the prefix. + * @return <code>true</code> if the byte sequence represented by the + * argument is a prefix of the byte sequence represented by + * this string; <code>false</code> otherwise. + */ + public final boolean startsWith(ByteString prefix) { + return size() >= prefix.size() && + substring(0, prefix.size()).equals(prefix); + } + + /** + * Tests if this bytestring ends with the specified suffix. + * Similar to {@link String#endsWith(String)} + * + * @param suffix the suffix. + * @return <code>true</code> if the byte sequence represented by the + * argument is a suffix of the byte sequence represented by + * this string; <code>false</code> otherwise. + */ + public final boolean endsWith(ByteString suffix) { + return size() >= suffix.size() && + substring(size() - suffix.size()).equals(suffix); + } + + // ================================================================= + // byte[] -> ByteString + + /** + * Copies the given bytes into a {@code ByteString}. + * + * @param bytes source array + * @param offset offset in source array + * @param size number of bytes to copy + * @return new {@code ByteString} + */ + public static ByteString copyFrom(byte[] bytes, int offset, int size) { + byte[] copy = new byte[size]; + System.arraycopy(bytes, offset, copy, 0, size); + return new LiteralByteString(copy); + } + + /** + * Copies the given bytes into a {@code ByteString}. + * + * @param bytes to copy + * @return new {@code ByteString} + */ + public static ByteString copyFrom(byte[] bytes) { + return copyFrom(bytes, 0, bytes.length); + } + + /** + * Copies the next {@code size} bytes from a {@code java.nio.ByteBuffer} into + * a {@code ByteString}. + * + * @param bytes source buffer + * @param size number of bytes to copy + * @return new {@code ByteString} + */ + public static ByteString copyFrom(ByteBuffer bytes, int size) { + byte[] copy = new byte[size]; + bytes.get(copy); + return new LiteralByteString(copy); + } + + /** + * Copies the remaining bytes from a {@code java.nio.ByteBuffer} into + * a {@code ByteString}. + * + * @param bytes sourceBuffer + * @return new {@code ByteString} + */ + public static ByteString copyFrom(ByteBuffer bytes) { + return copyFrom(bytes, bytes.remaining()); + } + + /** + * Encodes {@code text} into a sequence of bytes using the named charset + * and returns the result as a {@code ByteString}. + * + * @param text source string + * @param charsetName encoding to use + * @return new {@code ByteString} + * @throws UnsupportedEncodingException if the encoding isn't found + */ + public static ByteString copyFrom(String text, String charsetName) + throws UnsupportedEncodingException { + return new LiteralByteString(text.getBytes(charsetName)); + } + + /** + * Encodes {@code text} into a sequence of bytes using the named charset + * and returns the result as a {@code ByteString}. + * + * @param text source string + * @param charset encode using this charset + * @return new {@code ByteString} + */ + public static ByteString copyFrom(String text, Charset charset) { + return new LiteralByteString(text.getBytes(charset)); + } + + /** + * Encodes {@code text} into a sequence of UTF-8 bytes and returns the + * result as a {@code ByteString}. + * + * @param text source string + * @return new {@code ByteString} + */ + public static ByteString copyFromUtf8(String text) { + return new LiteralByteString(text.getBytes(Internal.UTF_8)); + } + + // ================================================================= + // InputStream -> ByteString + + /** + * Completely reads the given stream's bytes into a + * {@code ByteString}, blocking if necessary until all bytes are + * read through to the end of the stream. + * + * <b>Performance notes:</b> The returned {@code ByteString} is an + * immutable tree of byte arrays ("chunks") of the stream data. The + * first chunk is small, with subsequent chunks each being double + * the size, up to 8K. + * + * <p>Each byte read from the input stream will be copied twice to ensure + * that the resulting ByteString is truly immutable. + * + * @param streamToDrain The source stream, which is read completely + * but not closed. + * @return A new {@code ByteString} which is made up of chunks of + * various sizes, depending on the behavior of the underlying + * stream. + * @throws IOException IOException is thrown if there is a problem + * reading the underlying stream. + */ + public static ByteString readFrom(InputStream streamToDrain) + throws IOException { + return readFrom(streamToDrain, MIN_READ_FROM_CHUNK_SIZE, MAX_READ_FROM_CHUNK_SIZE); + } + + /** + * Completely reads the given stream's bytes into a + * {@code ByteString}, blocking if necessary until all bytes are + * read through to the end of the stream. + * + * <b>Performance notes:</b> The returned {@code ByteString} is an + * immutable tree of byte arrays ("chunks") of the stream data. The + * chunkSize parameter sets the size of these byte arrays. + * + * <p>Each byte read from the input stream will be copied twice to ensure + * that the resulting ByteString is truly immutable. + * + * @param streamToDrain The source stream, which is read completely + * but not closed. + * @param chunkSize The size of the chunks in which to read the + * stream. + * @return A new {@code ByteString} which is made up of chunks of + * the given size. + * @throws IOException IOException is thrown if there is a problem + * reading the underlying stream. + */ + public static ByteString readFrom(InputStream streamToDrain, int chunkSize) + throws IOException { + return readFrom(streamToDrain, chunkSize, chunkSize); + } + + // Helper method that takes the chunk size range as a parameter. + public static ByteString readFrom(InputStream streamToDrain, int minChunkSize, + int maxChunkSize) throws IOException { + Collection<ByteString> results = new ArrayList<ByteString>(); + + // copy the inbound bytes into a list of chunks; the chunk size + // grows exponentially to support both short and long streams. + int chunkSize = minChunkSize; + while (true) { + ByteString chunk = readChunk(streamToDrain, chunkSize); + if (chunk == null) { + break; + } + results.add(chunk); + chunkSize = Math.min(chunkSize * 2, maxChunkSize); + } + + return ByteString.copyFrom(results); + } + + /** + * Blocks until a chunk of the given size can be made from the + * stream, or EOF is reached. Calls read() repeatedly in case the + * given stream implementation doesn't completely fill the given + * buffer in one read() call. + * + * @return A chunk of the desired size, or else a chunk as large as + * was available when end of stream was reached. Returns null if the + * given stream had no more data in it. + */ + private static ByteString readChunk(InputStream in, final int chunkSize) + throws IOException { + final byte[] buf = new byte[chunkSize]; + int bytesRead = 0; + while (bytesRead < chunkSize) { + final int count = in.read(buf, bytesRead, chunkSize - bytesRead); + if (count == -1) { + break; + } + bytesRead += count; + } + + if (bytesRead == 0) { + return null; + } + + // Always make a copy since InputStream could steal a reference to buf. + return ByteString.copyFrom(buf, 0, bytesRead); + } + + // ================================================================= + // Multiple ByteStrings -> One ByteString + + /** + * Concatenate the given {@code ByteString} to this one. Short concatenations, + * of total size smaller than {@link ByteString#CONCATENATE_BY_COPY_SIZE}, are + * produced by copying the underlying bytes (as per Rope.java, <a + * href="http://www.cs.ubc.ca/local/reading/proceedings/spe91-95/spe/vol25/issue12/spe986.pdf"> + * BAP95 </a>. In general, the concatenate involves no copying. + * + * @param other string to concatenate + * @return a new {@code ByteString} instance + */ + public final ByteString concat(ByteString other) { + if (Integer.MAX_VALUE - size() < other.size()) { + throw new IllegalArgumentException("ByteString would be too long: " + + size() + "+" + other.size()); + } + + return RopeByteString.concatenate(this, other); + } + + /** + * Concatenates all byte strings in the iterable and returns the result. + * This is designed to run in O(list size), not O(total bytes). + * + * <p>The returned {@code ByteString} is not necessarily a unique object. + * If the list is empty, the returned object is the singleton empty + * {@code ByteString}. If the list has only one element, that + * {@code ByteString} will be returned without copying. + * + * @param byteStrings strings to be concatenated + * @return new {@code ByteString} + */ + public static ByteString copyFrom(Iterable<ByteString> byteStrings) { + // Determine the size; + final int size; + if (!(byteStrings instanceof Collection)) { + int tempSize = 0; + for (Iterator<ByteString> iter = byteStrings.iterator(); iter.hasNext(); + iter.next(), ++tempSize) { + } + size = tempSize; + } else { + size = ((Collection<ByteString>) byteStrings).size(); + } + + if (size == 0) { + return EMPTY; + } + + return balancedConcat(byteStrings.iterator(), size); + } + + // Internal function used by copyFrom(Iterable<ByteString>). + // Create a balanced concatenation of the next "length" elements from the + // iterable. + private static ByteString balancedConcat(Iterator<ByteString> iterator, int length) { + assert length >= 1; + ByteString result; + if (length == 1) { + result = iterator.next(); + } else { + int halfLength = length >>> 1; + ByteString left = balancedConcat(iterator, halfLength); + ByteString right = balancedConcat(iterator, length - halfLength); + result = left.concat(right); + } + return result; + } + + // ================================================================= + // ByteString -> byte[] + + /** + * Copies bytes into a buffer at the given offset. + * + * @param target buffer to copy into + * @param offset in the target buffer + * @throws IndexOutOfBoundsException if the offset is negative or too large + */ + public void copyTo(byte[] target, int offset) { + copyTo(target, 0, offset, size()); + } + + /** + * Copies bytes into a buffer. + * + * @param target buffer to copy into + * @param sourceOffset offset within these bytes + * @param targetOffset offset within the target buffer + * @param numberToCopy number of bytes to copy + * @throws IndexOutOfBoundsException if an offset or size is negative or too + * large + */ + public final void copyTo(byte[] target, int sourceOffset, int targetOffset, + int numberToCopy) { + checkRange(sourceOffset, sourceOffset + numberToCopy, size()); + checkRange(targetOffset, targetOffset + numberToCopy, target.length); + if (numberToCopy > 0) { + copyToInternal(target, sourceOffset, targetOffset, numberToCopy); + } + } + + /** + * Internal (package private) implementation of + * {@link #copyTo(byte[],int,int,int)}. + * It assumes that all error checking has already been performed and that + * {@code numberToCopy > 0}. + */ + protected abstract void copyToInternal(byte[] target, int sourceOffset, + int targetOffset, int numberToCopy); + + /** + * Copies bytes into a ByteBuffer. + * + * @param target ByteBuffer to copy into. + * @throws java.nio.ReadOnlyBufferException if the {@code target} is read-only + * @throws java.nio.BufferOverflowException if the {@code target}'s + * remaining() space is not large enough to hold the data. + */ + public abstract void copyTo(ByteBuffer target); + + /** + * Copies bytes to a {@code byte[]}. + * + * @return copied bytes + */ + public final byte[] toByteArray() { + final int size = size(); + if (size == 0) { + return Internal.EMPTY_BYTE_ARRAY; + } + byte[] result = new byte[size]; + copyToInternal(result, 0, 0, size); + return result; + } + + /** + * Writes the complete contents of this byte string to + * the specified output stream argument. + * + * <p>It is assumed that the {@link OutputStream} will not modify the contents passed it + * it. It may be possible for a malicious {@link OutputStream} to corrupt + * the data underlying the {@link ByteString}. + * + * @param out the output stream to which to write the data. + * @throws IOException if an I/O error occurs. + */ + public abstract void writeTo(OutputStream out) throws IOException; + + /** + * Writes a specified part of this byte string to an output stream. + * + * @param out the output stream to which to write the data. + * @param sourceOffset offset within these bytes + * @param numberToWrite number of bytes to write + * @throws IOException if an I/O error occurs. + * @throws IndexOutOfBoundsException if an offset or size is negative or too + * large + */ + final void writeTo(OutputStream out, int sourceOffset, int numberToWrite) + throws IOException { + checkRange(sourceOffset, sourceOffset + numberToWrite, size()); + if (numberToWrite > 0) { + writeToInternal(out, sourceOffset, numberToWrite); + } + } + + /** + * Internal version of {@link #writeTo(OutputStream,int,int)} that assumes + * all error checking has already been done. + */ + abstract void writeToInternal(OutputStream out, int sourceOffset, int numberToWrite) + throws IOException; + + /** + * Constructs a read-only {@code java.nio.ByteBuffer} whose content + * is equal to the contents of this byte string. + * The result uses the same backing array as the byte string, if possible. + * + * @return wrapped bytes + */ + public abstract ByteBuffer asReadOnlyByteBuffer(); + + /** + * Constructs a list of read-only {@code java.nio.ByteBuffer} objects + * such that the concatenation of their contents is equal to the contents + * of this byte string. The result uses the same backing arrays as the + * byte string. + * <p> + * By returning a list, implementations of this method may be able to avoid + * copying even when there are multiple backing arrays. + * + * @return a list of wrapped bytes + */ + public abstract List<ByteBuffer> asReadOnlyByteBufferList(); + + /** + * Constructs a new {@code String} by decoding the bytes using the + * specified charset. + * + * @param charsetName encode using this charset + * @return new string + * @throws UnsupportedEncodingException if charset isn't recognized + */ + public final String toString(String charsetName) + throws UnsupportedEncodingException { + try { + return toString(Charset.forName(charsetName)); + } catch (UnsupportedCharsetException e) { + UnsupportedEncodingException exception = new UnsupportedEncodingException(charsetName); + exception.initCause(e); + throw exception; + } + } + + /** + * Constructs a new {@code String} by decoding the bytes using the + * specified charset. Returns the same empty String if empty. + * + * @param charset encode using this charset + * @return new string + */ + public final String toString(Charset charset) { + return size() == 0 ? "" : toStringInternal(charset); + } + + /** + * Constructs a new {@code String} by decoding the bytes using the + * specified charset. + * + * @param charset encode using this charset + * @return new string + */ + protected abstract String toStringInternal(Charset charset); + + // ================================================================= + // UTF-8 decoding + + /** + * Constructs a new {@code String} by decoding the bytes as UTF-8. + * + * @return new string using UTF-8 encoding + */ + public final String toStringUtf8() { + return toString(Internal.UTF_8); + } + + /** + * Tells whether this {@code ByteString} represents a well-formed UTF-8 + * byte sequence, such that the original bytes can be converted to a + * String object and then round tripped back to bytes without loss. + * + * <p>More precisely, returns {@code true} whenever: <pre> {@code + * Arrays.equals(byteString.toByteArray(), + * new String(byteString.toByteArray(), "UTF-8").getBytes("UTF-8")) + * }</pre> + * + * <p>This method returns {@code false} for "overlong" byte sequences, + * as well as for 3-byte sequences that would map to a surrogate + * character, in accordance with the restricted definition of UTF-8 + * introduced in Unicode 3.1. Note that the UTF-8 decoder included in + * Oracle's JDK has been modified to also reject "overlong" byte + * sequences, but (as of 2011) still accepts 3-byte surrogate + * character byte sequences. + * + * <p>See the Unicode Standard,<br> + * Table 3-6. <em>UTF-8 Bit Distribution</em>,<br> + * Table 3-7. <em>Well Formed UTF-8 Byte Sequences</em>. + * + * @return whether the bytes in this {@code ByteString} are a + * well-formed UTF-8 byte sequence + */ + public abstract boolean isValidUtf8(); + + /** + * Tells whether the given byte sequence is a well-formed, malformed, or + * incomplete UTF-8 byte sequence. This method accepts and returns a partial + * state result, allowing the bytes for a complete UTF-8 byte sequence to be + * composed from multiple {@code ByteString} segments. + * + * @param state either {@code 0} (if this is the initial decoding operation) + * or the value returned from a call to a partial decoding method for the + * previous bytes + * @param offset offset of the first byte to check + * @param length number of bytes to check + * + * @return {@code -1} if the partial byte sequence is definitely malformed, + * {@code 0} if it is well-formed (no additional input needed), or, if the + * byte sequence is "incomplete", i.e. apparently terminated in the middle of + * a character, an opaque integer "state" value containing enough information + * to decode the character when passed to a subsequent invocation of a + * partial decoding method. + */ + protected abstract int partialIsValidUtf8(int state, int offset, int length); + + // ================================================================= + // equals() and hashCode() + + @Override + public abstract boolean equals(Object o); + + /** + * Base class for leaf {@link ByteString}s (i.e. non-ropes). + */ + abstract static class LeafByteString extends ByteString { + @Override + protected final int getTreeDepth() { + return 0; + } + + @Override + protected final boolean isBalanced() { + return true; + } + + /** + * Check equality of the substring of given length of this object starting at + * zero with another {@code ByteString} substring starting at offset. + * + * @param other what to compare a substring in + * @param offset offset into other + * @param length number of bytes to compare + * @return true for equality of substrings, else false. + */ + abstract boolean equalsRange(ByteString other, int offset, int length); + } + + /** + * Compute the hashCode using the traditional algorithm from {@link + * ByteString}. + * + * @return hashCode value + */ + @Override + public final int hashCode() { + int h = hash; + + if (h == 0) { + int size = size(); + h = partialHash(size, 0, size); + if (h == 0) { + h = 1; + } + hash = h; + } + return h; + } + + // ================================================================= + // Input stream + + /** + * Creates an {@code InputStream} which can be used to read the bytes. + * <p> + * The {@link InputStream} returned by this method is guaranteed to be + * completely non-blocking. The method {@link InputStream#available()} + * returns the number of bytes remaining in the stream. The methods + * {@link InputStream#read(byte[])}, {@link InputStream#read(byte[],int,int)} + * and {@link InputStream#skip(long)} will read/skip as many bytes as are + * available. The method {@link InputStream#markSupported()} returns + * {@code true}. + * <p> + * The methods in the returned {@link InputStream} might <b>not</b> be + * thread safe. + * + * @return an input stream that returns the bytes of this byte string. + */ + public abstract InputStream newInput(); + + /** + * Creates a {@link CodedInputStream} which can be used to read the bytes. + * Using this is often more efficient than creating a {@link CodedInputStream} + * that wraps the result of {@link #newInput()}. + * + * @return stream based on wrapped data + */ + public abstract CodedInputStream newCodedInput(); + + // ================================================================= + // Output stream + + /** + * Creates a new {@link Output} with the given initial capacity. Call {@link + * Output#toByteString()} to create the {@code ByteString} instance. + * <p> + * A {@link ByteString.Output} offers the same functionality as a + * {@link ByteArrayOutputStream}, except that it returns a {@link ByteString} + * rather than a {@code byte} array. + * + * @param initialCapacity estimate of number of bytes to be written + * @return {@code OutputStream} for building a {@code ByteString} + */ + public static Output newOutput(int initialCapacity) { + return new Output(initialCapacity); + } + + /** + * Creates a new {@link Output}. Call {@link Output#toByteString()} to create + * the {@code ByteString} instance. + * <p> + * A {@link ByteString.Output} offers the same functionality as a + * {@link ByteArrayOutputStream}, except that it returns a {@link ByteString} + * rather than a {@code byte array}. + * + * @return {@code OutputStream} for building a {@code ByteString} + */ + public static Output newOutput() { + return new Output(CONCATENATE_BY_COPY_SIZE); + } + + /** + * Outputs to a {@code ByteString} instance. Call {@link #toByteString()} to + * create the {@code ByteString} instance. + */ + public static final class Output extends OutputStream { + // Implementation note. + // The public methods of this class must be synchronized. ByteStrings + // are guaranteed to be immutable. Without some sort of locking, it could + // be possible for one thread to call toByteSring(), while another thread + // is still modifying the underlying byte array. + + private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + // argument passed by user, indicating initial capacity. + private final int initialCapacity; + // ByteStrings to be concatenated to create the result + private final ArrayList<ByteString> flushedBuffers; + // Total number of bytes in the ByteStrings of flushedBuffers + private int flushedBuffersTotalBytes; + // Current buffer to which we are writing + private byte[] buffer; + // Location in buffer[] to which we write the next byte. + private int bufferPos; + + /** + * Creates a new ByteString output stream with the specified + * initial capacity. + * + * @param initialCapacity the initial capacity of the output stream. + */ + Output(int initialCapacity) { + if (initialCapacity < 0) { + throw new IllegalArgumentException("Buffer size < 0"); + } + this.initialCapacity = initialCapacity; + this.flushedBuffers = new ArrayList<ByteString>(); + this.buffer = new byte[initialCapacity]; + } + + @Override + public synchronized void write(int b) { + if (bufferPos == buffer.length) { + flushFullBuffer(1); + } + buffer[bufferPos++] = (byte)b; + } + + @Override + public synchronized void write(byte[] b, int offset, int length) { + if (length <= buffer.length - bufferPos) { + // The bytes can fit into the current buffer. + System.arraycopy(b, offset, buffer, bufferPos, length); + bufferPos += length; + } else { + // Use up the current buffer + int copySize = buffer.length - bufferPos; + System.arraycopy(b, offset, buffer, bufferPos, copySize); + offset += copySize; + length -= copySize; + // Flush the buffer, and get a new buffer at least big enough to cover + // what we still need to output + flushFullBuffer(length); + System.arraycopy(b, offset, buffer, 0 /* count */, length); + bufferPos = length; + } + } + + /** + * Creates a byte string. Its size is the current size of this output + * stream and its output has been copied to it. + * + * @return the current contents of this output stream, as a byte string. + */ + public synchronized ByteString toByteString() { + flushLastBuffer(); + return ByteString.copyFrom(flushedBuffers); + } + + /** + * Implement java.util.Arrays.copyOf() for jdk 1.5. + */ + private byte[] copyArray(byte[] buffer, int length) { + byte[] result = new byte[length]; + System.arraycopy(buffer, 0, result, 0, Math.min(buffer.length, length)); + return result; + } + + /** + * Writes the complete contents of this byte array output stream to + * the specified output stream argument. + * + * @param out the output stream to which to write the data. + * @throws IOException if an I/O error occurs. + */ + public void writeTo(OutputStream out) throws IOException { + ByteString[] cachedFlushBuffers; + byte[] cachedBuffer; + int cachedBufferPos; + synchronized (this) { + // Copy the information we need into local variables so as to hold + // the lock for as short a time as possible. + cachedFlushBuffers = + flushedBuffers.toArray(new ByteString[flushedBuffers.size()]); + cachedBuffer = buffer; + cachedBufferPos = bufferPos; + } + for (ByteString byteString : cachedFlushBuffers) { + byteString.writeTo(out); + } + + out.write(copyArray(cachedBuffer, cachedBufferPos)); + } + + /** + * Returns the current size of the output stream. + * + * @return the current size of the output stream + */ + public synchronized int size() { + return flushedBuffersTotalBytes + bufferPos; + } + + /** + * Resets this stream, so that all currently accumulated output in the + * output stream is discarded. The output stream can be used again, + * reusing the already allocated buffer space. + */ + public synchronized void reset() { + flushedBuffers.clear(); + flushedBuffersTotalBytes = 0; + bufferPos = 0; + } + + @Override + public String toString() { + return String.format("<ByteString.Output@%s size=%d>", + Integer.toHexString(System.identityHashCode(this)), size()); + } + + /** + * Internal function used by writers. The current buffer is full, and the + * writer needs a new buffer whose size is at least the specified minimum + * size. + */ + private void flushFullBuffer(int minSize) { + flushedBuffers.add(new LiteralByteString(buffer)); + flushedBuffersTotalBytes += buffer.length; + // We want to increase our total capacity by 50%, but as a minimum, + // the new buffer should also at least be >= minSize and + // >= initial Capacity. + int newSize = Math.max(initialCapacity, + Math.max(minSize, flushedBuffersTotalBytes >>> 1)); + buffer = new byte[newSize]; + bufferPos = 0; + } + + /** + * Internal function used by {@link #toByteString()}. The current buffer may + * or may not be full, but it needs to be flushed. + */ + private void flushLastBuffer() { + if (bufferPos < buffer.length) { + if (bufferPos > 0) { + byte[] bufferCopy = copyArray(buffer, bufferPos); + flushedBuffers.add(new LiteralByteString(bufferCopy)); + } + // We reuse this buffer for further writes. + } else { + // Buffer is completely full. Huzzah. + flushedBuffers.add(new LiteralByteString(buffer)); + // 99% of the time, we're not going to use this OutputStream again. + // We set buffer to an empty byte stream so that we're handling this + // case without wasting space. In the rare case that more writes + // *do* occur, this empty buffer will be flushed and an appropriately + // sized new buffer will be created. + buffer = EMPTY_BYTE_ARRAY; + } + flushedBuffersTotalBytes += bufferPos; + bufferPos = 0; + } + } + + /** + * Constructs a new {@code ByteString} builder, which allows you to + * efficiently construct a {@code ByteString} by writing to a {@link + * CodedOutputStream}. Using this is much more efficient than calling {@code + * newOutput()} and wrapping that in a {@code CodedOutputStream}. + * + * <p>This is package-private because it's a somewhat confusing interface. + * Users can call {@link Message#toByteString()} instead of calling this + * directly. + * + * @param size The target byte size of the {@code ByteString}. You must write + * exactly this many bytes before building the result. + * @return the builder + */ + static CodedBuilder newCodedBuilder(int size) { + return new CodedBuilder(size); + } + + /** See {@link ByteString#newCodedBuilder(int)}. */ + static final class CodedBuilder { + private final CodedOutputStream output; + private final byte[] buffer; + + private CodedBuilder(int size) { + buffer = new byte[size]; + output = CodedOutputStream.newInstance(buffer); + } + + public ByteString build() { + output.checkNoSpaceLeft(); + + // We can be confident that the CodedOutputStream will not modify the + // underlying bytes anymore because it already wrote all of them. So, + // no need to make a copy. + return new LiteralByteString(buffer); + } + + public CodedOutputStream getCodedOutput() { + return output; + } + } + + // ================================================================= + // Methods {@link RopeByteString} needs on instances, which aren't part of the + // public API. + + /** + * Return the depth of the tree representing this {@code ByteString}, if any, + * whose root is this node. If this is a leaf node, return 0. + * + * @return tree depth or zero + */ + protected abstract int getTreeDepth(); + + /** + * Return {@code true} if this ByteString is literal (a leaf node) or a + * flat-enough tree in the sense of {@link RopeByteString}. + * + * @return true if the tree is flat enough + */ + protected abstract boolean isBalanced(); + + /** + * Return the cached hash code if available. + * + * @return value of cached hash code or 0 if not computed yet + */ + protected final int peekCachedHashCode() { + return hash; + } + + /** + * Compute the hash across the value bytes starting with the given hash, and + * return the result. This is used to compute the hash across strings + * represented as a set of pieces by allowing the hash computation to be + * continued from piece to piece. + * + * @param h starting hash value + * @param offset offset into this value to start looking at data values + * @param length number of data values to include in the hash computation + * @return ending hash value + */ + protected abstract int partialHash(int h, int offset, int length); + + /** + * Checks that the given index falls within the specified array size. + * + * @param index the index position to be tested + * @param size the length of the array + * @throws ArrayIndexOutOfBoundsException if the index does not fall within the array. + */ + static void checkIndex(int index, int size) { + if ((index | (size - (index + 1))) < 0) { + if (index < 0) { + throw new ArrayIndexOutOfBoundsException("Index < 0: " + index); + } + throw new ArrayIndexOutOfBoundsException("Index > length: " + index + ", " + size); + } + } + + /** + * Checks that the given range falls within the bounds of an array + * + * @param startIndex the start index of the range (inclusive) + * @param endIndex the end index of the range (exclusive) + * @param size the size of the array. + * @return the length of the range. + * @throws ArrayIndexOutOfBoundsException some or all of the range falls outside of the array. + */ + static int checkRange(int startIndex, int endIndex, int size) { + final int length = endIndex - startIndex; + if ((startIndex | endIndex | length | (size - endIndex)) < 0) { + if (startIndex < 0) { + throw new IndexOutOfBoundsException("Beginning index: " + startIndex + " < 0"); + } + if (endIndex < startIndex) { + throw new IndexOutOfBoundsException( + "Beginning index larger than ending index: " + startIndex + ", " + endIndex); + } + // endIndex >= size + throw new IndexOutOfBoundsException("End index: " + endIndex + " >= " + size); + } + return length; + } + + @Override + public final String toString() { + return String.format("<ByteString@%s size=%d>", + Integer.toHexString(System.identityHashCode(this)), size()); + } +} diff --git a/java/core/src/main/java/com/google/protobuf/CodedInputStream.java b/java/core/src/main/java/com/google/protobuf/CodedInputStream.java new file mode 100644 index 00000000..adc91536 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/CodedInputStream.java @@ -0,0 +1,1308 @@ +// 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.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Reads and decodes protocol message fields. + * + * This class contains two kinds of methods: methods that read specific + * protocol message constructs and field types (e.g. {@link #readTag()} and + * {@link #readInt32()}) and methods that read low-level values (e.g. + * {@link #readRawVarint32()} and {@link #readRawBytes}). If you are reading + * encoded protocol messages, you should use the former methods, but if you are + * reading some other format of your own design, use the latter. + * + * @author kenton@google.com Kenton Varda + */ +public final class CodedInputStream { + /** + * Create a new CodedInputStream wrapping the given InputStream. + */ + public static CodedInputStream newInstance(final InputStream input) { + return new CodedInputStream(input); + } + + /** + * Create a new CodedInputStream wrapping the given byte array. + */ + public static CodedInputStream newInstance(final byte[] buf) { + return newInstance(buf, 0, buf.length); + } + + /** + * Create a new CodedInputStream wrapping the given byte array slice. + */ + public static CodedInputStream newInstance(final byte[] buf, final int off, + final int len) { + CodedInputStream result = new CodedInputStream(buf, off, len); + try { + // Some uses of CodedInputStream can be more efficient if they know + // exactly how many bytes are available. By pushing the end point of the + // buffer as a limit, we allow them to get this information via + // getBytesUntilLimit(). Pushing a limit that we know is at the end of + // the stream can never hurt, since we can never past that point anyway. + result.pushLimit(len); + } catch (InvalidProtocolBufferException ex) { + // The only reason pushLimit() might throw an exception here is if len + // is negative. Normally pushLimit()'s parameter comes directly off the + // wire, so it's important to catch exceptions in case of corrupt or + // malicious data. However, in this case, we expect that len is not a + // user-supplied value, so we can assume that it being negative indicates + // a programming error. Therefore, throwing an unchecked exception is + // appropriate. + throw new IllegalArgumentException(ex); + } + return result; + } + + /** + * Create a new CodedInputStream wrapping the given ByteBuffer. The data + * starting from the ByteBuffer's current position to its limit will be read. + * The returned CodedInputStream may or may not share the underlying data + * in the ByteBuffer, therefore the ByteBuffer cannot be changed while the + * CodedInputStream is in use. + * Note that the ByteBuffer's position won't be changed by this function. + * Concurrent calls with the same ByteBuffer object are safe if no other + * thread is trying to alter the ByteBuffer's status. + */ + public static CodedInputStream newInstance(ByteBuffer buf) { + if (buf.hasArray()) { + return newInstance(buf.array(), buf.arrayOffset() + buf.position(), + buf.remaining()); + } else { + ByteBuffer temp = buf.duplicate(); + byte[] buffer = new byte[temp.remaining()]; + temp.get(buffer); + return newInstance(buffer); + } + } + + /** + * Create a new CodedInputStream wrapping a LiteralByteString. + */ + static CodedInputStream newInstance(LiteralByteString byteString) { + CodedInputStream result = new CodedInputStream(byteString); + try { + // Some uses of CodedInputStream can be more efficient if they know + // exactly how many bytes are available. By pushing the end point of the + // buffer as a limit, we allow them to get this information via + // getBytesUntilLimit(). Pushing a limit that we know is at the end of + // the stream can never hurt, since we can never past that point anyway. + result.pushLimit(byteString.size()); + } catch (InvalidProtocolBufferException ex) { + // The only reason pushLimit() might throw an exception here is if len + // is negative. Normally pushLimit()'s parameter comes directly off the + // wire, so it's important to catch exceptions in case of corrupt or + // malicious data. However, in this case, we expect that len is not a + // user-supplied value, so we can assume that it being negative indicates + // a programming error. Therefore, throwing an unchecked exception is + // appropriate. + throw new IllegalArgumentException(ex); + } + return result; + } + + // ----------------------------------------------------------------- + + /** + * Attempt to read a field tag, returning zero if we have reached EOF. + * Protocol message parsers use this to read tags, since a protocol message + * may legally end wherever a tag occurs, and zero is not a valid tag number. + */ + public int readTag() throws IOException { + if (isAtEnd()) { + lastTag = 0; + return 0; + } + + lastTag = readRawVarint32(); + if (WireFormat.getTagFieldNumber(lastTag) == 0) { + // If we actually read zero (or any tag number corresponding to field + // number zero), that's not a valid tag. + throw InvalidProtocolBufferException.invalidTag(); + } + return lastTag; + } + + /** + * Verifies that the last call to readTag() returned the given tag value. + * This is used to verify that a nested group ended with the correct + * end tag. + * + * @throws InvalidProtocolBufferException {@code value} does not match the + * last tag. + */ + public void checkLastTagWas(final int value) + throws InvalidProtocolBufferException { + if (lastTag != value) { + throw InvalidProtocolBufferException.invalidEndTag(); + } + } + + public int getLastTag() { + return lastTag; + } + + /** + * Reads and discards a single field, given its tag value. + * + * @return {@code false} if the tag is an endgroup tag, in which case + * nothing is skipped. Otherwise, returns {@code true}. + */ + public boolean skipField(final int tag) throws IOException { + switch (WireFormat.getTagWireType(tag)) { + case WireFormat.WIRETYPE_VARINT: + skipRawVarint(); + return true; + case WireFormat.WIRETYPE_FIXED64: + skipRawBytes(8); + return true; + case WireFormat.WIRETYPE_LENGTH_DELIMITED: + skipRawBytes(readRawVarint32()); + return true; + case WireFormat.WIRETYPE_START_GROUP: + skipMessage(); + checkLastTagWas( + WireFormat.makeTag(WireFormat.getTagFieldNumber(tag), + WireFormat.WIRETYPE_END_GROUP)); + return true; + case WireFormat.WIRETYPE_END_GROUP: + return false; + case WireFormat.WIRETYPE_FIXED32: + skipRawBytes(4); + return true; + default: + throw InvalidProtocolBufferException.invalidWireType(); + } + } + + /** + * Reads a single field and writes it to output in wire format, + * given its tag value. + * + * @return {@code false} if the tag is an endgroup tag, in which case + * nothing is skipped. Otherwise, returns {@code true}. + */ + public boolean skipField(final int tag, final CodedOutputStream output) + throws IOException { + switch (WireFormat.getTagWireType(tag)) { + case WireFormat.WIRETYPE_VARINT: { + long value = readInt64(); + output.writeRawVarint32(tag); + output.writeUInt64NoTag(value); + return true; + } + case WireFormat.WIRETYPE_FIXED64: { + long value = readRawLittleEndian64(); + output.writeRawVarint32(tag); + output.writeFixed64NoTag(value); + return true; + } + case WireFormat.WIRETYPE_LENGTH_DELIMITED: { + ByteString value = readBytes(); + output.writeRawVarint32(tag); + output.writeBytesNoTag(value); + return true; + } + case WireFormat.WIRETYPE_START_GROUP: { + output.writeRawVarint32(tag); + skipMessage(output); + int endtag = WireFormat.makeTag(WireFormat.getTagFieldNumber(tag), + WireFormat.WIRETYPE_END_GROUP); + checkLastTagWas(endtag); + output.writeRawVarint32(endtag); + return true; + } + case WireFormat.WIRETYPE_END_GROUP: { + return false; + } + case WireFormat.WIRETYPE_FIXED32: { + int value = readRawLittleEndian32(); + output.writeRawVarint32(tag); + output.writeFixed32NoTag(value); + return true; + } + default: + throw InvalidProtocolBufferException.invalidWireType(); + } + } + + /** + * Reads and discards an entire message. This will read either until EOF + * or until an endgroup tag, whichever comes first. + */ + public void skipMessage() throws IOException { + while (true) { + final int tag = readTag(); + if (tag == 0 || !skipField(tag)) { + return; + } + } + } + + /** + * Reads an entire message and writes it to output in wire format. + * This will read either until EOF or until an endgroup tag, + * whichever comes first. + */ + public void skipMessage(CodedOutputStream output) throws IOException { + while (true) { + final int tag = readTag(); + if (tag == 0 || !skipField(tag, output)) { + return; + } + } + } + + /** + * Collects the bytes skipped and returns the data in a ByteBuffer. + */ + private class SkippedDataSink implements RefillCallback { + private int lastPos = bufferPos; + private ByteArrayOutputStream byteArrayStream; + + @Override + public void onRefill() { + if (byteArrayStream == null) { + byteArrayStream = new ByteArrayOutputStream(); + } + byteArrayStream.write(buffer, lastPos, bufferPos - lastPos); + lastPos = 0; + } + + /** + * Gets skipped data in a ByteBuffer. This method should only be + * called once. + */ + ByteBuffer getSkippedData() { + if (byteArrayStream == null) { + return ByteBuffer.wrap(buffer, lastPos, bufferPos - lastPos); + } else { + byteArrayStream.write(buffer, lastPos, bufferPos); + return ByteBuffer.wrap(byteArrayStream.toByteArray()); + } + } + } + + + // ----------------------------------------------------------------- + + /** Read a {@code double} field value from the stream. */ + public double readDouble() throws IOException { + return Double.longBitsToDouble(readRawLittleEndian64()); + } + + /** Read a {@code float} field value from the stream. */ + public float readFloat() throws IOException { + return Float.intBitsToFloat(readRawLittleEndian32()); + } + + /** Read a {@code uint64} field value from the stream. */ + public long readUInt64() throws IOException { + return readRawVarint64(); + } + + /** Read an {@code int64} field value from the stream. */ + public long readInt64() throws IOException { + return readRawVarint64(); + } + + /** Read an {@code int32} field value from the stream. */ + public int readInt32() throws IOException { + return readRawVarint32(); + } + + /** Read a {@code fixed64} field value from the stream. */ + public long readFixed64() throws IOException { + return readRawLittleEndian64(); + } + + /** Read a {@code fixed32} field value from the stream. */ + public int readFixed32() throws IOException { + return readRawLittleEndian32(); + } + + /** Read a {@code bool} field value from the stream. */ + public boolean readBool() throws IOException { + return readRawVarint64() != 0; + } + + /** + * Read a {@code string} field value from the stream. + * If the stream contains malformed UTF-8, + * replace the offending bytes with the standard UTF-8 replacement character. + */ + public String readString() throws IOException { + final int size = readRawVarint32(); + if (size <= (bufferSize - bufferPos) && size > 0) { + // Fast path: We already have the bytes in a contiguous buffer, so + // just copy directly from it. + final String result = new String(buffer, bufferPos, size, Internal.UTF_8); + bufferPos += size; + return result; + } else if (size == 0) { + return ""; + } else { + // Slow path: Build a byte array first then copy it. + return new String(readRawBytesSlowPath(size), Internal.UTF_8); + } + } + + /** + * Read a {@code string} field value from the stream. + * If the stream contains malformed UTF-8, + * throw exception {@link InvalidProtocolBufferException}. + */ + public String readStringRequireUtf8() throws IOException { + final int size = readRawVarint32(); + final byte[] bytes; + int pos = bufferPos; + if (size <= (bufferSize - pos) && size > 0) { + // Fast path: We already have the bytes in a contiguous buffer, so + // just copy directly from it. + bytes = buffer; + bufferPos = pos + size; + } else if (size == 0) { + return ""; + } else { + // Slow path: Build a byte array first then copy it. + bytes = readRawBytesSlowPath(size); + pos = 0; + } + // TODO(martinrb): We could save a pass by validating while decoding. + if (!Utf8.isValidUtf8(bytes, pos, pos + size)) { + throw InvalidProtocolBufferException.invalidUtf8(); + } + return new String(bytes, pos, size, Internal.UTF_8); + } + + /** Read a {@code group} field value from the stream. */ + public void readGroup(final int fieldNumber, + final MessageLite.Builder builder, + final ExtensionRegistryLite extensionRegistry) + throws IOException { + if (recursionDepth >= recursionLimit) { + throw InvalidProtocolBufferException.recursionLimitExceeded(); + } + ++recursionDepth; + builder.mergeFrom(this, extensionRegistry); + checkLastTagWas( + WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP)); + --recursionDepth; + } + + + /** Read a {@code group} field value from the stream. */ + public <T extends MessageLite> T readGroup( + final int fieldNumber, + final Parser<T> parser, + final ExtensionRegistryLite extensionRegistry) + throws IOException { + if (recursionDepth >= recursionLimit) { + throw InvalidProtocolBufferException.recursionLimitExceeded(); + } + ++recursionDepth; + T result = parser.parsePartialFrom(this, extensionRegistry); + checkLastTagWas( + WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP)); + --recursionDepth; + return result; + } + + /** + * Reads a {@code group} field value from the stream and merges it into the + * given {@link UnknownFieldSet}. + * + * @deprecated UnknownFieldSet.Builder now implements MessageLite.Builder, so + * you can just call {@link #readGroup}. + */ + @Deprecated + public void readUnknownGroup(final int fieldNumber, + final MessageLite.Builder builder) + throws IOException { + // We know that UnknownFieldSet will ignore any ExtensionRegistry so it + // is safe to pass null here. (We can't call + // ExtensionRegistry.getEmptyRegistry() because that would make this + // class depend on ExtensionRegistry, which is not part of the lite + // library.) + readGroup(fieldNumber, builder, null); + } + + /** Read an embedded message field value from the stream. */ + public void readMessage(final MessageLite.Builder builder, + final ExtensionRegistryLite extensionRegistry) + throws IOException { + final int length = readRawVarint32(); + if (recursionDepth >= recursionLimit) { + throw InvalidProtocolBufferException.recursionLimitExceeded(); + } + final int oldLimit = pushLimit(length); + ++recursionDepth; + builder.mergeFrom(this, extensionRegistry); + checkLastTagWas(0); + --recursionDepth; + popLimit(oldLimit); + } + + + /** Read an embedded message field value from the stream. */ + public <T extends MessageLite> T readMessage( + final Parser<T> parser, + final ExtensionRegistryLite extensionRegistry) + throws IOException { + int length = readRawVarint32(); + if (recursionDepth >= recursionLimit) { + throw InvalidProtocolBufferException.recursionLimitExceeded(); + } + final int oldLimit = pushLimit(length); + ++recursionDepth; + T result = parser.parsePartialFrom(this, extensionRegistry); + checkLastTagWas(0); + --recursionDepth; + popLimit(oldLimit); + return result; + } + + /** Read a {@code bytes} field value from the stream. */ + public ByteString readBytes() throws IOException { + final int size = readRawVarint32(); + if (size <= (bufferSize - bufferPos) && size > 0) { + // Fast path: We already have the bytes in a contiguous buffer, so + // just copy directly from it. + final ByteString result = bufferIsImmutable && enableAliasing + ? new BoundedByteString(buffer, bufferPos, size) + : ByteString.copyFrom(buffer, bufferPos, size); + bufferPos += size; + return result; + } else if (size == 0) { + return ByteString.EMPTY; + } else { + // Slow path: Build a byte array first then copy it. + return new LiteralByteString(readRawBytesSlowPath(size)); + } + } + + /** Read a {@code bytes} field value from the stream. */ + public byte[] readByteArray() throws IOException { + final int size = readRawVarint32(); + if (size <= (bufferSize - bufferPos) && size > 0) { + // Fast path: We already have the bytes in a contiguous buffer, so + // just copy directly from it. + final byte[] result = + Arrays.copyOfRange(buffer, bufferPos, bufferPos + size); + bufferPos += size; + return result; + } else { + // Slow path: Build a byte array first then copy it. + return readRawBytesSlowPath(size); + } + } + + /** Read a {@code bytes} field value from the stream. */ + public ByteBuffer readByteBuffer() throws IOException { + final int size = readRawVarint32(); + if (size <= (bufferSize - bufferPos) && size > 0) { + // Fast path: We already have the bytes in a contiguous buffer. + // When aliasing is enabled, we can return a ByteBuffer pointing directly + // into the underlying byte array without copy if the CodedInputStream is + // constructed from a byte array. If aliasing is disabled or the input is + // from an InputStream or ByteString, we have to make a copy of the bytes. + ByteBuffer result = input == null && !bufferIsImmutable && enableAliasing + ? ByteBuffer.wrap(buffer, bufferPos, size).slice() + : ByteBuffer.wrap(Arrays.copyOfRange( + buffer, bufferPos, bufferPos + size)); + bufferPos += size; + return result; + } else if (size == 0) { + return Internal.EMPTY_BYTE_BUFFER; + } else { + // Slow path: Build a byte array first then copy it. + return ByteBuffer.wrap(readRawBytesSlowPath(size)); + } + } + + /** Read a {@code uint32} field value from the stream. */ + public int readUInt32() throws IOException { + return readRawVarint32(); + } + + /** + * Read an enum field value from the stream. Caller is responsible + * for converting the numeric value to an actual enum. + */ + public int readEnum() throws IOException { + return readRawVarint32(); + } + + /** Read an {@code sfixed32} field value from the stream. */ + public int readSFixed32() throws IOException { + return readRawLittleEndian32(); + } + + /** Read an {@code sfixed64} field value from the stream. */ + public long readSFixed64() throws IOException { + return readRawLittleEndian64(); + } + + /** Read an {@code sint32} field value from the stream. */ + public int readSInt32() throws IOException { + return decodeZigZag32(readRawVarint32()); + } + + /** Read an {@code sint64} field value from the stream. */ + public long readSInt64() throws IOException { + return decodeZigZag64(readRawVarint64()); + } + + // ================================================================= + + /** + * Read a raw Varint from the stream. If larger than 32 bits, discard the + * upper bits. + */ + public int readRawVarint32() throws IOException { + // See implementation notes for readRawVarint64 + fastpath: { + int pos = bufferPos; + + if (bufferSize == pos) { + break fastpath; + } + + final byte[] buffer = this.buffer; + int x; + if ((x = buffer[pos++]) >= 0) { + bufferPos = pos; + return x; + } else if (bufferSize - pos < 9) { + break fastpath; + } 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 ^= (~0 << 7) ^ (~0 << 14) ^ (~0 << 21) ^ (~0 << 28); + if (y < 0 && + buffer[pos++] < 0 && + buffer[pos++] < 0 && + buffer[pos++] < 0 && + buffer[pos++] < 0 && + buffer[pos++] < 0) { + break fastpath; // Will throw malformedVarint() + } + } + bufferPos = pos; + return x; + } + return (int) readRawVarint64SlowPath(); + } + + private void skipRawVarint() throws IOException { + if (bufferSize - bufferPos >= 10) { + final byte[] buffer = this.buffer; + int pos = bufferPos; + for (int i = 0; i < 10; i++) { + if (buffer[pos++] >= 0) { + bufferPos = pos; + return; + } + } + } + skipRawVarintSlowPath(); + } + + private void skipRawVarintSlowPath() throws IOException { + for (int i = 0; i < 10; i++) { + if (readRawByte() >= 0) { + return; + } + } + throw InvalidProtocolBufferException.malformedVarint(); + } + + /** + * Reads a varint from the input one byte at a time, so that it does not + * read any bytes after the end of the varint. If you simply wrapped the + * stream in a CodedInputStream and used {@link #readRawVarint32(InputStream)} + * then you would probably end up reading past the end of the varint since + * CodedInputStream buffers its input. + */ + static int readRawVarint32(final InputStream input) throws IOException { + final int firstByte = input.read(); + if (firstByte == -1) { + throw InvalidProtocolBufferException.truncatedMessage(); + } + return readRawVarint32(firstByte, input); + } + + /** + * Like {@link #readRawVarint32(InputStream)}, but expects that the caller + * has already read one byte. This allows the caller to determine if EOF + * has been reached before attempting to read. + */ + public static int readRawVarint32( + final int firstByte, final InputStream input) throws IOException { + if ((firstByte & 0x80) == 0) { + return firstByte; + } + + int result = firstByte & 0x7f; + int offset = 7; + for (; offset < 32; offset += 7) { + final int b = input.read(); + if (b == -1) { + throw InvalidProtocolBufferException.truncatedMessage(); + } + result |= (b & 0x7f) << offset; + if ((b & 0x80) == 0) { + return result; + } + } + // Keep reading up to 64 bits. + for (; offset < 64; offset += 7) { + final int b = input.read(); + if (b == -1) { + throw InvalidProtocolBufferException.truncatedMessage(); + } + if ((b & 0x80) == 0) { + return result; + } + } + throw InvalidProtocolBufferException.malformedVarint(); + } + + /** Read a raw Varint from the stream. */ + public long readRawVarint64() throws IOException { + // Implementation notes: + // + // Optimized for one-byte values, expected to be common. + // The particular code below was selected from various candidates + // empirically, by winning VarintBenchmark. + // + // Sign extension of (signed) Java bytes is usually a nuisance, but + // we exploit it here to more easily obtain the sign of bytes read. + // Instead of cleaning up the sign extension bits by masking eagerly, + // we delay until we find the final (positive) byte, when we clear all + // accumulated bits with one xor. We depend on javac to constant fold. + fastpath: { + int pos = bufferPos; + + if (bufferSize == pos) { + break fastpath; + } + + final byte[] buffer = this.buffer; + long x; + int y; + if ((y = buffer[pos++]) >= 0) { + bufferPos = pos; + return y; + } else if (bufferSize - pos < 9) { + break fastpath; + } 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); + } else if ((x ^= ((long) buffer[pos++] << 42)) >= 0L) { + x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35) ^ (~0L << 42); + } else if ((x ^= ((long) buffer[pos++] << 49)) < 0L) { + x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35) ^ (~0L << 42) + ^ (~0L << 49); + } else { + x ^= ((long) buffer[pos++] << 56); + x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35) ^ (~0L << 42) + ^ (~0L << 49) ^ (~0L << 56); + if (x < 0L) { + if (buffer[pos++] < 0L) { + break fastpath; // Will throw malformedVarint() + } + } + } + bufferPos = pos; + return x; + } + return readRawVarint64SlowPath(); + } + + /** Variant of readRawVarint64 for when uncomfortably close to the limit. */ + /* Visible for testing */ + long readRawVarint64SlowPath() throws IOException { + long result = 0; + for (int shift = 0; shift < 64; shift += 7) { + final byte b = readRawByte(); + result |= (long) (b & 0x7F) << shift; + if ((b & 0x80) == 0) { + return result; + } + } + throw InvalidProtocolBufferException.malformedVarint(); + } + + /** Read a 32-bit little-endian integer from the stream. */ + public int readRawLittleEndian32() throws IOException { + int pos = bufferPos; + + // hand-inlined ensureAvailable(4); + if (bufferSize - pos < 4) { + refillBuffer(4); + pos = bufferPos; + } + + final byte[] buffer = this.buffer; + bufferPos = pos + 4; + return (((buffer[pos] & 0xff)) | + ((buffer[pos + 1] & 0xff) << 8) | + ((buffer[pos + 2] & 0xff) << 16) | + ((buffer[pos + 3] & 0xff) << 24)); + } + + /** Read a 64-bit little-endian integer from the stream. */ + public long readRawLittleEndian64() throws IOException { + int pos = bufferPos; + + // hand-inlined ensureAvailable(8); + if (bufferSize - pos < 8) { + refillBuffer(8); + pos = bufferPos; + } + + final byte[] buffer = this.buffer; + bufferPos = pos + 8; + return ((((long) buffer[pos] & 0xffL)) | + (((long) buffer[pos + 1] & 0xffL) << 8) | + (((long) buffer[pos + 2] & 0xffL) << 16) | + (((long) buffer[pos + 3] & 0xffL) << 24) | + (((long) buffer[pos + 4] & 0xffL) << 32) | + (((long) buffer[pos + 5] & 0xffL) << 40) | + (((long) buffer[pos + 6] & 0xffL) << 48) | + (((long) buffer[pos + 7] & 0xffL) << 56)); + } + + /** + * Decode a ZigZag-encoded 32-bit value. ZigZag encodes signed integers + * into values that can be efficiently encoded with varint. (Otherwise, + * negative values must be sign-extended to 64 bits to be varint encoded, + * thus always taking 10 bytes on the wire.) + * + * @param n An unsigned 32-bit integer, stored in a signed int because + * Java has no explicit unsigned support. + * @return A signed 32-bit integer. + */ + public static int decodeZigZag32(final int n) { + return (n >>> 1) ^ -(n & 1); + } + + /** + * Decode a ZigZag-encoded 64-bit value. ZigZag encodes signed integers + * into values that can be efficiently encoded with varint. (Otherwise, + * negative values must be sign-extended to 64 bits to be varint encoded, + * thus always taking 10 bytes on the wire.) + * + * @param n An unsigned 64-bit integer, stored in a signed int because + * Java has no explicit unsigned support. + * @return A signed 64-bit integer. + */ + public static long decodeZigZag64(final long n) { + return (n >>> 1) ^ -(n & 1); + } + + // ----------------------------------------------------------------- + + private final byte[] buffer; + private final boolean bufferIsImmutable; + private int bufferSize; + private int bufferSizeAfterLimit; + private int bufferPos; + private final InputStream input; + private int lastTag; + private boolean enableAliasing = false; + + /** + * The total number of bytes read before the current buffer. The total + * bytes read up to the current position can be computed as + * {@code totalBytesRetired + bufferPos}. This value may be negative if + * reading started in the middle of the current buffer (e.g. if the + * constructor that takes a byte array and an offset was used). + */ + private int totalBytesRetired; + + /** The absolute position of the end of the current message. */ + private int currentLimit = Integer.MAX_VALUE; + + /** See setRecursionLimit() */ + private int recursionDepth; + private int recursionLimit = DEFAULT_RECURSION_LIMIT; + + /** See setSizeLimit() */ + private int sizeLimit = DEFAULT_SIZE_LIMIT; + + 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; + + private CodedInputStream(final byte[] buffer, final int off, final int len) { + this.buffer = buffer; + bufferSize = off + len; + bufferPos = off; + totalBytesRetired = -off; + input = null; + bufferIsImmutable = false; + } + + private CodedInputStream(final InputStream input) { + buffer = new byte[BUFFER_SIZE]; + bufferSize = 0; + bufferPos = 0; + totalBytesRetired = 0; + this.input = input; + bufferIsImmutable = false; + } + + private CodedInputStream(final LiteralByteString byteString) { + buffer = byteString.bytes; + bufferPos = byteString.getOffsetIntoBytes(); + bufferSize = bufferPos + byteString.size(); + totalBytesRetired = -bufferPos; + input = null; + bufferIsImmutable = true; + } + + public void enableAliasing(boolean enabled) { + this.enableAliasing = enabled; + } + + /** + * Set the maximum message recursion depth. In order to prevent malicious + * messages from causing stack overflows, {@code CodedInputStream} limits + * how deeply messages may be nested. The default limit is 64. + * + * @return the old limit. + */ + public int setRecursionLimit(final int limit) { + if (limit < 0) { + throw new IllegalArgumentException( + "Recursion limit cannot be negative: " + limit); + } + final int oldLimit = recursionLimit; + recursionLimit = limit; + return oldLimit; + } + + /** + * Set the maximum message size. In order to prevent malicious + * messages from exhausting memory or causing integer overflows, + * {@code CodedInputStream} limits how large a message may be. + * The default limit is 64MB. You should set this limit as small + * as you can without harming your app's functionality. Note that + * size limits only apply when reading from an {@code InputStream}, not + * when constructed around a raw byte array (nor with + * {@link ByteString#newCodedInput}). + * <p> + * If you want to read several messages from a single CodedInputStream, you + * could call {@link #resetSizeCounter()} after each one to avoid hitting the + * size limit. + * + * @return the old limit. + */ + public int setSizeLimit(final int limit) { + if (limit < 0) { + throw new IllegalArgumentException( + "Size limit cannot be negative: " + limit); + } + final int oldLimit = sizeLimit; + sizeLimit = limit; + return oldLimit; + } + + /** + * Resets the current size counter to zero (see {@link #setSizeLimit(int)}). + */ + public void resetSizeCounter() { + totalBytesRetired = -bufferPos; + } + + /** + * Sets {@code currentLimit} to (current position) + {@code byteLimit}. This + * is called when descending into a length-delimited embedded message. + * + * <p>Note that {@code pushLimit()} does NOT affect how many bytes the + * {@code CodedInputStream} reads from an underlying {@code InputStream} when + * refreshing its buffer. If you need to prevent reading past a certain + * point in the underlying {@code InputStream} (e.g. because you expect it to + * contain more data after the end of the message which you need to handle + * differently) then you must place a wrapper around your {@code InputStream} + * which limits the amount of data that can be read from it. + * + * @return the old limit. + */ + public int pushLimit(int byteLimit) throws InvalidProtocolBufferException { + if (byteLimit < 0) { + throw InvalidProtocolBufferException.negativeSize(); + } + byteLimit += totalBytesRetired + bufferPos; + final int oldLimit = currentLimit; + if (byteLimit > oldLimit) { + throw InvalidProtocolBufferException.truncatedMessage(); + } + currentLimit = byteLimit; + + recomputeBufferSizeAfterLimit(); + + return oldLimit; + } + + private void recomputeBufferSizeAfterLimit() { + bufferSize += bufferSizeAfterLimit; + final int bufferEnd = totalBytesRetired + bufferSize; + if (bufferEnd > currentLimit) { + // Limit is in current buffer. + bufferSizeAfterLimit = bufferEnd - currentLimit; + bufferSize -= bufferSizeAfterLimit; + } else { + bufferSizeAfterLimit = 0; + } + } + + /** + * Discards the current limit, returning to the previous limit. + * + * @param oldLimit The old limit, as returned by {@code pushLimit}. + */ + public void popLimit(final int oldLimit) { + currentLimit = oldLimit; + recomputeBufferSizeAfterLimit(); + } + + /** + * Returns the number of bytes to be read before the current limit. + * If no limit is set, returns -1. + */ + public int getBytesUntilLimit() { + if (currentLimit == Integer.MAX_VALUE) { + return -1; + } + + final int currentAbsolutePosition = totalBytesRetired + bufferPos; + return currentLimit - currentAbsolutePosition; + } + + /** + * Returns true if the stream has reached the end of the input. This is the + * case if either the end of the underlying input source has been reached or + * if the stream has reached a limit created using {@link #pushLimit(int)}. + */ + public boolean isAtEnd() throws IOException { + return bufferPos == bufferSize && !tryRefillBuffer(1); + } + + /** + * The total bytes read up to the current position. Calling + * {@link #resetSizeCounter()} resets this value to zero. + */ + public int getTotalBytesRead() { + return totalBytesRetired + bufferPos; + } + + private interface RefillCallback { + void onRefill(); + } + + private RefillCallback refillCallback = null; + + /** + * Reads more bytes from the input, making at least {@code n} bytes available + * in the buffer. Caller must ensure that the requested space is not yet + * available, and that the requested space is less than BUFFER_SIZE. + * + * @throws InvalidProtocolBufferException The end of the stream or the current + * limit was reached. + */ + private void refillBuffer(int n) throws IOException { + if (!tryRefillBuffer(n)) { + throw InvalidProtocolBufferException.truncatedMessage(); + } + } + + /** + * Tries to read more bytes from the input, making at least {@code n} bytes + * available in the buffer. Caller must ensure that the requested space is + * not yet available, and that the requested space is less than BUFFER_SIZE. + * + * @return {@code true} if the bytes could be made available; {@code false} + * if the end of the stream or the current limit was reached. + */ + private boolean tryRefillBuffer(int n) throws IOException { + if (bufferPos + n <= bufferSize) { + throw new IllegalStateException( + "refillBuffer() called when " + n + + " bytes were already available in buffer"); + } + + if (totalBytesRetired + bufferPos + n > currentLimit) { + // Oops, we hit a limit. + return false; + } + + if (refillCallback != null) { + refillCallback.onRefill(); + } + + if (input != null) { + int pos = bufferPos; + if (pos > 0) { + if (bufferSize > pos) { + System.arraycopy(buffer, pos, buffer, 0, bufferSize - pos); + } + totalBytesRetired += pos; + bufferSize -= pos; + bufferPos = 0; + } + + int bytesRead = input.read(buffer, bufferSize, buffer.length - bufferSize); + if (bytesRead == 0 || bytesRead < -1 || bytesRead > buffer.length) { + throw new IllegalStateException( + "InputStream#read(byte[]) returned invalid result: " + bytesRead + + "\nThe InputStream implementation is buggy."); + } + if (bytesRead > 0) { + bufferSize += bytesRead; + // Integer-overflow-conscious check against sizeLimit + if (totalBytesRetired + n - sizeLimit > 0) { + throw InvalidProtocolBufferException.sizeLimitExceeded(); + } + recomputeBufferSizeAfterLimit(); + return (bufferSize >= n) ? true : tryRefillBuffer(n); + } + } + + return false; + } + + /** + * Read one byte from the input. + * + * @throws InvalidProtocolBufferException The end of the stream or the current + * limit was reached. + */ + public byte readRawByte() throws IOException { + if (bufferPos == bufferSize) { + refillBuffer(1); + } + return buffer[bufferPos++]; + } + + /** + * Read a fixed size of bytes from the input. + * + * @throws InvalidProtocolBufferException The end of the stream or the current + * limit was reached. + */ + public byte[] readRawBytes(final int size) throws IOException { + final int pos = bufferPos; + if (size <= (bufferSize - pos) && size > 0) { + bufferPos = pos + size; + return Arrays.copyOfRange(buffer, pos, pos + size); + } else { + return readRawBytesSlowPath(size); + } + } + + /** + * Exactly like readRawBytes, but caller must have already checked the fast + * path: (size <= (bufferSize - pos) && size > 0) + */ + private byte[] readRawBytesSlowPath(final int size) throws IOException { + if (size <= 0) { + if (size == 0) { + return Internal.EMPTY_BYTE_ARRAY; + } else { + throw InvalidProtocolBufferException.negativeSize(); + } + } + + // Verify that the message size so far has not exceeded sizeLimit. + int currentMessageSize = totalBytesRetired + bufferPos + size; + if (currentMessageSize > sizeLimit) { + throw InvalidProtocolBufferException.sizeLimitExceeded(); + } + + // Verify that the message size so far has not exceeded currentLimit. + if (currentMessageSize > currentLimit) { + // Read to the end of the stream anyway. + skipRawBytes(currentLimit - totalBytesRetired - bufferPos); + throw InvalidProtocolBufferException.truncatedMessage(); + } + + // We need the input stream to proceed. + if (input == null) { + throw InvalidProtocolBufferException.truncatedMessage(); + } + + final int originalBufferPos = bufferPos; + final int bufferedBytes = bufferSize - bufferPos; + + // Mark the current buffer consumed. + totalBytesRetired += bufferSize; + bufferPos = 0; + bufferSize = 0; + + // Determine the number of bytes we need to read from the input stream. + int sizeLeft = size - bufferedBytes; + // TODO(nathanmittler): Consider using a value larger than BUFFER_SIZE. + if (sizeLeft < BUFFER_SIZE || sizeLeft <= input.available()) { + // Either the bytes we need are known to be available, or the required buffer is + // within an allowed threshold - go ahead and allocate the buffer now. + final byte[] bytes = new byte[size]; + + // Copy all of the buffered bytes to the result buffer. + System.arraycopy(buffer, originalBufferPos, bytes, 0, bufferedBytes); + + // Fill the remaining bytes from the input stream. + int pos = bufferedBytes; + while (pos < bytes.length) { + int n = input.read(bytes, pos, size - pos); + if (n == -1) { + throw InvalidProtocolBufferException.truncatedMessage(); + } + totalBytesRetired += n; + pos += n; + } + + return bytes; + } + + // The size is very large. For security reasons, we can't allocate the + // entire byte array yet. The size comes directly from the input, so a + // maliciously-crafted message could provide a bogus very large size in + // order to trick the app into allocating a lot of memory. We avoid this + // by allocating and reading only a small chunk at a time, so that the + // malicious message must actually *be* extremely large to cause + // problems. Meanwhile, we limit the allowed size of a message elsewhere. + final List<byte[]> chunks = new ArrayList<byte[]>(); + + while (sizeLeft > 0) { + // TODO(nathanmittler): Consider using a value larger than BUFFER_SIZE. + final byte[] chunk = new byte[Math.min(sizeLeft, BUFFER_SIZE)]; + int pos = 0; + while (pos < chunk.length) { + final int n = input.read(chunk, pos, chunk.length - pos); + if (n == -1) { + throw InvalidProtocolBufferException.truncatedMessage(); + } + totalBytesRetired += n; + pos += n; + } + sizeLeft -= chunk.length; + chunks.add(chunk); + } + + // OK, got everything. Now concatenate it all into one buffer. + final byte[] bytes = new byte[size]; + + // Start by copying the leftover bytes from this.buffer. + System.arraycopy(buffer, originalBufferPos, bytes, 0, bufferedBytes); + + // And now all the chunks. + int pos = bufferedBytes; + for (final byte[] chunk : chunks) { + System.arraycopy(chunk, 0, bytes, pos, chunk.length); + pos += chunk.length; + } + + // Done. + return bytes; + } + + /** + * Reads and discards {@code size} bytes. + * + * @throws InvalidProtocolBufferException The end of the stream or the current + * limit was reached. + */ + public void skipRawBytes(final int size) throws IOException { + if (size <= (bufferSize - bufferPos) && size >= 0) { + // We have all the bytes we need already. + bufferPos += size; + } else { + skipRawBytesSlowPath(size); + } + } + + /** + * Exactly like skipRawBytes, but caller must have already checked the fast + * path: (size <= (bufferSize - pos) && size >= 0) + */ + private void skipRawBytesSlowPath(final int size) throws IOException { + if (size < 0) { + throw InvalidProtocolBufferException.negativeSize(); + } + + if (totalBytesRetired + bufferPos + size > currentLimit) { + // Read to the end of the stream anyway. + skipRawBytes(currentLimit - totalBytesRetired - bufferPos); + // Then fail. + throw InvalidProtocolBufferException.truncatedMessage(); + } + + // Skipping more bytes than are in the buffer. First skip what we have. + int pos = bufferSize - bufferPos; + bufferPos = bufferSize; + + // Keep refilling the buffer until we get to the point we wanted to skip to. + // This has the side effect of ensuring the limits are updated correctly. + refillBuffer(1); + while (size - pos > bufferSize) { + pos += bufferSize; + bufferPos = bufferSize; + refillBuffer(1); + } + + bufferPos = size - pos; + } +} diff --git a/java/core/src/main/java/com/google/protobuf/CodedOutputStream.java b/java/core/src/main/java/com/google/protobuf/CodedOutputStream.java new file mode 100644 index 00000000..d8ebad21 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/CodedOutputStream.java @@ -0,0 +1,1332 @@ +// 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.Utf8.UnpairedSurrogateException; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Encodes and writes protocol message fields. + * + * <p>This class contains two kinds of methods: methods that write specific + * protocol message constructs and field types (e.g. {@link #writeTag} and + * {@link #writeInt32}) and methods that write low-level values (e.g. + * {@link #writeRawVarint32} and {@link #writeRawBytes}). If you are + * writing encoded protocol messages, you should use the former methods, but if + * you are writing some other format of your own design, use the latter. + * + * <p>This class is totally unsynchronized. + * + * @author kneton@google.com Kenton Varda + */ +public final class CodedOutputStream { + + private static final Logger logger = Logger.getLogger(CodedOutputStream.class.getName()); + + // TODO(dweis): Consider migrating to a ByteBuffer. + private final byte[] buffer; + private final int limit; + private int position; + private int totalBytesWritten = 0; + + private final OutputStream output; + + /** + * The buffer size used in {@link #newInstance(OutputStream)}. + */ + public static final int DEFAULT_BUFFER_SIZE = 4096; + + /** + * Returns the buffer size to efficiently write dataLength bytes to this + * CodedOutputStream. Used by AbstractMessageLite. + * + * @return the buffer size to efficiently write dataLength bytes to this + * CodedOutputStream. + */ + static int computePreferredBufferSize(int dataLength) { + if (dataLength > DEFAULT_BUFFER_SIZE) return DEFAULT_BUFFER_SIZE; + return dataLength; + } + + private CodedOutputStream(final byte[] buffer, final int offset, + final int length) { + output = null; + this.buffer = buffer; + position = offset; + limit = offset + length; + } + + private CodedOutputStream(final OutputStream output, final byte[] buffer) { + this.output = output; + this.buffer = buffer; + position = 0; + limit = buffer.length; + } + + /** + * Create a new {@code CodedOutputStream} wrapping the given + * {@code OutputStream}. + */ + public static CodedOutputStream newInstance(final OutputStream output) { + return newInstance(output, DEFAULT_BUFFER_SIZE); + } + + /** + * Create a new {@code CodedOutputStream} wrapping the given + * {@code OutputStream} with a given buffer size. + */ + public static CodedOutputStream newInstance(final OutputStream output, + final int bufferSize) { + return new CodedOutputStream(output, new byte[bufferSize]); + } + + /** + * Create a new {@code CodedOutputStream} that writes directly to the given + * byte array. If more bytes are written than fit in the array, + * {@link OutOfSpaceException} will be thrown. Writing directly to a flat + * array is faster than writing to an {@code OutputStream}. See also + * {@link ByteString#newCodedBuilder}. + */ + public static CodedOutputStream newInstance(final byte[] flatArray) { + return newInstance(flatArray, 0, flatArray.length); + } + + /** + * Create a new {@code CodedOutputStream} that writes directly to the given + * byte array slice. If more bytes are written than fit in the slice, + * {@link OutOfSpaceException} will be thrown. Writing directly to a flat + * array is faster than writing to an {@code OutputStream}. See also + * {@link ByteString#newCodedBuilder}. + */ + public static CodedOutputStream newInstance(final byte[] flatArray, + final int offset, + final int length) { + return new CodedOutputStream(flatArray, offset, length); + } + + /** + * Create a new {@code CodedOutputStream} that writes to the given ByteBuffer. + */ + public static CodedOutputStream newInstance(ByteBuffer byteBuffer) { + return newInstance(byteBuffer, DEFAULT_BUFFER_SIZE); + } + + /** + * Create a new {@code CodedOutputStream} that writes to the given ByteBuffer. + */ + public static CodedOutputStream newInstance(ByteBuffer byteBuffer, + int bufferSize) { + return newInstance(new ByteBufferOutputStream(byteBuffer), bufferSize); + } + + private static class ByteBufferOutputStream extends OutputStream { + private final ByteBuffer byteBuffer; + public ByteBufferOutputStream(ByteBuffer byteBuffer) { + this.byteBuffer = byteBuffer; + } + + @Override + public void write(int b) throws IOException { + byteBuffer.put((byte) b); + } + + @Override + public void write(byte[] data, int offset, int length) throws IOException { + byteBuffer.put(data, offset, length); + } + } + + // ----------------------------------------------------------------- + + /** Write a {@code double} field, including tag, to the stream. */ + public void writeDouble(final int fieldNumber, final double value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED64); + writeDoubleNoTag(value); + } + + /** Write a {@code float} field, including tag, to the stream. */ + public void writeFloat(final int fieldNumber, final float value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED32); + writeFloatNoTag(value); + } + + /** Write a {@code uint64} field, including tag, to the stream. */ + public void writeUInt64(final int fieldNumber, final long value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + writeUInt64NoTag(value); + } + + /** Write an {@code int64} field, including tag, to the stream. */ + public void writeInt64(final int fieldNumber, final long value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + writeInt64NoTag(value); + } + + /** Write an {@code int32} field, including tag, to the stream. */ + public void writeInt32(final int fieldNumber, final int value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + writeInt32NoTag(value); + } + + /** Write a {@code fixed64} field, including tag, to the stream. */ + public void writeFixed64(final int fieldNumber, final long value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED64); + writeFixed64NoTag(value); + } + + /** Write a {@code fixed32} field, including tag, to the stream. */ + public void writeFixed32(final int fieldNumber, final int value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED32); + writeFixed32NoTag(value); + } + + /** Write a {@code bool} field, including tag, to the stream. */ + public void writeBool(final int fieldNumber, final boolean value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + writeBoolNoTag(value); + } + + /** Write a {@code string} field, including tag, to the stream. */ + public void writeString(final int fieldNumber, final String value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeStringNoTag(value); + } + + /** Write a {@code group} field, including tag, to the stream. */ + public void writeGroup(final int fieldNumber, final MessageLite value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_START_GROUP); + writeGroupNoTag(value); + writeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP); + } + + + /** Write an embedded message field, including tag, to the stream. */ + public void writeMessage(final int fieldNumber, final MessageLite value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeMessageNoTag(value); + } + + + /** Write a {@code bytes} field, including tag, to the stream. */ + public void writeBytes(final int fieldNumber, final ByteString value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeBytesNoTag(value); + } + + /** Write a {@code bytes} field, including tag, to the stream. */ + public void writeByteArray(final int fieldNumber, final byte[] value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeByteArrayNoTag(value); + } + + /** Write a {@code bytes} field, including tag, to the stream. */ + public void writeByteArray(final int fieldNumber, + final byte[] value, + final int offset, + final int length) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeByteArrayNoTag(value, offset, length); + } + + /** + * Write a {@code bytes} field, including tag, to the stream. + * This method will write all content of the ByteBuffer regardless of the + * current position and limit (i.e., the number of bytes to be written is + * value.capacity(), not value.remaining()). Furthermore, this method doesn't + * alter the state of the passed-in ByteBuffer. Its position, limit, mark, + * etc. will remain unchanged. If you only want to write the remaining bytes + * of a ByteBuffer, you can call + * {@code writeByteBuffer(fieldNumber, byteBuffer.slice())}. + */ + public void writeByteBuffer(final int fieldNumber, final ByteBuffer value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeByteBufferNoTag(value); + } + + /** Write a {@code uint32} field, including tag, to the stream. */ + public void writeUInt32(final int fieldNumber, final int value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + writeUInt32NoTag(value); + } + + /** + * Write an enum field, including tag, to the stream. Caller is responsible + * for converting the enum value to its numeric value. + */ + public void writeEnum(final int fieldNumber, final int value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + writeEnumNoTag(value); + } + + /** Write an {@code sfixed32} field, including tag, to the stream. */ + public void writeSFixed32(final int fieldNumber, final int value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED32); + writeSFixed32NoTag(value); + } + + /** Write an {@code sfixed64} field, including tag, to the stream. */ + public void writeSFixed64(final int fieldNumber, final long value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED64); + writeSFixed64NoTag(value); + } + + /** Write an {@code sint32} field, including tag, to the stream. */ + public void writeSInt32(final int fieldNumber, final int value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + writeSInt32NoTag(value); + } + + /** Write an {@code sint64} field, including tag, to the stream. */ + public void writeSInt64(final int fieldNumber, final long value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + writeSInt64NoTag(value); + } + + /** + * Write a MessageSet extension field to the stream. For historical reasons, + * the wire format differs from normal fields. + */ + public void writeMessageSetExtension(final int fieldNumber, + final MessageLite value) + throws IOException { + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_START_GROUP); + writeUInt32(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber); + writeMessage(WireFormat.MESSAGE_SET_MESSAGE, value); + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_END_GROUP); + } + + /** + * Write an unparsed MessageSet extension field to the stream. For + * historical reasons, the wire format differs from normal fields. + */ + public void writeRawMessageSetExtension(final int fieldNumber, + final ByteString value) + throws IOException { + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_START_GROUP); + writeUInt32(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber); + writeBytes(WireFormat.MESSAGE_SET_MESSAGE, value); + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_END_GROUP); + } + + // ----------------------------------------------------------------- + + /** Write a {@code double} field to the stream. */ + public void writeDoubleNoTag(final double value) throws IOException { + writeRawLittleEndian64(Double.doubleToRawLongBits(value)); + } + + /** Write a {@code float} field to the stream. */ + public void writeFloatNoTag(final float value) throws IOException { + writeRawLittleEndian32(Float.floatToRawIntBits(value)); + } + + /** Write a {@code uint64} field to the stream. */ + public void writeUInt64NoTag(final long value) throws IOException { + writeRawVarint64(value); + } + + /** Write an {@code int64} field to the stream. */ + public void writeInt64NoTag(final long value) throws IOException { + writeRawVarint64(value); + } + + /** Write an {@code int32} field to the stream. */ + public void writeInt32NoTag(final int value) throws IOException { + if (value >= 0) { + writeRawVarint32(value); + } else { + // Must sign-extend. + writeRawVarint64(value); + } + } + + /** Write a {@code fixed64} field to the stream. */ + public void writeFixed64NoTag(final long value) throws IOException { + writeRawLittleEndian64(value); + } + + /** Write a {@code fixed32} field to the stream. */ + public void writeFixed32NoTag(final int value) throws IOException { + writeRawLittleEndian32(value); + } + + /** Write a {@code bool} field to the stream. */ + public void writeBoolNoTag(final boolean value) throws IOException { + writeRawByte(value ? 1 : 0); + } + + /** Write a {@code string} field to the stream. */ + // TODO(dweis): Document behavior on ill-formed UTF-16 input. + public void writeStringNoTag(final String value) throws IOException { + try { + efficientWriteStringNoTag(value); + } catch (UnpairedSurrogateException e) { + logger.log(Level.WARNING, + "Converting ill-formed UTF-16. Your Protocol Buffer will not round trip correctly!", e); + inefficientWriteStringNoTag(value); + } + } + + /** Write a {@code string} field to the stream. */ + private void inefficientWriteStringNoTag(final String value) throws IOException { + // Unfortunately there does not appear to be any way to tell Java to encode + // UTF-8 directly into our buffer, so we have to let it create its own byte + // array and then copy. + // TODO(dweis): Consider using nio Charset methods instead. + final byte[] bytes = value.getBytes(Internal.UTF_8); + writeRawVarint32(bytes.length); + writeRawBytes(bytes); + } + + /** + * Write a {@code string} field to the stream efficiently. If the {@code string} is malformed, + * this method rolls back its changes and throws an {@link UnpairedSurrogateException} with the + * intent that the caller will catch and retry with {@link #inefficientWriteStringNoTag(String)}. + * + * @param value the string to write to the stream + * + * @throws UnpairedSurrogateException when {@code value} is ill-formed UTF-16. + */ + private void efficientWriteStringNoTag(final String value) throws IOException { + // UTF-8 byte length of the string is at least its UTF-16 code unit length (value.length()), + // and at most 3 times of it. We take advantage of this in both branches below. + final int maxLength = value.length() * Utf8.MAX_BYTES_PER_CHAR; + final int maxLengthVarIntSize = computeRawVarint32Size(maxLength); + + // If we are streaming and the potential length is too big to fit in our buffer, we take the + // slower path. Otherwise, we're good to try the fast path. + if (output != null && maxLengthVarIntSize + maxLength > limit - position) { + // Allocate a byte[] that we know can fit the string and encode into it. String.getBytes() + // does the same internally and then does *another copy* to return a byte[] of exactly the + // right size. We can skip that copy and just writeRawBytes up to the actualLength of the + // UTF-8 encoded bytes. + final byte[] encodedBytes = new byte[maxLength]; + int actualLength = Utf8.encode(value, encodedBytes, 0, maxLength); + writeRawVarint32(actualLength); + writeRawBytes(encodedBytes, 0, actualLength); + } else { + // Optimize for the case where we know this length results in a constant varint length as this + // saves a pass for measuring the length of the string. + final int minLengthVarIntSize = computeRawVarint32Size(value.length()); + int oldPosition = position; + final int length; + try { + if (minLengthVarIntSize == maxLengthVarIntSize) { + position = oldPosition + minLengthVarIntSize; + int newPosition = Utf8.encode(value, buffer, position, limit - position); + // Since this class is stateful and tracks the position, we rewind and store the state, + // prepend the length, then reset it back to the end of the string. + position = oldPosition; + length = newPosition - oldPosition - minLengthVarIntSize; + writeRawVarint32(length); + position = newPosition; + } else { + length = Utf8.encodedLength(value); + writeRawVarint32(length); + position = Utf8.encode(value, buffer, position, limit - position); + } + } catch (UnpairedSurrogateException e) { + // Be extra careful and restore the original position for retrying the write with the less + // efficient path. + position = oldPosition; + throw e; + } catch (ArrayIndexOutOfBoundsException e) { + throw new OutOfSpaceException(e); + } + totalBytesWritten += length; + } + } + + /** Write a {@code group} field to the stream. */ + public void writeGroupNoTag(final MessageLite value) throws IOException { + value.writeTo(this); + } + + + /** Write an embedded message field to the stream. */ + public void writeMessageNoTag(final MessageLite value) throws IOException { + writeRawVarint32(value.getSerializedSize()); + value.writeTo(this); + } + + + /** Write a {@code bytes} field to the stream. */ + public void writeBytesNoTag(final ByteString value) throws IOException { + writeRawVarint32(value.size()); + writeRawBytes(value); + } + + /** Write a {@code bytes} field to the stream. */ + public void writeByteArrayNoTag(final byte[] value) throws IOException { + writeRawVarint32(value.length); + writeRawBytes(value); + } + + /** Write a {@code bytes} field to the stream. */ + public void writeByteArrayNoTag(final byte[] value, + final int offset, + final int length) throws IOException { + writeRawVarint32(length); + writeRawBytes(value, offset, length); + } + + /** + * Write a {@code bytes} field to the stream. This method will write all + * content of the ByteBuffer regardless of the current position and limit + * (i.e., the number of bytes to be written is value.capacity(), not + * value.remaining()). Furthermore, this method doesn't alter the state of + * the passed-in ByteBuffer. Its position, limit, mark, etc. will remain + * unchanged. If you only want to write the remaining bytes of a ByteBuffer, + * you can call {@code writeByteBufferNoTag(byteBuffer.slice())}. + */ + public void writeByteBufferNoTag(final ByteBuffer value) throws IOException { + writeRawVarint32(value.capacity()); + writeRawBytes(value); + } + + /** Write a {@code uint32} field to the stream. */ + public void writeUInt32NoTag(final int value) throws IOException { + writeRawVarint32(value); + } + + /** + * Write an enum field to the stream. Caller is responsible + * for converting the enum value to its numeric value. + */ + public void writeEnumNoTag(final int value) throws IOException { + writeInt32NoTag(value); + } + + /** Write an {@code sfixed32} field to the stream. */ + public void writeSFixed32NoTag(final int value) throws IOException { + writeRawLittleEndian32(value); + } + + /** Write an {@code sfixed64} field to the stream. */ + public void writeSFixed64NoTag(final long value) throws IOException { + writeRawLittleEndian64(value); + } + + /** Write an {@code sint32} field to the stream. */ + public void writeSInt32NoTag(final int value) throws IOException { + writeRawVarint32(encodeZigZag32(value)); + } + + /** Write an {@code sint64} field to the stream. */ + public void writeSInt64NoTag(final long value) throws IOException { + writeRawVarint64(encodeZigZag64(value)); + } + + // ================================================================= + + /** + * Compute the number of bytes that would be needed to encode a + * {@code double} field, including tag. + */ + public static int computeDoubleSize(final int fieldNumber, + final double value) { + return computeTagSize(fieldNumber) + computeDoubleSizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code float} field, including tag. + */ + public static int computeFloatSize(final int fieldNumber, final float value) { + return computeTagSize(fieldNumber) + computeFloatSizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code uint64} field, including tag. + */ + public static int computeUInt64Size(final int fieldNumber, final long value) { + return computeTagSize(fieldNumber) + computeUInt64SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code int64} field, including tag. + */ + public static int computeInt64Size(final int fieldNumber, final long value) { + return computeTagSize(fieldNumber) + computeInt64SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code int32} field, including tag. + */ + public static int computeInt32Size(final int fieldNumber, final int value) { + return computeTagSize(fieldNumber) + computeInt32SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code fixed64} field, including tag. + */ + public static int computeFixed64Size(final int fieldNumber, + final long value) { + return computeTagSize(fieldNumber) + computeFixed64SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code fixed32} field, including tag. + */ + public static int computeFixed32Size(final int fieldNumber, + final int value) { + return computeTagSize(fieldNumber) + computeFixed32SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code bool} field, including tag. + */ + public static int computeBoolSize(final int fieldNumber, + final boolean value) { + return computeTagSize(fieldNumber) + computeBoolSizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code string} field, including tag. + */ + public static int computeStringSize(final int fieldNumber, + final String value) { + return computeTagSize(fieldNumber) + computeStringSizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code group} field, including tag. + */ + public static int computeGroupSize(final int fieldNumber, + final MessageLite value) { + return computeTagSize(fieldNumber) * 2 + computeGroupSizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * embedded message field, including tag. + */ + public static int computeMessageSize(final int fieldNumber, + final MessageLite value) { + return computeTagSize(fieldNumber) + computeMessageSizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code bytes} field, including tag. + */ + public static int computeBytesSize(final int fieldNumber, + final ByteString value) { + return computeTagSize(fieldNumber) + computeBytesSizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code bytes} field, including tag. + */ + public static int computeByteArraySize(final int fieldNumber, + final byte[] value) { + return computeTagSize(fieldNumber) + computeByteArraySizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code bytes} field, including tag. + */ + public static int computeByteBufferSize(final int fieldNumber, + final ByteBuffer value) { + return computeTagSize(fieldNumber) + computeByteBufferSizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * embedded message in lazy field, including tag. + */ + public static int computeLazyFieldSize(final int fieldNumber, + final LazyFieldLite value) { + return computeTagSize(fieldNumber) + computeLazyFieldSizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code uint32} field, including tag. + */ + public static int computeUInt32Size(final int fieldNumber, final int value) { + return computeTagSize(fieldNumber) + computeUInt32SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * enum field, including tag. Caller is responsible for converting the + * enum value to its numeric value. + */ + public static int computeEnumSize(final int fieldNumber, final int value) { + return computeTagSize(fieldNumber) + computeEnumSizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code sfixed32} field, including tag. + */ + public static int computeSFixed32Size(final int fieldNumber, + final int value) { + return computeTagSize(fieldNumber) + computeSFixed32SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code sfixed64} field, including tag. + */ + public static int computeSFixed64Size(final int fieldNumber, + final long value) { + return computeTagSize(fieldNumber) + computeSFixed64SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code sint32} field, including tag. + */ + public static int computeSInt32Size(final int fieldNumber, final int value) { + return computeTagSize(fieldNumber) + computeSInt32SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code sint64} field, including tag. + */ + public static int computeSInt64Size(final int fieldNumber, final long value) { + return computeTagSize(fieldNumber) + computeSInt64SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * MessageSet extension to the stream. For historical reasons, + * the wire format differs from normal fields. + */ + public static int computeMessageSetExtensionSize( + final int fieldNumber, final MessageLite value) { + return computeTagSize(WireFormat.MESSAGE_SET_ITEM) * 2 + + computeUInt32Size(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber) + + computeMessageSize(WireFormat.MESSAGE_SET_MESSAGE, value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * unparsed MessageSet extension field to the stream. For + * historical reasons, the wire format differs from normal fields. + */ + public static int computeRawMessageSetExtensionSize( + final int fieldNumber, final ByteString value) { + return computeTagSize(WireFormat.MESSAGE_SET_ITEM) * 2 + + computeUInt32Size(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber) + + computeBytesSize(WireFormat.MESSAGE_SET_MESSAGE, value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * lazily parsed MessageSet extension field to the stream. For + * historical reasons, the wire format differs from normal fields. + */ + public static int computeLazyFieldMessageSetExtensionSize( + final int fieldNumber, final LazyFieldLite value) { + return computeTagSize(WireFormat.MESSAGE_SET_ITEM) * 2 + + computeUInt32Size(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber) + + computeLazyFieldSize(WireFormat.MESSAGE_SET_MESSAGE, value); + } + + // ----------------------------------------------------------------- + + /** + * Compute the number of bytes that would be needed to encode a + * {@code double} field, including tag. + */ + public static int computeDoubleSizeNoTag(final double value) { + return LITTLE_ENDIAN_64_SIZE; + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code float} field, including tag. + */ + public static int computeFloatSizeNoTag(final float value) { + return LITTLE_ENDIAN_32_SIZE; + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code uint64} field, including tag. + */ + public static int computeUInt64SizeNoTag(final long value) { + return computeRawVarint64Size(value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code int64} field, including tag. + */ + public static int computeInt64SizeNoTag(final long value) { + return computeRawVarint64Size(value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code int32} field, including tag. + */ + public static int computeInt32SizeNoTag(final int value) { + if (value >= 0) { + return computeRawVarint32Size(value); + } else { + // Must sign-extend. + return 10; + } + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code fixed64} field. + */ + public static int computeFixed64SizeNoTag(final long value) { + return LITTLE_ENDIAN_64_SIZE; + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code fixed32} field. + */ + public static int computeFixed32SizeNoTag(final int value) { + return LITTLE_ENDIAN_32_SIZE; + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code bool} field. + */ + public static int computeBoolSizeNoTag(final boolean value) { + return 1; + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code string} field. + */ + public static int computeStringSizeNoTag(final String value) { + int length; + try { + length = Utf8.encodedLength(value); + } catch (UnpairedSurrogateException e) { + // TODO(dweis): Consider using nio Charset methods instead. + final byte[] bytes = value.getBytes(Internal.UTF_8); + length = bytes.length; + } + + return computeRawVarint32Size(length) + length; + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code group} field. + */ + public static int computeGroupSizeNoTag(final MessageLite value) { + return value.getSerializedSize(); + } + + /** + * Compute the number of bytes that would be needed to encode an embedded + * message field. + */ + public static int computeMessageSizeNoTag(final MessageLite value) { + final int size = value.getSerializedSize(); + return computeRawVarint32Size(size) + size; + } + + /** + * Compute the number of bytes that would be needed to encode an embedded + * message stored in lazy field. + */ + public static int computeLazyFieldSizeNoTag(final LazyFieldLite value) { + final int size = value.getSerializedSize(); + return computeRawVarint32Size(size) + size; + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code bytes} field. + */ + public static int computeBytesSizeNoTag(final ByteString value) { + return computeRawVarint32Size(value.size()) + + value.size(); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code bytes} field. + */ + public static int computeByteArraySizeNoTag(final byte[] value) { + return computeRawVarint32Size(value.length) + value.length; + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code bytes} field. + */ + public static int computeByteBufferSizeNoTag(final ByteBuffer value) { + return computeRawVarint32Size(value.capacity()) + value.capacity(); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code uint32} field. + */ + public static int computeUInt32SizeNoTag(final int value) { + return computeRawVarint32Size(value); + } + + /** + * Compute the number of bytes that would be needed to encode an enum field. + * Caller is responsible for converting the enum value to its numeric value. + */ + public static int computeEnumSizeNoTag(final int value) { + return computeInt32SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code sfixed32} field. + */ + public static int computeSFixed32SizeNoTag(final int value) { + return LITTLE_ENDIAN_32_SIZE; + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code sfixed64} field. + */ + public static int computeSFixed64SizeNoTag(final long value) { + return LITTLE_ENDIAN_64_SIZE; + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code sint32} field. + */ + public static int computeSInt32SizeNoTag(final int value) { + return computeRawVarint32Size(encodeZigZag32(value)); + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code sint64} field. + */ + public static int computeSInt64SizeNoTag(final long value) { + return computeRawVarint64Size(encodeZigZag64(value)); + } + + // ================================================================= + + /** + * Internal helper that writes the current buffer to the output. The + * buffer position is reset to its initial value when this returns. + */ + private void refreshBuffer() throws IOException { + if (output == null) { + // We're writing to a single buffer. + throw new OutOfSpaceException(); + } + + // Since we have an output stream, this is our buffer + // and buffer offset == 0 + output.write(buffer, 0, position); + position = 0; + } + + /** + * Flushes the stream and forces any buffered bytes to be written. This + * does not flush the underlying OutputStream. + */ + public void flush() throws IOException { + if (output != null) { + refreshBuffer(); + } + } + + /** + * If writing to a flat array, return the space left in the array. + * Otherwise, throws {@code UnsupportedOperationException}. + */ + public int spaceLeft() { + if (output == null) { + return limit - position; + } else { + throw new UnsupportedOperationException( + "spaceLeft() can only be called on CodedOutputStreams that are " + + "writing to a flat array."); + } + } + + /** + * Verifies that {@link #spaceLeft()} returns zero. It's common to create + * a byte array that is exactly big enough to hold a message, then write to + * it with a {@code CodedOutputStream}. Calling {@code checkNoSpaceLeft()} + * after writing verifies that the message was actually as big as expected, + * which can help catch bugs. + */ + public void checkNoSpaceLeft() { + if (spaceLeft() != 0) { + throw new IllegalStateException( + "Did not write as much data as expected."); + } + } + + /** + * If you create a CodedOutputStream around a simple flat array, you must + * not attempt to write more bytes than the array has space. Otherwise, + * this exception will be thrown. + */ + public static class OutOfSpaceException extends IOException { + private static final long serialVersionUID = -6947486886997889499L; + + private static final String MESSAGE = + "CodedOutputStream was writing to a flat byte array and ran out of space."; + + OutOfSpaceException() { + super(MESSAGE); + } + + OutOfSpaceException(Throwable cause) { + super(MESSAGE, cause); + } + } + + /** + * Get the total number of bytes successfully written to this stream. The + * returned value is not guaranteed to be accurate if exceptions have been + * found in the middle of writing. + */ + public int getTotalBytesWritten() { + return totalBytesWritten; + } + + /** Write a single byte. */ + public void writeRawByte(final byte value) throws IOException { + if (position == limit) { + refreshBuffer(); + } + + buffer[position++] = value; + ++totalBytesWritten; + } + + /** Write a single byte, represented by an integer value. */ + public void writeRawByte(final int value) throws IOException { + writeRawByte((byte) value); + } + + /** Write a byte string. */ + public void writeRawBytes(final ByteString value) throws IOException { + writeRawBytes(value, 0, value.size()); + } + + /** Write an array of bytes. */ + public void writeRawBytes(final byte[] value) throws IOException { + writeRawBytes(value, 0, value.length); + } + + /** + * Write a ByteBuffer. This method will write all content of the ByteBuffer + * regardless of the current position and limit (i.e., the number of bytes + * to be written is value.capacity(), not value.remaining()). Furthermore, + * this method doesn't alter the state of the passed-in ByteBuffer. Its + * position, limit, mark, etc. will remain unchanged. If you only want to + * write the remaining bytes of a ByteBuffer, you can call + * {@code writeRawBytes(byteBuffer.slice())}. + */ + public void writeRawBytes(final ByteBuffer value) throws IOException { + if (value.hasArray()) { + writeRawBytes(value.array(), value.arrayOffset(), value.capacity()); + } else { + ByteBuffer duplicated = value.duplicate(); + duplicated.clear(); + writeRawBytesInternal(duplicated); + } + } + + /** Write a ByteBuffer that isn't backed by an array. */ + private void writeRawBytesInternal(final ByteBuffer value) + throws IOException { + int length = value.remaining(); + if (limit - position >= length) { + // We have room in the current buffer. + value.get(buffer, position, length); + position += length; + totalBytesWritten += length; + } else { + // Write extends past current buffer. Fill the rest of this buffer and + // flush. + final int bytesWritten = limit - position; + value.get(buffer, position, bytesWritten); + length -= bytesWritten; + position = limit; + totalBytesWritten += bytesWritten; + refreshBuffer(); + + // Now deal with the rest. + // Since we have an output stream, this is our buffer + // and buffer offset == 0 + while (length > limit) { + // Copy data into the buffer before writing it to OutputStream. + // TODO(xiaofeng): Introduce ZeroCopyOutputStream to avoid this copy. + value.get(buffer, 0, limit); + output.write(buffer, 0, limit); + length -= limit; + totalBytesWritten += limit; + } + value.get(buffer, 0, length); + position = length; + totalBytesWritten += length; + } + } + + /** Write part of an array of bytes. */ + public void writeRawBytes(final byte[] value, int offset, int length) + throws IOException { + if (limit - position >= length) { + // We have room in the current buffer. + System.arraycopy(value, offset, buffer, position, length); + position += length; + totalBytesWritten += length; + } else { + // Write extends past current buffer. Fill the rest of this buffer and + // flush. + final int bytesWritten = limit - position; + System.arraycopy(value, offset, buffer, position, bytesWritten); + offset += bytesWritten; + length -= bytesWritten; + position = limit; + totalBytesWritten += bytesWritten; + refreshBuffer(); + + // Now deal with the rest. + // Since we have an output stream, this is our buffer + // and buffer offset == 0 + if (length <= limit) { + // Fits in new buffer. + System.arraycopy(value, offset, buffer, 0, length); + position = length; + } else { + // Write is very big. Let's do it all at once. + output.write(value, offset, length); + } + totalBytesWritten += length; + } + } + + /** Write part of a byte string. */ + public void writeRawBytes(final ByteString value, int offset, int length) + throws IOException { + if (limit - position >= length) { + // We have room in the current buffer. + value.copyTo(buffer, offset, position, length); + position += length; + totalBytesWritten += length; + } else { + // Write extends past current buffer. Fill the rest of this buffer and + // flush. + final int bytesWritten = limit - position; + value.copyTo(buffer, offset, position, bytesWritten); + offset += bytesWritten; + length -= bytesWritten; + position = limit; + totalBytesWritten += bytesWritten; + refreshBuffer(); + + // Now deal with the rest. + // Since we have an output stream, this is our buffer + // and buffer offset == 0 + if (length <= limit) { + // Fits in new buffer. + value.copyTo(buffer, offset, 0, length); + position = length; + } else { + value.writeTo(output, offset, length); + } + totalBytesWritten += length; + } + } + + /** Encode and write a tag. */ + public void writeTag(final int fieldNumber, final int wireType) + throws IOException { + writeRawVarint32(WireFormat.makeTag(fieldNumber, wireType)); + } + + /** Compute the number of bytes that would be needed to encode a tag. */ + public static int computeTagSize(final int fieldNumber) { + return computeRawVarint32Size(WireFormat.makeTag(fieldNumber, 0)); + } + + /** + * Encode and write a varint. {@code value} is treated as + * unsigned, so it won't be sign-extended if negative. + */ + public void writeRawVarint32(int value) throws IOException { + while (true) { + if ((value & ~0x7F) == 0) { + writeRawByte(value); + return; + } else { + writeRawByte((value & 0x7F) | 0x80); + value >>>= 7; + } + } + } + + /** + * Compute the number of bytes that would be needed to encode a varint. + * {@code value} is treated as unsigned, so it won't be sign-extended if + * negative. + */ + public static int computeRawVarint32Size(final int value) { + if ((value & (~0 << 7)) == 0) return 1; + if ((value & (~0 << 14)) == 0) return 2; + if ((value & (~0 << 21)) == 0) return 3; + if ((value & (~0 << 28)) == 0) return 4; + return 5; + } + + /** Encode and write a varint. */ + public void writeRawVarint64(long value) throws IOException { + while (true) { + if ((value & ~0x7FL) == 0) { + writeRawByte((int)value); + return; + } else { + writeRawByte(((int)value & 0x7F) | 0x80); + value >>>= 7; + } + } + } + + /** Compute the number of bytes that would be needed to encode a varint. */ + public static int computeRawVarint64Size(long value) { + // handle two popular special cases up front ... + if ((value & (~0L << 7)) == 0L) return 1; + if (value < 0L) return 10; + // ... leaving us with 8 remaining, which we can divide and conquer + int n = 2; + if ((value & (~0L << 35)) != 0L) { n += 4; value >>>= 28; } + if ((value & (~0L << 21)) != 0L) { n += 2; value >>>= 14; } + if ((value & (~0L << 14)) != 0L) { n += 1; } + return n; + } + + /** Write a little-endian 32-bit integer. */ + public void writeRawLittleEndian32(final int value) throws IOException { + writeRawByte((value ) & 0xFF); + writeRawByte((value >> 8) & 0xFF); + writeRawByte((value >> 16) & 0xFF); + writeRawByte((value >> 24) & 0xFF); + } + + public static final int LITTLE_ENDIAN_32_SIZE = 4; + + /** Write a little-endian 64-bit integer. */ + public void writeRawLittleEndian64(final long value) throws IOException { + writeRawByte((int)(value ) & 0xFF); + writeRawByte((int)(value >> 8) & 0xFF); + writeRawByte((int)(value >> 16) & 0xFF); + writeRawByte((int)(value >> 24) & 0xFF); + writeRawByte((int)(value >> 32) & 0xFF); + writeRawByte((int)(value >> 40) & 0xFF); + writeRawByte((int)(value >> 48) & 0xFF); + writeRawByte((int)(value >> 56) & 0xFF); + } + + public static final int LITTLE_ENDIAN_64_SIZE = 8; + + /** + * Encode a ZigZag-encoded 32-bit value. ZigZag encodes signed integers + * into values that can be efficiently encoded with varint. (Otherwise, + * negative values must be sign-extended to 64 bits to be varint encoded, + * thus always taking 10 bytes on the wire.) + * + * @param n A signed 32-bit integer. + * @return An unsigned 32-bit integer, stored in a signed int because + * Java has no explicit unsigned support. + */ + public static int encodeZigZag32(final int n) { + // Note: the right-shift must be arithmetic + return (n << 1) ^ (n >> 31); + } + + /** + * Encode a ZigZag-encoded 64-bit value. ZigZag encodes signed integers + * into values that can be efficiently encoded with varint. (Otherwise, + * negative values must be sign-extended to 64 bits to be varint encoded, + * thus always taking 10 bytes on the wire.) + * + * @param n A signed 64-bit integer. + * @return An unsigned 64-bit integer, stored in a signed int because + * Java has no explicit unsigned support. + */ + public static long encodeZigZag64(final long n) { + // Note: the right-shift must be arithmetic + return (n << 1) ^ (n >> 63); + } +} diff --git a/java/core/src/main/java/com/google/protobuf/Descriptors.java b/java/core/src/main/java/com/google/protobuf/Descriptors.java new file mode 100644 index 00000000..5e15cfbe --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/Descriptors.java @@ -0,0 +1,2439 @@ +// 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.DescriptorProtos.*; +import com.google.protobuf.Descriptors.FileDescriptor.Syntax; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +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; + +/** + * Contains a collection of classes which describe protocol message types. + * + * Every message type has a {@link Descriptor}, which lists all + * its fields and other information about a type. You can get a message + * type's descriptor by calling {@code MessageType.getDescriptor()}, or + * (given a message object of the type) {@code message.getDescriptorForType()}. + * Furthermore, each message is associated with a {@link FileDescriptor} for + * a relevant {@code .proto} file. You can obtain it by calling + * {@code Descriptor.getFile()}. A {@link FileDescriptor} contains descriptors + * for all the messages defined in that file, and file descriptors for all the + * imported {@code .proto} files. + * + * Descriptors are built from DescriptorProtos, as defined in + * {@code google/protobuf/descriptor.proto}. + * + * @author kenton@google.com Kenton Varda + */ +public final class Descriptors { + private static final Logger logger = + Logger.getLogger(Descriptors.class.getName()); + /** + * Describes a {@code .proto} file, including everything defined within. + * That includes, in particular, descriptors for all the messages and + * file descriptors for all other imported {@code .proto} files + * (dependencies). + */ + public static final class FileDescriptor extends GenericDescriptor { + /** Convert the descriptor to its protocol message representation. */ + public FileDescriptorProto toProto() { return proto; } + + /** Get the file name. */ + public String getName() { return proto.getName(); } + + /** Returns this object. */ + public FileDescriptor getFile() { return this; } + + /** Returns the same as getName(). */ + public String getFullName() { return proto.getName(); } + + /** + * Get the proto package name. This is the package name given by the + * {@code package} statement in the {@code .proto} file, which differs + * from the Java package. + */ + public String getPackage() { return proto.getPackage(); } + + /** Get the {@code FileOptions}, defined in {@code descriptor.proto}. */ + public FileOptions getOptions() { return proto.getOptions(); } + + /** Get a list of top-level message types declared in this file. */ + public List<Descriptor> getMessageTypes() { + return Collections.unmodifiableList(Arrays.asList(messageTypes)); + } + + /** Get a list of top-level enum types declared in this file. */ + public List<EnumDescriptor> getEnumTypes() { + return Collections.unmodifiableList(Arrays.asList(enumTypes)); + } + + /** Get a list of top-level services declared in this file. */ + public List<ServiceDescriptor> getServices() { + return Collections.unmodifiableList(Arrays.asList(services)); + } + + /** Get a list of top-level extensions declared in this file. */ + public List<FieldDescriptor> getExtensions() { + return Collections.unmodifiableList(Arrays.asList(extensions)); + } + + /** Get a list of this file's dependencies (imports). */ + public List<FileDescriptor> getDependencies() { + return Collections.unmodifiableList(Arrays.asList(dependencies)); + } + + /** Get a list of this file's public dependencies (public imports). */ + public List<FileDescriptor> getPublicDependencies() { + 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. + * + * @param name The unqualified type name to look for. + * @return The message type's descriptor, or {@code null} if not found. + */ + public Descriptor findMessageTypeByName(String name) { + // Don't allow looking up nested types. This will make optimization + // easier later. + if (name.indexOf('.') != -1) { + return null; + } + if (getPackage().length() > 0) { + name = getPackage() + '.' + name; + } + final GenericDescriptor result = pool.findSymbol(name); + if (result != null && result instanceof Descriptor && + result.getFile() == this) { + return (Descriptor)result; + } else { + return null; + } + } + + /** + * Find an enum type in the file by name. Does not find nested types. + * + * @param name The unqualified type name to look for. + * @return The enum type's descriptor, or {@code null} if not found. + */ + public EnumDescriptor findEnumTypeByName(String name) { + // Don't allow looking up nested types. This will make optimization + // easier later. + if (name.indexOf('.') != -1) { + return null; + } + if (getPackage().length() > 0) { + name = getPackage() + '.' + name; + } + final GenericDescriptor result = pool.findSymbol(name); + if (result != null && result instanceof EnumDescriptor && + result.getFile() == this) { + return (EnumDescriptor)result; + } else { + return null; + } + } + + /** + * Find a service type in the file by name. + * + * @param name The unqualified type name to look for. + * @return The service type's descriptor, or {@code null} if not found. + */ + public ServiceDescriptor findServiceByName(String name) { + // Don't allow looking up nested types. This will make optimization + // easier later. + if (name.indexOf('.') != -1) { + return null; + } + if (getPackage().length() > 0) { + name = getPackage() + '.' + name; + } + final GenericDescriptor result = pool.findSymbol(name); + if (result != null && result instanceof ServiceDescriptor && + result.getFile() == this) { + return (ServiceDescriptor)result; + } else { + return null; + } + } + + /** + * Find an extension in the file by name. Does not find extensions nested + * inside message types. + * + * @param name The unqualified extension name to look for. + * @return The extension's descriptor, or {@code null} if not found. + */ + public FieldDescriptor findExtensionByName(String name) { + if (name.indexOf('.') != -1) { + return null; + } + if (getPackage().length() > 0) { + name = getPackage() + '.' + name; + } + final GenericDescriptor result = pool.findSymbol(name); + if (result != null && result instanceof FieldDescriptor && + result.getFile() == this) { + return (FieldDescriptor)result; + } else { + return null; + } + } + + /** + * Construct a {@code FileDescriptor}. + * + * @param proto The protocol message form of the FileDescriptor. + * @param dependencies {@code FileDescriptor}s corresponding to all of + * the file's dependencies. + * @throws DescriptorValidationException {@code proto} is not a valid + * descriptor. This can occur for a number of reasons, e.g. + * because a field has an undefined type or because two messages + * were defined with the same name. + */ + public static FileDescriptor buildFrom(final FileDescriptorProto proto, + final FileDescriptor[] dependencies) + throws DescriptorValidationException { + return buildFrom(proto, dependencies, false); + } + + + /** + * Construct a {@code FileDescriptor}. + * + * @param proto The protocol message form of the FileDescriptor. + * @param dependencies {@code FileDescriptor}s corresponding to all of + * the file's dependencies. + * @param allowUnknownDependencies If true, non-exist dependenncies will be + * ignored and undefined message types will be replaced with a + * placeholder type. + * @throws DescriptorValidationException {@code proto} is not a valid + * descriptor. This can occur for a number of reasons, e.g. + * because a field has an undefined type or because two messages + * were defined with the same name. + */ + private static FileDescriptor buildFrom( + final FileDescriptorProto proto, final FileDescriptor[] dependencies, + final boolean allowUnknownDependencies) + throws DescriptorValidationException { + // Building descriptors involves two steps: translating and linking. + // In the translation step (implemented by FileDescriptor's + // constructor), we build an object tree mirroring the + // FileDescriptorProto's tree and put all of the descriptors into the + // DescriptorPool's lookup tables. In the linking step, we look up all + // type references in the DescriptorPool, so that, for example, a + // FieldDescriptor for an embedded message contains a pointer directly + // to the Descriptor for that message's type. We also detect undefined + // types in the linking step. + final DescriptorPool pool = new DescriptorPool( + dependencies, allowUnknownDependencies); + final FileDescriptor result = new FileDescriptor( + proto, dependencies, pool, allowUnknownDependencies); + result.crossLink(); + return result; + } + + /** + * This method is to be called by generated code only. It is equivalent + * to {@code buildFrom} except that the {@code FileDescriptorProto} is + * encoded in protocol buffer wire format. + */ + public static void internalBuildGeneratedFileFrom( + final String[] descriptorDataParts, + final FileDescriptor[] dependencies, + final InternalDescriptorAssigner descriptorAssigner) { + // Hack: We can't embed a raw byte array inside generated Java code + // (at least, not efficiently), but we can embed Strings. So, the + // protocol compiler embeds the FileDescriptorProto as a giant + // string literal which is passed to this function to construct the + // file's FileDescriptor. The string literal contains only 8-bit + // characters, each one representing a byte of the FileDescriptorProto's + // serialized form. So, if we convert it to bytes in ISO-8859-1, we + // should get the original bytes that we want. + + // descriptorData may contain multiple strings in order to get around the + // Java 64k string literal limit. + StringBuilder descriptorData = new StringBuilder(); + for (String part : descriptorDataParts) { + descriptorData.append(part); + } + + final byte[] descriptorBytes; + descriptorBytes = descriptorData.toString().getBytes(Internal.ISO_8859_1); + + FileDescriptorProto proto; + try { + proto = FileDescriptorProto.parseFrom(descriptorBytes); + } catch (InvalidProtocolBufferException e) { + throw new IllegalArgumentException( + "Failed to parse protocol buffer descriptor for generated code.", e); + } + + final FileDescriptor result; + try { + // When building descriptors for generated code, we allow unknown + // dependencies by default. + result = buildFrom(proto, dependencies, true); + } catch (DescriptorValidationException e) { + throw new IllegalArgumentException( + "Invalid embedded descriptor for \"" + proto.getName() + "\".", e); + } + + final ExtensionRegistry registry = + descriptorAssigner.assignDescriptors(result); + + if (registry != null) { + // We must re-parse the proto using the registry. + try { + proto = FileDescriptorProto.parseFrom(descriptorBytes, registry); + } catch (InvalidProtocolBufferException e) { + throw new IllegalArgumentException( + "Failed to parse protocol buffer descriptor for generated code.", + e); + } + + result.setProto(proto); + } + } + + /** + * This method is to be called by generated code only. It uses Java + * reflection to load the dependencies' descriptors. + */ + public static void internalBuildGeneratedFileFrom( + final String[] descriptorDataParts, + final Class<?> descriptorOuterClass, + final String[] dependencies, + final String[] dependencyFileNames, + final InternalDescriptorAssigner descriptorAssigner) { + List<FileDescriptor> descriptors = new ArrayList<FileDescriptor>(); + for (int i = 0; i < dependencies.length; i++) { + try { + Class<?> clazz = + descriptorOuterClass.getClassLoader().loadClass(dependencies[i]); + descriptors.add( + (FileDescriptor) clazz.getField("descriptor").get(null)); + } catch (Exception e) { + // We allow unknown dependencies by default. If a dependency cannot + // be found we only generate a warning. + logger.warning("Descriptors for \"" + dependencyFileNames[i] + + "\" can not be found."); + } + } + FileDescriptor[] descriptorArray = new FileDescriptor[descriptors.size()]; + descriptors.toArray(descriptorArray); + internalBuildGeneratedFileFrom( + descriptorDataParts, descriptorArray, descriptorAssigner); + } + + /** + * This method is to be called by generated code only. It is used to + * update the FileDescriptorProto associated with the descriptor by + * parsing it again with the given ExtensionRegistry. This is needed to + * recognize custom options. + */ + public static void internalUpdateFileDescriptor( + final FileDescriptor descriptor, + final ExtensionRegistry registry) { + ByteString bytes = descriptor.proto.toByteString(); + FileDescriptorProto proto; + try { + proto = FileDescriptorProto.parseFrom(bytes, registry); + } catch (InvalidProtocolBufferException e) { + throw new IllegalArgumentException( + "Failed to parse protocol buffer descriptor for generated code.", e); + } + descriptor.setProto(proto); + } + + /** + * This class should be used by generated code only. When calling + * {@link FileDescriptor#internalBuildGeneratedFileFrom}, the caller + * provides a callback implementing this interface. The callback is called + * after the FileDescriptor has been constructed, in order to assign all + * the global variables defined in the generated code which point at parts + * of the FileDescriptor. The callback returns an ExtensionRegistry which + * contains any extensions which might be used in the descriptor -- that + * is, extensions of the various "Options" messages defined in + * descriptor.proto. The callback may also return null to indicate that + * no extensions are used in the descriptor. + */ + public interface InternalDescriptorAssigner { + ExtensionRegistry assignDescriptors(FileDescriptor root); + } + + private FileDescriptorProto proto; + private final Descriptor[] messageTypes; + private final EnumDescriptor[] enumTypes; + private final ServiceDescriptor[] services; + private final FieldDescriptor[] extensions; + private final FileDescriptor[] dependencies; + private final FileDescriptor[] publicDependencies; + private final DescriptorPool pool; + + private FileDescriptor(final FileDescriptorProto proto, + final FileDescriptor[] dependencies, + final DescriptorPool pool, + boolean allowUnknownDependencies) + throws DescriptorValidationException { + this.pool = pool; + this.proto = proto; + this.dependencies = dependencies.clone(); + HashMap<String, FileDescriptor> nameToFileMap = + new HashMap<String, FileDescriptor>(); + for (FileDescriptor file : dependencies) { + nameToFileMap.put(file.getName(), file); + } + List<FileDescriptor> publicDependencies = new ArrayList<FileDescriptor>(); + for (int i = 0; i < proto.getPublicDependencyCount(); i++) { + int index = proto.getPublicDependency(i); + if (index < 0 || index >= proto.getDependencyCount()) { + throw new DescriptorValidationException(this, + "Invalid public dependency index."); + } + String name = proto.getDependency(index); + FileDescriptor file = nameToFileMap.get(name); + if (file == null) { + if (!allowUnknownDependencies) { + throw new DescriptorValidationException(this, + "Invalid public dependency: " + name); + } + // Ignore unknown dependencies. + } else { + publicDependencies.add(file); + } + } + this.publicDependencies = new FileDescriptor[publicDependencies.size()]; + publicDependencies.toArray(this.publicDependencies); + + pool.addPackage(getPackage(), this); + + messageTypes = new Descriptor[proto.getMessageTypeCount()]; + for (int i = 0; i < proto.getMessageTypeCount(); i++) { + messageTypes[i] = + new Descriptor(proto.getMessageType(i), this, null, i); + } + + enumTypes = new EnumDescriptor[proto.getEnumTypeCount()]; + for (int i = 0; i < proto.getEnumTypeCount(); i++) { + enumTypes[i] = new EnumDescriptor(proto.getEnumType(i), this, null, i); + } + + services = new ServiceDescriptor[proto.getServiceCount()]; + for (int i = 0; i < proto.getServiceCount(); i++) { + services[i] = new ServiceDescriptor(proto.getService(i), this, i); + } + + extensions = new FieldDescriptor[proto.getExtensionCount()]; + for (int i = 0; i < proto.getExtensionCount(); i++) { + extensions[i] = new FieldDescriptor( + proto.getExtension(i), this, null, i, true); + } + } + + /** + * Create a placeholder FileDescriptor for a message Descriptor. + */ + FileDescriptor(String packageName, Descriptor message) + throws DescriptorValidationException { + this.pool = new DescriptorPool(new FileDescriptor[0], true); + this.proto = FileDescriptorProto.newBuilder() + .setName(message.getFullName() + ".placeholder.proto") + .setPackage(packageName).addMessageType(message.toProto()).build(); + this.dependencies = new FileDescriptor[0]; + this.publicDependencies = new FileDescriptor[0]; + + messageTypes = new Descriptor[] {message}; + enumTypes = new EnumDescriptor[0]; + services = new ServiceDescriptor[0]; + extensions = new FieldDescriptor[0]; + + pool.addPackage(packageName, this); + pool.addSymbol(message); + } + + /** Look up and cross-link all field types, etc. */ + private void crossLink() throws DescriptorValidationException { + for (final Descriptor messageType : messageTypes) { + messageType.crossLink(); + } + + for (final ServiceDescriptor service : services) { + service.crossLink(); + } + + for (final FieldDescriptor extension : extensions) { + extension.crossLink(); + } + } + + /** + * Replace our {@link FileDescriptorProto} with the given one, which is + * identical except that it might contain extensions that weren't present + * in the original. This method is needed for bootstrapping when a file + * defines custom options. The options may be defined in the file itself, + * so we can't actually parse them until we've constructed the descriptors, + * but to construct the descriptors we have to have parsed the descriptor + * protos. So, we have to parse the descriptor protos a second time after + * constructing the descriptors. + */ + private void setProto(final FileDescriptorProto proto) { + this.proto = proto; + + for (int i = 0; i < messageTypes.length; i++) { + messageTypes[i].setProto(proto.getMessageType(i)); + } + + for (int i = 0; i < enumTypes.length; i++) { + enumTypes[i].setProto(proto.getEnumType(i)); + } + + for (int i = 0; i < services.length; i++) { + services[i].setProto(proto.getService(i)); + } + + for (int i = 0; i < extensions.length; i++) { + extensions[i].setProto(proto.getExtension(i)); + } + } + + boolean supportsUnknownEnumValue() { + return getSyntax() == Syntax.PROTO3; + } + } + + // ================================================================= + + /** Describes a message type. */ + public static final class Descriptor extends GenericDescriptor { + /** + * Get the index of this descriptor within its parent. In other words, + * given a {@link FileDescriptor} {@code file}, the following is true: + * <pre> + * for all i in [0, file.getMessageTypeCount()): + * file.getMessageType(i).getIndex() == i + * </pre> + * Similarly, for a {@link Descriptor} {@code messageType}: + * <pre> + * for all i in [0, messageType.getNestedTypeCount()): + * messageType.getNestedType(i).getIndex() == i + * </pre> + */ + public int getIndex() { return index; } + + /** Convert the descriptor to its protocol message representation. */ + public DescriptorProto toProto() { return proto; } + + /** Get the type's unqualified name. */ + public String getName() { return proto.getName(); } + + /** + * Get the type's fully-qualified name, within the proto language's + * namespace. This differs from the Java name. For example, given this + * {@code .proto}: + * <pre> + * package foo.bar; + * option java_package = "com.example.protos" + * message Baz {} + * </pre> + * {@code Baz}'s full name is "foo.bar.Baz". + */ + public String getFullName() { return fullName; } + + /** Get the {@link FileDescriptor} containing this descriptor. */ + public FileDescriptor getFile() { return file; } + + /** If this is a nested type, get the outer descriptor, otherwise null. */ + public Descriptor getContainingType() { return containingType; } + + /** Get the {@code MessageOptions}, defined in {@code descriptor.proto}. */ + public MessageOptions getOptions() { return proto.getOptions(); } + + /** Get a list of this message type's fields. */ + public List<FieldDescriptor> getFields() { + return Collections.unmodifiableList(Arrays.asList(fields)); + } + + /** Get a list of this message type's oneofs. */ + public List<OneofDescriptor> getOneofs() { + return Collections.unmodifiableList(Arrays.asList(oneofs)); + } + + /** Get a list of this message type's extensions. */ + public List<FieldDescriptor> getExtensions() { + return Collections.unmodifiableList(Arrays.asList(extensions)); + } + + /** Get a list of message types nested within this one. */ + public List<Descriptor> getNestedTypes() { + return Collections.unmodifiableList(Arrays.asList(nestedTypes)); + } + + /** Get a list of enum types nested within this one. */ + public List<EnumDescriptor> getEnumTypes() { + return Collections.unmodifiableList(Arrays.asList(enumTypes)); + } + + /** Determines if the given field number is an extension. */ + public boolean isExtensionNumber(final int number) { + for (final DescriptorProto.ExtensionRange range : + proto.getExtensionRangeList()) { + if (range.getStart() <= number && number < range.getEnd()) { + return true; + } + } + return false; + } + + /** Determines if the given field number is reserved. */ + public boolean isReservedNumber(final int number) { + for (final DescriptorProto.ReservedRange range : + proto.getReservedRangeList()) { + if (range.getStart() <= number && number < range.getEnd()) { + return true; + } + } + return false; + } + + /** Determines if the given field name is reserved. */ + public boolean isReservedName(final String name) { + if (name == null) { + throw new NullPointerException(); + } + for (final String reservedName : proto.getReservedNameList()) { + if (reservedName.equals(name)) { + return true; + } + } + return false; + } + + /** + * Indicates whether the message can be extended. That is, whether it has + * any "extensions x to y" ranges declared on it. + */ + public boolean isExtendable() { + return proto.getExtensionRangeList().size() != 0; + } + + /** + * Finds a field by name. + * @param name The unqualified name of the field (e.g. "foo"). + * @return The field's descriptor, or {@code null} if not found. + */ + public FieldDescriptor findFieldByName(final String name) { + final GenericDescriptor result = + file.pool.findSymbol(fullName + '.' + name); + if (result != null && result instanceof FieldDescriptor) { + return (FieldDescriptor)result; + } else { + return null; + } + } + + /** + * Finds a field by field number. + * @param number The field number within this message type. + * @return The field's descriptor, or {@code null} if not found. + */ + public FieldDescriptor findFieldByNumber(final int number) { + return file.pool.fieldsByNumber.get( + new DescriptorPool.DescriptorIntPair(this, number)); + } + + /** + * Finds a nested message type by name. + * @param name The unqualified name of the nested type (e.g. "Foo"). + * @return The types's descriptor, or {@code null} if not found. + */ + public Descriptor findNestedTypeByName(final String name) { + final GenericDescriptor result = + file.pool.findSymbol(fullName + '.' + name); + if (result != null && result instanceof Descriptor) { + return (Descriptor)result; + } else { + return null; + } + } + + /** + * Finds a nested enum type by name. + * @param name The unqualified name of the nested type (e.g. "Foo"). + * @return The types's descriptor, or {@code null} if not found. + */ + public EnumDescriptor findEnumTypeByName(final String name) { + final GenericDescriptor result = + file.pool.findSymbol(fullName + '.' + name); + if (result != null && result instanceof EnumDescriptor) { + return (EnumDescriptor)result; + } else { + return null; + } + } + + private final int index; + private DescriptorProto proto; + private final String fullName; + private final FileDescriptor file; + private final Descriptor containingType; + private final Descriptor[] nestedTypes; + private final EnumDescriptor[] enumTypes; + private final FieldDescriptor[] fields; + private final FieldDescriptor[] extensions; + private final OneofDescriptor[] oneofs; + + // Used to create a placeholder when the type cannot be found. + Descriptor(final String fullname) throws DescriptorValidationException { + String name = fullname; + String packageName = ""; + int pos = fullname.lastIndexOf('.'); + if (pos != -1) { + name = fullname.substring(pos + 1); + packageName = fullname.substring(0, pos); + } + this.index = 0; + this.proto = DescriptorProto.newBuilder().setName(name).addExtensionRange( + DescriptorProto.ExtensionRange.newBuilder().setStart(1) + .setEnd(536870912).build()).build(); + this.fullName = fullname; + this.containingType = null; + + this.nestedTypes = new Descriptor[0]; + this.enumTypes = new EnumDescriptor[0]; + this.fields = new FieldDescriptor[0]; + this.extensions = new FieldDescriptor[0]; + this.oneofs = new OneofDescriptor[0]; + + // Create a placeholder FileDescriptor to hold this message. + this.file = new FileDescriptor(packageName, this); + } + + private Descriptor(final DescriptorProto proto, + final FileDescriptor file, + final Descriptor parent, + final int index) + throws DescriptorValidationException { + this.index = index; + this.proto = proto; + fullName = computeFullName(file, parent, proto.getName()); + this.file = file; + containingType = parent; + + oneofs = new OneofDescriptor[proto.getOneofDeclCount()]; + for (int i = 0; i < proto.getOneofDeclCount(); i++) { + oneofs[i] = new OneofDescriptor( + proto.getOneofDecl(i), file, this, i); + } + + nestedTypes = new Descriptor[proto.getNestedTypeCount()]; + for (int i = 0; i < proto.getNestedTypeCount(); i++) { + nestedTypes[i] = new Descriptor( + proto.getNestedType(i), file, this, i); + } + + enumTypes = new EnumDescriptor[proto.getEnumTypeCount()]; + for (int i = 0; i < proto.getEnumTypeCount(); i++) { + enumTypes[i] = new EnumDescriptor( + proto.getEnumType(i), file, this, i); + } + + fields = new FieldDescriptor[proto.getFieldCount()]; + for (int i = 0; i < proto.getFieldCount(); i++) { + fields[i] = new FieldDescriptor( + proto.getField(i), file, this, i, false); + } + + extensions = new FieldDescriptor[proto.getExtensionCount()]; + for (int i = 0; i < proto.getExtensionCount(); i++) { + extensions[i] = new FieldDescriptor( + proto.getExtension(i), file, this, i, true); + } + + for (int i = 0; i < proto.getOneofDeclCount(); i++) { + oneofs[i].fields = new FieldDescriptor[oneofs[i].getFieldCount()]; + oneofs[i].fieldCount = 0; + } + for (int i = 0; i < proto.getFieldCount(); i++) { + OneofDescriptor oneofDescriptor = fields[i].getContainingOneof(); + if (oneofDescriptor != null) { + oneofDescriptor.fields[oneofDescriptor.fieldCount++] = fields[i]; + } + } + + file.pool.addSymbol(this); + } + + /** Look up and cross-link all field types, etc. */ + private void crossLink() throws DescriptorValidationException { + for (final Descriptor nestedType : nestedTypes) { + nestedType.crossLink(); + } + + for (final FieldDescriptor field : fields) { + field.crossLink(); + } + + for (final FieldDescriptor extension : extensions) { + extension.crossLink(); + } + } + + /** See {@link FileDescriptor#setProto}. */ + private void setProto(final DescriptorProto proto) { + this.proto = proto; + + for (int i = 0; i < nestedTypes.length; i++) { + nestedTypes[i].setProto(proto.getNestedType(i)); + } + + for (int i = 0; i < enumTypes.length; i++) { + enumTypes[i].setProto(proto.getEnumType(i)); + } + + for (int i = 0; i < fields.length; i++) { + fields[i].setProto(proto.getField(i)); + } + + for (int i = 0; i < extensions.length; i++) { + extensions[i].setProto(proto.getExtension(i)); + } + } + } + + // ================================================================= + + /** Describes a field of a message type. */ + public static final class FieldDescriptor + extends GenericDescriptor + implements Comparable<FieldDescriptor>, + FieldSet.FieldDescriptorLite<FieldDescriptor> { + /** + * Get the index of this descriptor within its parent. + * @see Descriptors.Descriptor#getIndex() + */ + public int getIndex() { return index; } + + /** Convert the descriptor to its protocol message representation. */ + public FieldDescriptorProto toProto() { return proto; } + + /** Get the field's unqualified name. */ + public String getName() { return proto.getName(); } + + /** Get the field's number. */ + public int getNumber() { return proto.getNumber(); } + + /** + * Get the field's fully-qualified name. + * @see Descriptors.Descriptor#getFullName() + */ + public String getFullName() { return fullName; } + + /** Get the JSON name of this field. */ + public String getJsonName() { + return jsonName; + } + + /** + * Get the field's java type. This is just for convenience. Every + * {@code FieldDescriptorProto.Type} maps to exactly one Java type. + */ + public JavaType getJavaType() { return type.getJavaType(); } + + /** For internal use only. */ + public WireFormat.JavaType getLiteJavaType() { + return getLiteType().getJavaType(); + } + + /** Get the {@code FileDescriptor} containing this descriptor. */ + public FileDescriptor getFile() { return file; } + + /** Get the field's declared type. */ + public Type getType() { return type; } + + /** For internal use only. */ + public WireFormat.FieldType getLiteType() { + return table[type.ordinal()]; + } + + /** For internal use only. */ + public boolean needsUtf8Check() { + if (type != Type.STRING) { + return false; + } + if (getContainingType().getOptions().getMapEntry()) { + // Always enforce strict UTF-8 checking for map fields. + return true; + } + if (getFile().getSyntax() == Syntax.PROTO3) { + return true; + } + return getFile().getOptions().getJavaStringCheckUtf8(); + } + + public 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. + private static final WireFormat.FieldType[] table = + WireFormat.FieldType.values(); + + /** Is this field declared required? */ + public boolean isRequired() { + return proto.getLabel() == FieldDescriptorProto.Label.LABEL_REQUIRED; + } + + /** Is this field declared optional? */ + public boolean isOptional() { + return proto.getLabel() == FieldDescriptorProto.Label.LABEL_OPTIONAL; + } + + /** Is this field declared repeated? */ + public boolean isRepeated() { + return proto.getLabel() == FieldDescriptorProto.Label.LABEL_REPEATED; + } + + /** Does this field have the {@code [packed = true]} option or is this field + * packable in proto3 and not explicitly setted to unpacked? + */ + public boolean isPacked() { + if (!isPackable()) { + return false; + } + if (getFile().getSyntax() == FileDescriptor.Syntax.PROTO2) { + return getOptions().getPacked(); + } else { + return !getOptions().hasPacked() || getOptions().getPacked(); + } + } + + /** Can this field be packed? i.e. is it a repeated primitive field? */ + public boolean isPackable() { + return isRepeated() && getLiteType().isPackable(); + } + + /** Returns true if the field had an explicitly-defined default value. */ + public boolean hasDefaultValue() { return proto.hasDefaultValue(); } + + /** + * Returns the field's default value. Valid for all types except for + * messages and groups. For all other types, the object returned is of + * the same class that would returned by Message.getField(this). + */ + public Object getDefaultValue() { + if (getJavaType() == JavaType.MESSAGE) { + throw new UnsupportedOperationException( + "FieldDescriptor.getDefaultValue() called on an embedded message " + + "field."); + } + return defaultValue; + } + + /** Get the {@code FieldOptions}, defined in {@code descriptor.proto}. */ + public FieldOptions getOptions() { return proto.getOptions(); } + + /** Is this field an extension? */ + public boolean isExtension() { return proto.hasExtendee(); } + + /** + * Get the field's containing type. For extensions, this is the type being + * extended, not the location where the extension was defined. See + * {@link #getExtensionScope()}. + */ + public Descriptor getContainingType() { return containingType; } + + /** Get the field's containing oneof. */ + public OneofDescriptor getContainingOneof() { return containingOneof; } + + /** + * For extensions defined nested within message types, gets the outer + * type. Not valid for non-extension fields. For example, consider + * this {@code .proto} file: + * <pre> + * message Foo { + * extensions 1000 to max; + * } + * extend Foo { + * optional int32 baz = 1234; + * } + * message Bar { + * extend Foo { + * optional int32 qux = 4321; + * } + * } + * </pre> + * Both {@code baz}'s and {@code qux}'s containing type is {@code Foo}. + * However, {@code baz}'s extension scope is {@code null} while + * {@code qux}'s extension scope is {@code Bar}. + */ + public Descriptor getExtensionScope() { + if (!isExtension()) { + throw new UnsupportedOperationException( + "This field is not an extension."); + } + return extensionScope; + } + + /** For embedded message and group fields, gets the field's type. */ + public Descriptor getMessageType() { + if (getJavaType() != JavaType.MESSAGE) { + throw new UnsupportedOperationException( + "This field is not of message type."); + } + return messageType; + } + + /** For enum fields, gets the field's type. */ + public EnumDescriptor getEnumType() { + if (getJavaType() != JavaType.ENUM) { + throw new UnsupportedOperationException( + "This field is not of enum type."); + } + return enumType; + } + + /** + * Compare with another {@code FieldDescriptor}. This orders fields in + * "canonical" order, which simply means ascending order by field number. + * {@code other} must be a field of the same type -- i.e. + * {@code getContainingType()} must return the same {@code Descriptor} for + * both fields. + * + * @return negative, zero, or positive if {@code this} is less than, + * equal to, or greater than {@code other}, respectively. + */ + public int compareTo(final FieldDescriptor other) { + if (other.containingType != containingType) { + throw new IllegalArgumentException( + "FieldDescriptors can only be compared to other FieldDescriptors " + + "for fields of the same message type."); + } + return getNumber() - other.getNumber(); + } + + @Override + public String toString() { + return getFullName(); + } + + private final int index; + + private FieldDescriptorProto proto; + private final String fullName; + private final String jsonName; + private final FileDescriptor file; + private final Descriptor extensionScope; + + // Possibly initialized during cross-linking. + private Type type; + private Descriptor containingType; + private Descriptor messageType; + private OneofDescriptor containingOneof; + private EnumDescriptor enumType; + private Object defaultValue; + + public enum Type { + DOUBLE (JavaType.DOUBLE ), + FLOAT (JavaType.FLOAT ), + INT64 (JavaType.LONG ), + UINT64 (JavaType.LONG ), + INT32 (JavaType.INT ), + FIXED64 (JavaType.LONG ), + FIXED32 (JavaType.INT ), + BOOL (JavaType.BOOLEAN ), + STRING (JavaType.STRING ), + GROUP (JavaType.MESSAGE ), + MESSAGE (JavaType.MESSAGE ), + BYTES (JavaType.BYTE_STRING), + UINT32 (JavaType.INT ), + ENUM (JavaType.ENUM ), + SFIXED32(JavaType.INT ), + SFIXED64(JavaType.LONG ), + SINT32 (JavaType.INT ), + SINT64 (JavaType.LONG ); + + Type(final JavaType javaType) { + this.javaType = javaType; + } + + private JavaType javaType; + + public FieldDescriptorProto.Type toProto() { + return FieldDescriptorProto.Type.valueOf(ordinal() + 1); + } + public JavaType getJavaType() { return javaType; } + + public static Type valueOf(final FieldDescriptorProto.Type type) { + return values()[type.getNumber() - 1]; + } + } + + static { + // Refuse to init if someone added a new declared type. + if (Type.values().length != FieldDescriptorProto.Type.values().length) { + throw new RuntimeException("" + + "descriptor.proto has a new declared type but Descriptors.java " + + "wasn't updated."); + } + } + + public enum JavaType { + INT(0), + LONG(0L), + FLOAT(0F), + DOUBLE(0D), + BOOLEAN(false), + STRING(""), + BYTE_STRING(ByteString.EMPTY), + ENUM(null), + MESSAGE(null); + + JavaType(final Object defaultDefault) { + this.defaultDefault = defaultDefault; + } + + /** + * The default default value for fields of this type, if it's a primitive + * type. This is meant for use inside this file only, hence is private. + */ + private final Object defaultDefault; + } + + // TODO(xiaofeng): Implement it consistently across different languages. See b/24751348. + private static String fieldNameToLowerCamelCase(String name) { + StringBuilder result = new StringBuilder(name.length()); + boolean isNextUpperCase = false; + for (int i = 0; i < name.length(); i++) { + Character ch = name.charAt(i); + if (Character.isLowerCase(ch)) { + if (isNextUpperCase) { + result.append(Character.toUpperCase(ch)); + } else { + result.append(ch); + } + isNextUpperCase = false; + } else if (Character.isUpperCase(ch)) { + if (i == 0) { + // Force first letter to lower-case. + result.append(Character.toLowerCase(ch)); + } else { + // Capital letters after the first are left as-is. + result.append(ch); + } + isNextUpperCase = false; + } else if (Character.isDigit(ch)) { + result.append(ch); + isNextUpperCase = false; + } else { + isNextUpperCase = true; + } + } + return result.toString(); + } + + private FieldDescriptor(final FieldDescriptorProto proto, + final FileDescriptor file, + final Descriptor parent, + final int index, + final boolean isExtension) + throws DescriptorValidationException { + this.index = index; + this.proto = proto; + fullName = computeFullName(file, parent, proto.getName()); + this.file = file; + if (proto.hasJsonName()) { + jsonName = proto.getJsonName(); + } else { + jsonName = fieldNameToLowerCamelCase(proto.getName()); + } + + if (proto.hasType()) { + type = Type.valueOf(proto.getType()); + } + + if (getNumber() <= 0) { + throw new DescriptorValidationException(this, + "Field numbers must be positive integers."); + } + + if (isExtension) { + if (!proto.hasExtendee()) { + throw new DescriptorValidationException(this, + "FieldDescriptorProto.extendee not set for extension field."); + } + containingType = null; // Will be filled in when cross-linking + if (parent != null) { + extensionScope = parent; + } else { + extensionScope = null; + } + + if (proto.hasOneofIndex()) { + throw new DescriptorValidationException(this, + "FieldDescriptorProto.oneof_index set for extension field."); + } + containingOneof = null; + } else { + if (proto.hasExtendee()) { + throw new DescriptorValidationException(this, + "FieldDescriptorProto.extendee set for non-extension field."); + } + containingType = parent; + + if (proto.hasOneofIndex()) { + if (proto.getOneofIndex() < 0 || + proto.getOneofIndex() >= parent.toProto().getOneofDeclCount()) { + throw new DescriptorValidationException(this, + "FieldDescriptorProto.oneof_index is out of range for type " + + parent.getName()); + } + containingOneof = parent.getOneofs().get(proto.getOneofIndex()); + containingOneof.fieldCount++; + } else { + containingOneof = null; + } + extensionScope = null; + } + + file.pool.addSymbol(this); + } + + /** Look up and cross-link all field types, etc. */ + private void crossLink() throws DescriptorValidationException { + if (proto.hasExtendee()) { + final GenericDescriptor extendee = + file.pool.lookupSymbol(proto.getExtendee(), this, + DescriptorPool.SearchFilter.TYPES_ONLY); + if (!(extendee instanceof Descriptor)) { + throw new DescriptorValidationException(this, + '\"' + proto.getExtendee() + "\" is not a message type."); + } + containingType = (Descriptor)extendee; + + if (!getContainingType().isExtensionNumber(getNumber())) { + throw new DescriptorValidationException(this, + '\"' + getContainingType().getFullName() + + "\" does not declare " + getNumber() + + " as an extension number."); + } + } + + if (proto.hasTypeName()) { + final GenericDescriptor typeDescriptor = + file.pool.lookupSymbol(proto.getTypeName(), this, + DescriptorPool.SearchFilter.TYPES_ONLY); + + if (!proto.hasType()) { + // Choose field type based on symbol. + if (typeDescriptor instanceof Descriptor) { + type = Type.MESSAGE; + } else if (typeDescriptor instanceof EnumDescriptor) { + type = Type.ENUM; + } else { + throw new DescriptorValidationException(this, + '\"' + proto.getTypeName() + "\" is not a type."); + } + } + + if (getJavaType() == JavaType.MESSAGE) { + if (!(typeDescriptor instanceof Descriptor)) { + throw new DescriptorValidationException(this, + '\"' + proto.getTypeName() + "\" is not a message type."); + } + messageType = (Descriptor)typeDescriptor; + + if (proto.hasDefaultValue()) { + throw new DescriptorValidationException(this, + "Messages can't have default values."); + } + } else if (getJavaType() == JavaType.ENUM) { + if (!(typeDescriptor instanceof EnumDescriptor)) { + throw new DescriptorValidationException(this, + '\"' + proto.getTypeName() + "\" is not an enum type."); + } + enumType = (EnumDescriptor)typeDescriptor; + } else { + throw new DescriptorValidationException(this, + "Field with primitive type has type_name."); + } + } else { + if (getJavaType() == JavaType.MESSAGE || + getJavaType() == JavaType.ENUM) { + throw new DescriptorValidationException(this, + "Field with message or enum type missing type_name."); + } + } + + // Only repeated primitive fields may be packed. + if (proto.getOptions().getPacked() && !isPackable()) { + throw new DescriptorValidationException(this, + "[packed = true] can only be specified for repeated primitive " + + "fields."); + } + + // We don't attempt to parse the default value until here because for + // enums we need the enum type's descriptor. + if (proto.hasDefaultValue()) { + if (isRepeated()) { + throw new DescriptorValidationException(this, + "Repeated fields cannot have default values."); + } + + try { + switch (getType()) { + case INT32: + case SINT32: + case SFIXED32: + defaultValue = TextFormat.parseInt32(proto.getDefaultValue()); + break; + case UINT32: + case FIXED32: + defaultValue = TextFormat.parseUInt32(proto.getDefaultValue()); + break; + case INT64: + case SINT64: + case SFIXED64: + defaultValue = TextFormat.parseInt64(proto.getDefaultValue()); + break; + case UINT64: + case FIXED64: + defaultValue = TextFormat.parseUInt64(proto.getDefaultValue()); + break; + case FLOAT: + if (proto.getDefaultValue().equals("inf")) { + defaultValue = Float.POSITIVE_INFINITY; + } else if (proto.getDefaultValue().equals("-inf")) { + defaultValue = Float.NEGATIVE_INFINITY; + } else if (proto.getDefaultValue().equals("nan")) { + defaultValue = Float.NaN; + } else { + defaultValue = Float.valueOf(proto.getDefaultValue()); + } + break; + case DOUBLE: + if (proto.getDefaultValue().equals("inf")) { + defaultValue = Double.POSITIVE_INFINITY; + } else if (proto.getDefaultValue().equals("-inf")) { + defaultValue = Double.NEGATIVE_INFINITY; + } else if (proto.getDefaultValue().equals("nan")) { + defaultValue = Double.NaN; + } else { + defaultValue = Double.valueOf(proto.getDefaultValue()); + } + break; + case BOOL: + defaultValue = Boolean.valueOf(proto.getDefaultValue()); + break; + case STRING: + defaultValue = proto.getDefaultValue(); + break; + case BYTES: + try { + defaultValue = + TextFormat.unescapeBytes(proto.getDefaultValue()); + } catch (TextFormat.InvalidEscapeSequenceException e) { + throw new DescriptorValidationException(this, + "Couldn't parse default value: " + e.getMessage(), e); + } + break; + case ENUM: + defaultValue = enumType.findValueByName(proto.getDefaultValue()); + if (defaultValue == null) { + throw new DescriptorValidationException(this, + "Unknown enum default value: \"" + + proto.getDefaultValue() + '\"'); + } + break; + case MESSAGE: + case GROUP: + throw new DescriptorValidationException(this, + "Message type had default value."); + } + } catch (NumberFormatException e) { + throw new DescriptorValidationException(this, + "Could not parse default value: \"" + + proto.getDefaultValue() + '\"', e); + } + } else { + // Determine the default default for this field. + if (isRepeated()) { + defaultValue = Collections.emptyList(); + } else { + switch (getJavaType()) { + case ENUM: + // We guarantee elsewhere that an enum type always has at least + // one possible value. + defaultValue = enumType.getValues().get(0); + break; + case MESSAGE: + defaultValue = null; + break; + default: + defaultValue = getJavaType().defaultDefault; + break; + } + } + } + + if (!isExtension()) { + file.pool.addFieldByNumber(this); + } + + if (containingType != null && + containingType.getOptions().getMessageSetWireFormat()) { + if (isExtension()) { + if (!isOptional() || getType() != Type.MESSAGE) { + throw new DescriptorValidationException(this, + "Extensions of MessageSets must be optional messages."); + } + } else { + throw new DescriptorValidationException(this, + "MessageSets cannot have fields, only extensions."); + } + } + } + + /** See {@link FileDescriptor#setProto}. */ + private void setProto(final FieldDescriptorProto proto) { + this.proto = proto; + } + + /** + * For internal use only. This is to satisfy the FieldDescriptorLite + * interface. + */ + public MessageLite.Builder internalMergeFrom( + MessageLite.Builder to, MessageLite from) { + // FieldDescriptors are only used with non-lite messages so we can just + // down-cast and call mergeFrom directly. + return ((Message.Builder) to).mergeFrom((Message) from); + } + + } + + // ================================================================= + + /** Describes an enum type. */ + public static final class EnumDescriptor extends GenericDescriptor + implements Internal.EnumLiteMap<EnumValueDescriptor> { + /** + * Get the index of this descriptor within its parent. + * @see Descriptors.Descriptor#getIndex() + */ + public int getIndex() { return index; } + + /** Convert the descriptor to its protocol message representation. */ + public EnumDescriptorProto toProto() { return proto; } + + /** Get the type's unqualified name. */ + public String getName() { return proto.getName(); } + + /** + * Get the type's fully-qualified name. + * @see Descriptors.Descriptor#getFullName() + */ + public String getFullName() { return fullName; } + + /** Get the {@link FileDescriptor} containing this descriptor. */ + public FileDescriptor getFile() { return file; } + + /** If this is a nested type, get the outer descriptor, otherwise null. */ + public Descriptor getContainingType() { return containingType; } + + /** Get the {@code EnumOptions}, defined in {@code descriptor.proto}. */ + public EnumOptions getOptions() { return proto.getOptions(); } + + /** Get a list of defined values for this enum. */ + public List<EnumValueDescriptor> getValues() { + return Collections.unmodifiableList(Arrays.asList(values)); + } + + /** + * Find an enum value by name. + * @param name The unqualified name of the value (e.g. "FOO"). + * @return the value's descriptor, or {@code null} if not found. + */ + public EnumValueDescriptor findValueByName(final String name) { + final GenericDescriptor result = + file.pool.findSymbol(fullName + '.' + name); + if (result != null && result instanceof EnumValueDescriptor) { + return (EnumValueDescriptor)result; + } else { + return null; + } + } + + /** + * Find an enum value by number. If multiple enum values have the same + * number, this returns the first defined value with that number. + * @param number The value's number. + * @return the value's descriptor, or {@code null} if not found. + */ + public EnumValueDescriptor findValueByNumber(final int number) { + 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<EnumValueDescriptor> reference = unknownValues.get(key); + if (reference != null) { + result = reference.get(); + } + if (result == null) { + result = new EnumValueDescriptor(file, this, key); + unknownValues.put(key, new WeakReference<EnumValueDescriptor>(result)); + } + } + return result; + } + + // Used in tests only. + int getUnknownEnumValueDescriptorCount() { + return unknownValues.size(); + } + + private final int index; + private EnumDescriptorProto proto; + private final String fullName; + private final FileDescriptor file; + private final Descriptor containingType; + private EnumValueDescriptor[] values; + private final WeakHashMap<Integer, WeakReference<EnumValueDescriptor>> unknownValues = + new WeakHashMap<Integer, WeakReference<EnumValueDescriptor>>(); + + private EnumDescriptor(final EnumDescriptorProto proto, + final FileDescriptor file, + final Descriptor parent, + final int index) + throws DescriptorValidationException { + this.index = index; + this.proto = proto; + fullName = computeFullName(file, parent, proto.getName()); + this.file = file; + containingType = parent; + + if (proto.getValueCount() == 0) { + // We cannot allow enums with no values because this would mean there + // would be no valid default value for fields of this type. + throw new DescriptorValidationException(this, + "Enums must contain at least one value."); + } + + values = new EnumValueDescriptor[proto.getValueCount()]; + for (int i = 0; i < proto.getValueCount(); i++) { + values[i] = new EnumValueDescriptor( + proto.getValue(i), file, this, i); + } + + file.pool.addSymbol(this); + } + + /** See {@link FileDescriptor#setProto}. */ + private void setProto(final EnumDescriptorProto proto) { + this.proto = proto; + + for (int i = 0; i < values.length; i++) { + values[i].setProto(proto.getValue(i)); + } + } + } + + // ================================================================= + + /** + * Describes one value within an enum type. Note that multiple defined + * values may have the same number. In generated Java code, all values + * with the same number after the first become aliases of the first. + * However, they still have independent EnumValueDescriptors. + */ + public static final class EnumValueDescriptor extends GenericDescriptor + implements Internal.EnumLite { + /** + * Get the index of this descriptor within its parent. + * @see Descriptors.Descriptor#getIndex() + */ + public int getIndex() { return index; } + + /** Convert the descriptor to its protocol message representation. */ + public EnumValueDescriptorProto toProto() { return proto; } + + /** Get the value's unqualified name. */ + public String getName() { return proto.getName(); } + + /** Get the value's number. */ + public int getNumber() { return proto.getNumber(); } + + @Override + public String toString() { return proto.getName(); } + + /** + * Get the value's fully-qualified name. + * @see Descriptors.Descriptor#getFullName() + */ + public String getFullName() { return fullName; } + + /** Get the {@link FileDescriptor} containing this descriptor. */ + public FileDescriptor getFile() { return file; } + + /** Get the value's enum type. */ + public EnumDescriptor getType() { return type; } + + /** + * Get the {@code EnumValueOptions}, defined in {@code descriptor.proto}. + */ + public EnumValueOptions getOptions() { return proto.getOptions(); } + + private final int index; + private EnumValueDescriptorProto proto; + private final String fullName; + private final FileDescriptor file; + private final EnumDescriptor type; + + private EnumValueDescriptor(final EnumValueDescriptorProto proto, + final FileDescriptor file, + final EnumDescriptor parent, + final int index) + throws DescriptorValidationException { + this.index = index; + this.proto = proto; + this.file = file; + type = parent; + + fullName = parent.getFullName() + '.' + proto.getName(); + + 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) { + this.proto = proto; + } + } + + // ================================================================= + + /** Describes a service type. */ + public static final class ServiceDescriptor extends GenericDescriptor { + /** + * Get the index of this descriptor within its parent. + * * @see Descriptors.Descriptor#getIndex() + */ + public int getIndex() { return index; } + + /** Convert the descriptor to its protocol message representation. */ + public ServiceDescriptorProto toProto() { return proto; } + + /** Get the type's unqualified name. */ + public String getName() { return proto.getName(); } + + /** + * Get the type's fully-qualified name. + * @see Descriptors.Descriptor#getFullName() + */ + public String getFullName() { return fullName; } + + /** Get the {@link FileDescriptor} containing this descriptor. */ + public FileDescriptor getFile() { return file; } + + /** Get the {@code ServiceOptions}, defined in {@code descriptor.proto}. */ + public ServiceOptions getOptions() { return proto.getOptions(); } + + /** Get a list of methods for this service. */ + public List<MethodDescriptor> getMethods() { + return Collections.unmodifiableList(Arrays.asList(methods)); + } + + /** + * Find a method by name. + * @param name The unqualified name of the method (e.g. "Foo"). + * @return the method's descriptor, or {@code null} if not found. + */ + public MethodDescriptor findMethodByName(final String name) { + final GenericDescriptor result = + file.pool.findSymbol(fullName + '.' + name); + if (result != null && result instanceof MethodDescriptor) { + return (MethodDescriptor)result; + } else { + return null; + } + } + + private final int index; + private ServiceDescriptorProto proto; + private final String fullName; + private final FileDescriptor file; + private MethodDescriptor[] methods; + + private ServiceDescriptor(final ServiceDescriptorProto proto, + final FileDescriptor file, + final int index) + throws DescriptorValidationException { + this.index = index; + this.proto = proto; + fullName = computeFullName(file, null, proto.getName()); + this.file = file; + + methods = new MethodDescriptor[proto.getMethodCount()]; + for (int i = 0; i < proto.getMethodCount(); i++) { + methods[i] = new MethodDescriptor( + proto.getMethod(i), file, this, i); + } + + file.pool.addSymbol(this); + } + + private void crossLink() throws DescriptorValidationException { + for (final MethodDescriptor method : methods) { + method.crossLink(); + } + } + + /** See {@link FileDescriptor#setProto}. */ + private void setProto(final ServiceDescriptorProto proto) { + this.proto = proto; + + for (int i = 0; i < methods.length; i++) { + methods[i].setProto(proto.getMethod(i)); + } + } + } + + // ================================================================= + + /** + * Describes one method within a service type. + */ + public static final class MethodDescriptor extends GenericDescriptor { + /** + * Get the index of this descriptor within its parent. + * * @see Descriptors.Descriptor#getIndex() + */ + public int getIndex() { return index; } + + /** Convert the descriptor to its protocol message representation. */ + public MethodDescriptorProto toProto() { return proto; } + + /** Get the method's unqualified name. */ + public String getName() { return proto.getName(); } + + /** + * Get the method's fully-qualified name. + * @see Descriptors.Descriptor#getFullName() + */ + public String getFullName() { return fullName; } + + /** Get the {@link FileDescriptor} containing this descriptor. */ + public FileDescriptor getFile() { return file; } + + /** Get the method's service type. */ + public ServiceDescriptor getService() { return service; } + + /** Get the method's input type. */ + public Descriptor getInputType() { return inputType; } + + /** Get the method's output type. */ + public Descriptor getOutputType() { return outputType; } + + /** + * Get the {@code MethodOptions}, defined in {@code descriptor.proto}. + */ + public MethodOptions getOptions() { return proto.getOptions(); } + + private final int index; + private MethodDescriptorProto proto; + private final String fullName; + private final FileDescriptor file; + private final ServiceDescriptor service; + + // Initialized during cross-linking. + private Descriptor inputType; + private Descriptor outputType; + + private MethodDescriptor(final MethodDescriptorProto proto, + final FileDescriptor file, + final ServiceDescriptor parent, + final int index) + throws DescriptorValidationException { + this.index = index; + this.proto = proto; + this.file = file; + service = parent; + + fullName = parent.getFullName() + '.' + proto.getName(); + + file.pool.addSymbol(this); + } + + private void crossLink() throws DescriptorValidationException { + final GenericDescriptor input = + file.pool.lookupSymbol(proto.getInputType(), this, + DescriptorPool.SearchFilter.TYPES_ONLY); + if (!(input instanceof Descriptor)) { + throw new DescriptorValidationException(this, + '\"' + proto.getInputType() + "\" is not a message type."); + } + inputType = (Descriptor)input; + + final GenericDescriptor output = + file.pool.lookupSymbol(proto.getOutputType(), this, + DescriptorPool.SearchFilter.TYPES_ONLY); + if (!(output instanceof Descriptor)) { + throw new DescriptorValidationException(this, + '\"' + proto.getOutputType() + "\" is not a message type."); + } + outputType = (Descriptor)output; + } + + /** See {@link FileDescriptor#setProto}. */ + private void setProto(final MethodDescriptorProto proto) { + this.proto = proto; + } + } + + // ================================================================= + + private static String computeFullName(final FileDescriptor file, + final Descriptor parent, + final String name) { + if (parent != null) { + return parent.getFullName() + '.' + name; + } else if (file.getPackage().length() > 0) { + return file.getPackage() + '.' + name; + } else { + return name; + } + } + + // ================================================================= + + /** + * All descriptors implement this to make it easier to implement tools like + * {@code DescriptorPool}.<p> + * + * This class is public so that the methods it exposes can be called from + * outside of this package. However, it should only be subclassed from + * nested classes of Descriptors. + */ + public abstract static class GenericDescriptor { + public abstract Message toProto(); + public abstract String getName(); + public abstract String getFullName(); + public abstract FileDescriptor getFile(); + } + + /** + * Thrown when building descriptors fails because the source DescriptorProtos + * are not valid. + */ + public static class DescriptorValidationException extends Exception { + private static final long serialVersionUID = 5750205775490483148L; + + /** Gets the full name of the descriptor where the error occurred. */ + public String getProblemSymbolName() { return name; } + + /** + * Gets the protocol message representation of the invalid descriptor. + */ + public Message getProblemProto() { return proto; } + + /** + * Gets a human-readable description of the error. + */ + public String getDescription() { return description; } + + private final String name; + private final Message proto; + private final String description; + + private DescriptorValidationException( + final GenericDescriptor problemDescriptor, + final String description) { + super(problemDescriptor.getFullName() + ": " + description); + + // Note that problemDescriptor may be partially uninitialized, so we + // don't want to expose it directly to the user. So, we only provide + // the name and the original proto. + name = problemDescriptor.getFullName(); + proto = problemDescriptor.toProto(); + this.description = description; + } + + private DescriptorValidationException( + final GenericDescriptor problemDescriptor, + final String description, + final Throwable cause) { + this(problemDescriptor, description); + initCause(cause); + } + + private DescriptorValidationException( + final FileDescriptor problemDescriptor, + final String description) { + super(problemDescriptor.getName() + ": " + description); + + // Note that problemDescriptor may be partially uninitialized, so we + // don't want to expose it directly to the user. So, we only provide + // the name and the original proto. + name = problemDescriptor.getName(); + proto = problemDescriptor.toProto(); + this.description = description; + } + } + + // ================================================================= + + /** + * A private helper class which contains lookup tables containing all the + * descriptors defined in a particular file. + */ + private static final class DescriptorPool { + + /** Defines what subclass of descriptors to search in the descriptor pool. + */ + enum SearchFilter { + TYPES_ONLY, AGGREGATES_ONLY, ALL_SYMBOLS + } + + DescriptorPool(final FileDescriptor[] dependencies, + boolean allowUnknownDependencies) { + this.dependencies = new HashSet<FileDescriptor>(); + this.allowUnknownDependencies = allowUnknownDependencies; + + for (int i = 0; i < dependencies.length; i++) { + this.dependencies.add(dependencies[i]); + importPublicDependencies(dependencies[i]); + } + + for (final FileDescriptor dependency : this.dependencies) { + try { + addPackage(dependency.getPackage(), dependency); + } catch (DescriptorValidationException e) { + // Can't happen, because addPackage() only fails when the name + // conflicts with a non-package, but we have not yet added any + // non-packages at this point. + assert false; + } + } + } + + /** Find and put public dependencies of the file into dependencies set.*/ + private void importPublicDependencies(final FileDescriptor file) { + for (FileDescriptor dependency : file.getPublicDependencies()) { + if (dependencies.add(dependency)) { + importPublicDependencies(dependency); + } + } + } + + private final Set<FileDescriptor> dependencies; + private boolean allowUnknownDependencies; + + private final Map<String, GenericDescriptor> descriptorsByName = + new HashMap<String, GenericDescriptor>(); + private final Map<DescriptorIntPair, FieldDescriptor> fieldsByNumber = + new HashMap<DescriptorIntPair, FieldDescriptor>(); + private final Map<DescriptorIntPair, EnumValueDescriptor> enumValuesByNumber + = new HashMap<DescriptorIntPair, EnumValueDescriptor>(); + + /** Find a generic descriptor by fully-qualified name. */ + GenericDescriptor findSymbol(final String fullName) { + return findSymbol(fullName, SearchFilter.ALL_SYMBOLS); + } + + /** Find a descriptor by fully-qualified name and given option to only + * search valid field type descriptors. + */ + GenericDescriptor findSymbol(final String fullName, + final SearchFilter filter) { + GenericDescriptor result = descriptorsByName.get(fullName); + if (result != null) { + if ((filter==SearchFilter.ALL_SYMBOLS) || + ((filter==SearchFilter.TYPES_ONLY) && isType(result)) || + ((filter==SearchFilter.AGGREGATES_ONLY) && isAggregate(result))) { + return result; + } + } + + for (final FileDescriptor dependency : dependencies) { + result = dependency.pool.descriptorsByName.get(fullName); + if (result != null) { + if ((filter==SearchFilter.ALL_SYMBOLS) || + ((filter==SearchFilter.TYPES_ONLY) && isType(result)) || + ((filter==SearchFilter.AGGREGATES_ONLY) && isAggregate(result))) { + return result; + } + } + } + + return null; + } + + /** Checks if the descriptor is a valid type for a message field. */ + boolean isType(GenericDescriptor descriptor) { + return (descriptor instanceof Descriptor) || + (descriptor instanceof EnumDescriptor); + } + + /** Checks if the descriptor is a valid namespace type. */ + boolean isAggregate(GenericDescriptor descriptor) { + return (descriptor instanceof Descriptor) || + (descriptor instanceof EnumDescriptor) || + (descriptor instanceof PackageDescriptor) || + (descriptor instanceof ServiceDescriptor); + } + + /** + * Look up a type descriptor by name, relative to some other descriptor. + * The name may be fully-qualified (with a leading '.'), + * partially-qualified, or unqualified. C++-like name lookup semantics + * are used to search for the matching descriptor. + */ + GenericDescriptor lookupSymbol(final String name, + final GenericDescriptor relativeTo, + final DescriptorPool.SearchFilter filter) + throws DescriptorValidationException { + // TODO(kenton): This could be optimized in a number of ways. + + GenericDescriptor result; + String fullname; + if (name.startsWith(".")) { + // Fully-qualified name. + fullname = name.substring(1); + result = findSymbol(fullname, filter); + } else { + // If "name" is a compound identifier, we want to search for the + // first component of it, then search within it for the rest. + // If name is something like "Foo.Bar.baz", and symbols named "Foo" are + // defined in multiple parent scopes, we only want to find "Bar.baz" in + // the innermost one. E.g., the following should produce an error: + // message Bar { message Baz {} } + // message Foo { + // message Bar { + // } + // optional Bar.Baz baz = 1; + // } + // So, we look for just "Foo" first, then look for "Bar.baz" within it + // if found. + final int firstPartLength = name.indexOf('.'); + final String firstPart; + if (firstPartLength == -1) { + firstPart = name; + } else { + firstPart = name.substring(0, firstPartLength); + } + + // We will search each parent scope of "relativeTo" looking for the + // symbol. + final StringBuilder scopeToTry = + new StringBuilder(relativeTo.getFullName()); + + while (true) { + // Chop off the last component of the scope. + final int dotpos = scopeToTry.lastIndexOf("."); + if (dotpos == -1) { + fullname = name; + result = findSymbol(name, filter); + break; + } else { + scopeToTry.setLength(dotpos + 1); + + // Append firstPart and try to find + scopeToTry.append(firstPart); + result = findSymbol(scopeToTry.toString(), + DescriptorPool.SearchFilter.AGGREGATES_ONLY); + + if (result != null) { + if (firstPartLength != -1) { + // We only found the first part of the symbol. Now look for + // the whole thing. If this fails, we *don't* want to keep + // searching parent scopes. + scopeToTry.setLength(dotpos + 1); + scopeToTry.append(name); + result = findSymbol(scopeToTry.toString(), filter); + } + fullname = scopeToTry.toString(); + break; + } + + // Not found. Remove the name so we can try again. + scopeToTry.setLength(dotpos); + } + } + } + + if (result == null) { + if (allowUnknownDependencies && filter == SearchFilter.TYPES_ONLY) { + logger.warning("The descriptor for message type \"" + name + + "\" can not be found and a placeholder is created for it"); + // We create a dummy message descriptor here regardless of the + // expected type. If the type should be message, this dummy + // descriptor will work well and if the type should be enum, a + // DescriptorValidationException will be thrown latter. In either + // case, the code works as expected: we allow unknown message types + // but not unknwon enum types. + result = new Descriptor(fullname); + // Add the placeholder file as a dependency so we can find the + // placeholder symbol when resolving other references. + this.dependencies.add(result.getFile()); + return result; + } else { + throw new DescriptorValidationException(relativeTo, + '\"' + name + "\" is not defined."); + } + } else { + return result; + } + } + + /** + * Adds a symbol to the symbol table. If a symbol with the same name + * already exists, throws an error. + */ + void addSymbol(final GenericDescriptor descriptor) + throws DescriptorValidationException { + validateSymbolName(descriptor); + + final String fullName = descriptor.getFullName(); + final int dotpos = fullName.lastIndexOf('.'); + + final GenericDescriptor old = descriptorsByName.put(fullName, descriptor); + if (old != null) { + descriptorsByName.put(fullName, old); + + if (descriptor.getFile() == old.getFile()) { + if (dotpos == -1) { + throw new DescriptorValidationException(descriptor, + '\"' + fullName + "\" is already defined."); + } else { + throw new DescriptorValidationException(descriptor, + '\"' + fullName.substring(dotpos + 1) + + "\" is already defined in \"" + + fullName.substring(0, dotpos) + "\"."); + } + } else { + throw new DescriptorValidationException(descriptor, + '\"' + fullName + "\" is already defined in file \"" + + old.getFile().getName() + "\"."); + } + } + } + + /** + * Represents a package in the symbol table. We use PackageDescriptors + * just as placeholders so that someone cannot define, say, a message type + * that has the same name as an existing package. + */ + private static final class PackageDescriptor extends GenericDescriptor { + public Message toProto() { return file.toProto(); } + public String getName() { return name; } + public String getFullName() { return fullName; } + public FileDescriptor getFile() { return file; } + + PackageDescriptor(final String name, final String fullName, + final FileDescriptor file) { + this.file = file; + this.fullName = fullName; + this.name = name; + } + + private final String name; + private final String fullName; + private final FileDescriptor file; + } + + /** + * Adds a package to the symbol tables. If a package by the same name + * already exists, that is fine, but if some other kind of symbol exists + * under the same name, an exception is thrown. If the package has + * multiple components, this also adds the parent package(s). + */ + void addPackage(final String fullName, final FileDescriptor file) + throws DescriptorValidationException { + final int dotpos = fullName.lastIndexOf('.'); + final String name; + if (dotpos == -1) { + name = fullName; + } else { + addPackage(fullName.substring(0, dotpos), file); + name = fullName.substring(dotpos + 1); + } + + final GenericDescriptor old = + descriptorsByName.put(fullName, + new PackageDescriptor(name, fullName, file)); + if (old != null) { + descriptorsByName.put(fullName, old); + if (!(old instanceof PackageDescriptor)) { + throw new DescriptorValidationException(file, + '\"' + name + "\" is already defined (as something other than a " + + "package) in file \"" + old.getFile().getName() + "\"."); + } + } + } + + /** A (GenericDescriptor, int) pair, used as a map key. */ + private static final class DescriptorIntPair { + private final GenericDescriptor descriptor; + private final int number; + + DescriptorIntPair(final GenericDescriptor descriptor, final int number) { + this.descriptor = descriptor; + this.number = number; + } + + @Override + public int hashCode() { + return descriptor.hashCode() * ((1 << 16) - 1) + number; + } + @Override + public boolean equals(final Object obj) { + if (!(obj instanceof DescriptorIntPair)) { + return false; + } + final DescriptorIntPair other = (DescriptorIntPair)obj; + return descriptor == other.descriptor && number == other.number; + } + } + + /** + * Adds a field to the fieldsByNumber table. Throws an exception if a + * field with the same containing type and number already exists. + */ + void addFieldByNumber(final FieldDescriptor field) + throws DescriptorValidationException { + final DescriptorIntPair key = + new DescriptorIntPair(field.getContainingType(), field.getNumber()); + final FieldDescriptor old = fieldsByNumber.put(key, field); + if (old != null) { + fieldsByNumber.put(key, old); + throw new DescriptorValidationException(field, + "Field number " + field.getNumber() + + " has already been used in \"" + + field.getContainingType().getFullName() + + "\" by field \"" + old.getName() + "\"."); + } + } + + /** + * Adds an enum value to the enumValuesByNumber table. If an enum value + * with the same type and number already exists, does nothing. (This is + * allowed; the first value define with the number takes precedence.) + */ + void addEnumValueByNumber(final EnumValueDescriptor value) { + final DescriptorIntPair key = + new DescriptorIntPair(value.getType(), value.getNumber()); + final EnumValueDescriptor old = enumValuesByNumber.put(key, value); + if (old != null) { + enumValuesByNumber.put(key, old); + // Not an error: Multiple enum values may have the same number, but + // we only want the first one in the map. + } + } + + /** + * Verifies that the descriptor's name is valid (i.e. it contains only + * letters, digits, and underscores, and does not start with a digit). + */ + static void validateSymbolName(final GenericDescriptor descriptor) + throws DescriptorValidationException { + final String name = descriptor.getName(); + if (name.length() == 0) { + throw new DescriptorValidationException(descriptor, "Missing name."); + } else { + boolean valid = true; + for (int i = 0; i < name.length(); i++) { + final char c = name.charAt(i); + // Non-ASCII characters are not valid in protobuf identifiers, even + // if they are letters or digits. + if (c >= 128) { + valid = false; + } + // First character must be letter or _. Subsequent characters may + // be letters, numbers, or digits. + if (Character.isLetter(c) || c == '_' || + (Character.isDigit(c) && i > 0)) { + // Valid + } else { + valid = false; + } + } + if (!valid) { + throw new DescriptorValidationException(descriptor, + '\"' + name + "\" is not a valid identifier."); + } + } + } + } + + /** Describes an oneof of a message type. */ + public static final class OneofDescriptor { + /** Get the index of this descriptor within its parent. */ + public int getIndex() { return index; } + + public String getName() { return proto.getName(); } + + public FileDescriptor getFile() { return file; } + + public String getFullName() { return fullName; } + + public Descriptor getContainingType() { return containingType; } + + public int getFieldCount() { return fieldCount; } + + /** Get a list of this message type's fields. */ + public List<FieldDescriptor> getFields() { + return Collections.unmodifiableList(Arrays.asList(fields)); + } + + public FieldDescriptor getField(int index) { + return fields[index]; + } + + private OneofDescriptor(final OneofDescriptorProto proto, + final FileDescriptor file, + final Descriptor parent, + final int index) + throws DescriptorValidationException { + this.proto = proto; + fullName = computeFullName(file, parent, proto.getName()); + this.file = file; + this.index = index; + + containingType = parent; + fieldCount = 0; + } + + private final int index; + private OneofDescriptorProto proto; + private final String fullName; + private final FileDescriptor file; + + private Descriptor containingType; + private int fieldCount; + private FieldDescriptor[] fields; + } +} diff --git a/java/core/src/main/java/com/google/protobuf/DoubleArrayList.java b/java/core/src/main/java/com/google/protobuf/DoubleArrayList.java new file mode 100644 index 00000000..bcc9d6ee --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/DoubleArrayList.java @@ -0,0 +1,250 @@ +// 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.Internal.DoubleList; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.RandomAccess; + +/** + * An implementation of {@link DoubleList} on top of a primitive array. + * + * @author dweis@google.com (Daniel Weis) + */ +final class DoubleArrayList + extends AbstractProtobufList<Double> implements DoubleList, RandomAccess { + + private static final int DEFAULT_CAPACITY = 10; + + private static final DoubleArrayList EMPTY_LIST = new DoubleArrayList(); + static { + EMPTY_LIST.makeImmutable(); + } + + public static DoubleArrayList emptyList() { + return EMPTY_LIST; + } + + /** + * The backing store for the list. + */ + private double[] array; + + /** + * The size of the list distinct from the length of the array. That is, it is the number of + * elements set in the list. + */ + private int size; + + /** + * Constructs a new mutable {@code DoubleArrayList} with default capacity. + */ + DoubleArrayList() { + this(DEFAULT_CAPACITY); + } + + /** + * Constructs a new mutable {@code DoubleArrayList} with the provided capacity. + */ + DoubleArrayList(int capacity) { + array = new double[capacity]; + size = 0; + } + + /** + * Constructs a new mutable {@code DoubleArrayList} containing the same elements as {@code other}. + */ + DoubleArrayList(List<Double> other) { + if (other instanceof DoubleArrayList) { + DoubleArrayList list = (DoubleArrayList) other; + array = list.array.clone(); + size = list.size; + } else { + size = other.size(); + array = new double[size]; + for (int i = 0; i < size; i++) { + array[i] = other.get(i); + } + } + } + + @Override + public Double get(int index) { + return getDouble(index); + } + + @Override + public double getDouble(int index) { + ensureIndexInRange(index); + return array[index]; + } + + @Override + public int size() { + return size; + } + + @Override + public Double set(int index, Double element) { + return setDouble(index, element); + } + + @Override + public double setDouble(int index, double element) { + ensureIsMutable(); + ensureIndexInRange(index); + double previousValue = array[index]; + array[index] = element; + return previousValue; + } + + @Override + public void add(int index, Double element) { + addDouble(index, element); + } + + /** + * Like {@link #add(Double)} but more efficient in that it doesn't box the element. + */ + @Override + public void addDouble(double element) { + addDouble(size, element); + } + + /** + * Like {@link #add(int, Double)} but more efficient in that it doesn't box the element. + */ + private void addDouble(int index, double element) { + ensureIsMutable(); + if (index < 0 || index > size) { + throw new IndexOutOfBoundsException(makeOutOfBoundsExceptionMessage(index)); + } + + if (size < array.length) { + // Shift everything over to make room + System.arraycopy(array, index, array, index + 1, size - index); + } else { + // Resize to 1.5x the size + int length = ((size * 3) / 2) + 1; + double[] newArray = new double[length]; + + // Copy the first part directly + System.arraycopy(array, 0, newArray, 0, index); + + // Copy the rest shifted over by one to make room + System.arraycopy(array, index, newArray, index + 1, size - index); + array = newArray; + } + + array[index] = element; + size++; + modCount++; + } + + @Override + public boolean addAll(Collection<? extends Double> collection) { + ensureIsMutable(); + + if (collection == null) { + throw new NullPointerException(); + } + + // We specialize when adding another DoubleArrayList to avoid boxing elements. + if (!(collection instanceof DoubleArrayList)) { + return super.addAll(collection); + } + + DoubleArrayList list = (DoubleArrayList) collection; + if (list.size == 0) { + return false; + } + + int overflow = Integer.MAX_VALUE - size; + if (overflow < list.size) { + // We can't actually represent a list this large. + throw new OutOfMemoryError(); + } + + int newSize = size + list.size; + if (newSize > array.length) { + array = Arrays.copyOf(array, newSize); + } + + System.arraycopy(list.array, 0, array, size, list.size); + size = newSize; + modCount++; + return true; + } + + @Override + public boolean remove(Object o) { + ensureIsMutable(); + for (int i = 0; i < size; i++) { + if (o.equals(array[i])) { + System.arraycopy(array, i + 1, array, i, size - i); + size--; + modCount++; + return true; + } + } + return false; + } + + @Override + public Double remove(int index) { + ensureIsMutable(); + ensureIndexInRange(index); + double value = array[index]; + System.arraycopy(array, index + 1, array, index, size - index); + size--; + modCount++; + return value; + } + + /** + * Ensures that the provided {@code index} is within the range of {@code [0, size]}. Throws an + * {@link IndexOutOfBoundsException} if it is not. + * + * @param index the index to verify is in range + */ + private void ensureIndexInRange(int index) { + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException(makeOutOfBoundsExceptionMessage(index)); + } + } + + private String makeOutOfBoundsExceptionMessage(int index) { + return "Index:" + index + ", Size:" + size; + } +} diff --git a/java/core/src/main/java/com/google/protobuf/DynamicMessage.java b/java/core/src/main/java/com/google/protobuf/DynamicMessage.java new file mode 100644 index 00000000..3ea1b688 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/DynamicMessage.java @@ -0,0 +1,644 @@ +// 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 com.google.protobuf.Descriptors.OneofDescriptor; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * An implementation of {@link Message} that can represent arbitrary types, + * given a {@link Descriptors.Descriptor}. + * + * @author kenton@google.com Kenton Varda + */ +public final class DynamicMessage extends AbstractMessage { + private final Descriptor type; + private final FieldSet<FieldDescriptor> fields; + private final FieldDescriptor[] oneofCases; + private final UnknownFieldSet unknownFields; + private int memoizedSize = -1; + + /** + * Construct a {@code DynamicMessage} using the given {@code FieldSet}. + * oneofCases stores the FieldDescriptor for each oneof to indicate + * which field is set. Caller should make sure the array is immutable. + * + * This constructor is package private and will be used in + * {@code DynamicMutableMessage} to convert a mutable message to an immutable + * message. + */ + DynamicMessage(Descriptor type, FieldSet<FieldDescriptor> fields, + FieldDescriptor[] oneofCases, + UnknownFieldSet unknownFields) { + this.type = type; + this.fields = fields; + this.oneofCases = oneofCases; + this.unknownFields = unknownFields; + } + + /** + * Get a {@code DynamicMessage} representing the default instance of the + * given type. + */ + public static DynamicMessage getDefaultInstance(Descriptor type) { + int oneofDeclCount = type.toProto().getOneofDeclCount(); + FieldDescriptor[] oneofCases = new FieldDescriptor[oneofDeclCount]; + return new DynamicMessage(type, FieldSet.<FieldDescriptor>emptySet(), + oneofCases, + UnknownFieldSet.getDefaultInstance()); + } + + + /** Parse a message of the given type from the given input stream. */ + public static DynamicMessage parseFrom(Descriptor type, + CodedInputStream input) + throws IOException { + return newBuilder(type).mergeFrom(input).buildParsed(); + } + + /** Parse a message of the given type from the given input stream. */ + public static DynamicMessage parseFrom( + Descriptor type, + CodedInputStream input, + ExtensionRegistry extensionRegistry) + throws IOException { + return newBuilder(type).mergeFrom(input, extensionRegistry).buildParsed(); + } + + /** Parse {@code data} as a message of the given type and return it. */ + public static DynamicMessage parseFrom(Descriptor type, ByteString data) + throws InvalidProtocolBufferException { + return newBuilder(type).mergeFrom(data).buildParsed(); + } + + /** Parse {@code data} as a message of the given type and return it. */ + public static DynamicMessage parseFrom(Descriptor type, ByteString data, + ExtensionRegistry extensionRegistry) + throws InvalidProtocolBufferException { + return newBuilder(type).mergeFrom(data, extensionRegistry).buildParsed(); + } + + /** Parse {@code data} as a message of the given type and return it. */ + public static DynamicMessage parseFrom(Descriptor type, byte[] data) + throws InvalidProtocolBufferException { + return newBuilder(type).mergeFrom(data).buildParsed(); + } + + /** Parse {@code data} as a message of the given type and return it. */ + public static DynamicMessage parseFrom(Descriptor type, byte[] data, + ExtensionRegistry extensionRegistry) + throws InvalidProtocolBufferException { + return newBuilder(type).mergeFrom(data, extensionRegistry).buildParsed(); + } + + /** Parse a message of the given type from {@code input} and return it. */ + public static DynamicMessage parseFrom(Descriptor type, InputStream input) + throws IOException { + return newBuilder(type).mergeFrom(input).buildParsed(); + } + + /** Parse a message of the given type from {@code input} and return it. */ + public static DynamicMessage parseFrom(Descriptor type, InputStream input, + ExtensionRegistry extensionRegistry) + throws IOException { + return newBuilder(type).mergeFrom(input, extensionRegistry).buildParsed(); + } + + /** Construct a {@link Message.Builder} for the given type. */ + public static Builder newBuilder(Descriptor type) { + return new Builder(type); + } + + /** + * Construct a {@link Message.Builder} for a message of the same type as + * {@code prototype}, and initialize it with {@code prototype}'s contents. + */ + public static Builder newBuilder(Message prototype) { + return new Builder(prototype.getDescriptorForType()).mergeFrom(prototype); + } + + // ----------------------------------------------------------------- + // Implementation of Message interface. + + public Descriptor getDescriptorForType() { + return type; + } + + public DynamicMessage getDefaultInstanceForType() { + return getDefaultInstance(type); + } + + public Map<FieldDescriptor, Object> getAllFields() { + return fields.getAllFields(); + } + + public boolean hasOneof(OneofDescriptor oneof) { + verifyOneofContainingType(oneof); + FieldDescriptor field = oneofCases[oneof.getIndex()]; + if (field == null) { + return false; + } + return true; + } + + public FieldDescriptor getOneofFieldDescriptor(OneofDescriptor oneof) { + verifyOneofContainingType(oneof); + return oneofCases[oneof.getIndex()]; + } + + public boolean hasField(FieldDescriptor field) { + verifyContainingType(field); + return fields.hasField(field); + } + + public Object getField(FieldDescriptor field) { + verifyContainingType(field); + Object result = fields.getField(field); + if (result == null) { + if (field.isRepeated()) { + result = Collections.emptyList(); + } else if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + result = getDefaultInstance(field.getMessageType()); + } else { + result = field.getDefaultValue(); + } + } + return result; + } + + public int getRepeatedFieldCount(FieldDescriptor field) { + verifyContainingType(field); + return fields.getRepeatedFieldCount(field); + } + + public Object getRepeatedField(FieldDescriptor field, int index) { + verifyContainingType(field); + return fields.getRepeatedField(field, index); + } + + public UnknownFieldSet getUnknownFields() { + return unknownFields; + } + + static boolean isInitialized(Descriptor type, + FieldSet<FieldDescriptor> fields) { + // Check that all required fields are present. + for (final FieldDescriptor field : type.getFields()) { + if (field.isRequired()) { + if (!fields.hasField(field)) { + return false; + } + } + } + + // Check that embedded messages are initialized. + return fields.isInitialized(); + } + + @Override + public boolean isInitialized() { + return isInitialized(type, fields); + } + + @Override + public void writeTo(CodedOutputStream output) throws IOException { + if (type.getOptions().getMessageSetWireFormat()) { + fields.writeMessageSetTo(output); + unknownFields.writeAsMessageSetTo(output); + } else { + fields.writeTo(output); + unknownFields.writeTo(output); + } + } + + @Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + if (type.getOptions().getMessageSetWireFormat()) { + size = fields.getMessageSetSerializedSize(); + size += unknownFields.getSerializedSizeAsMessageSet(); + } else { + size = fields.getSerializedSize(); + size += unknownFields.getSerializedSize(); + } + + memoizedSize = size; + return size; + } + + public Builder newBuilderForType() { + return new Builder(type); + } + + public Builder toBuilder() { + return newBuilderForType().mergeFrom(this); + } + + public Parser<DynamicMessage> getParserForType() { + return new AbstractParser<DynamicMessage>() { + public DynamicMessage parsePartialFrom( + CodedInputStream input, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + Builder builder = newBuilder(type); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (IOException e) { + throw new InvalidProtocolBufferException(e.getMessage()) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + } + + /** Verifies that the field is a field of this message. */ + private void verifyContainingType(FieldDescriptor field) { + if (field.getContainingType() != type) { + throw new IllegalArgumentException( + "FieldDescriptor does not match message type."); + } + } + + /** Verifies that the oneof is an oneof of this message. */ + private void verifyOneofContainingType(OneofDescriptor oneof) { + if (oneof.getContainingType() != type) { + throw new IllegalArgumentException( + "OneofDescriptor does not match message type."); + } + } + + // ================================================================= + + /** + * Builder for {@link DynamicMessage}s. + */ + public static final class Builder extends AbstractMessage.Builder<Builder> { + private final Descriptor type; + private FieldSet<FieldDescriptor> fields; + private final FieldDescriptor[] oneofCases; + private UnknownFieldSet unknownFields; + + /** Construct a {@code Builder} for the given type. */ + private Builder(Descriptor type) { + this.type = type; + this.fields = FieldSet.newFieldSet(); + this.unknownFields = UnknownFieldSet.getDefaultInstance(); + this.oneofCases = new FieldDescriptor[type.toProto().getOneofDeclCount()]; + } + + // --------------------------------------------------------------- + // Implementation of Message.Builder interface. + + @Override + public Builder clear() { + if (fields.isImmutable()) { + fields = FieldSet.newFieldSet(); + } else { + fields.clear(); + } + unknownFields = UnknownFieldSet.getDefaultInstance(); + return this; + } + + @Override + public Builder mergeFrom(Message other) { + if (other instanceof DynamicMessage) { + // This should be somewhat faster than calling super.mergeFrom(). + DynamicMessage otherDynamicMessage = (DynamicMessage) other; + if (otherDynamicMessage.type != type) { + throw new IllegalArgumentException( + "mergeFrom(Message) can only merge messages of the same type."); + } + ensureIsMutable(); + fields.mergeFrom(otherDynamicMessage.fields); + mergeUnknownFields(otherDynamicMessage.unknownFields); + for (int i = 0; i < oneofCases.length; i++) { + if (oneofCases[i] == null) { + oneofCases[i] = otherDynamicMessage.oneofCases[i]; + } else { + if ((otherDynamicMessage.oneofCases[i] != null) + && (oneofCases[i] != otherDynamicMessage.oneofCases[i])) { + fields.clearField(oneofCases[i]); + oneofCases[i] = otherDynamicMessage.oneofCases[i]; + } + } + } + return this; + } else { + return super.mergeFrom(other); + } + } + + public DynamicMessage build() { + if (!isInitialized()) { + throw newUninitializedMessageException( + new DynamicMessage(type, fields, + java.util.Arrays.copyOf(oneofCases, oneofCases.length), unknownFields)); + } + return buildPartial(); + } + + /** + * Helper for DynamicMessage.parseFrom() methods to call. Throws + * {@link InvalidProtocolBufferException} instead of + * {@link UninitializedMessageException}. + */ + private DynamicMessage buildParsed() throws InvalidProtocolBufferException { + if (!isInitialized()) { + throw newUninitializedMessageException( + new DynamicMessage(type, fields, + java.util.Arrays.copyOf(oneofCases, oneofCases.length), unknownFields)) + .asInvalidProtocolBufferException(); + } + return buildPartial(); + } + + public DynamicMessage buildPartial() { + fields.makeImmutable(); + DynamicMessage result = + new DynamicMessage(type, fields, + java.util.Arrays.copyOf(oneofCases, oneofCases.length), unknownFields); + return result; + } + + @Override + public Builder clone() { + Builder result = new Builder(type); + result.fields.mergeFrom(fields); + result.mergeUnknownFields(unknownFields); + System.arraycopy(oneofCases, 0, result.oneofCases, 0 , oneofCases.length); + return result; + } + + public boolean isInitialized() { + return DynamicMessage.isInitialized(type, fields); + } + + public Descriptor getDescriptorForType() { + return type; + } + + public DynamicMessage getDefaultInstanceForType() { + return getDefaultInstance(type); + } + + public Map<FieldDescriptor, Object> getAllFields() { + return fields.getAllFields(); + } + + public Builder newBuilderForField(FieldDescriptor field) { + verifyContainingType(field); + + if (field.getJavaType() != FieldDescriptor.JavaType.MESSAGE) { + throw new IllegalArgumentException( + "newBuilderForField is only valid for fields with message type."); + } + + return new Builder(field.getMessageType()); + } + + public boolean hasOneof(OneofDescriptor oneof) { + verifyOneofContainingType(oneof); + FieldDescriptor field = oneofCases[oneof.getIndex()]; + if (field == null) { + return false; + } + return true; + } + + public FieldDescriptor getOneofFieldDescriptor(OneofDescriptor oneof) { + verifyOneofContainingType(oneof); + return oneofCases[oneof.getIndex()]; + } + + public Builder clearOneof(OneofDescriptor oneof) { + verifyOneofContainingType(oneof); + FieldDescriptor field = oneofCases[oneof.getIndex()]; + if (field != null) { + clearField(field); + } + return this; + } + + public boolean hasField(FieldDescriptor field) { + verifyContainingType(field); + return fields.hasField(field); + } + + public Object getField(FieldDescriptor field) { + verifyContainingType(field); + Object result = fields.getField(field); + if (result == null) { + if (field.isRepeated()) { + result = Collections.emptyList(); + } else if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + result = getDefaultInstance(field.getMessageType()); + } else { + result = field.getDefaultValue(); + } + } + return result; + } + + public Builder setField(FieldDescriptor field, Object value) { + verifyContainingType(field); + ensureIsMutable(); + // TODO(xiaofeng): This check should really be put in FieldSet.setField() + // where all other such checks are done. However, currently + // FieldSet.setField() permits Integer value for enum fields probably + // because of some internal features we support. Should figure it out + // and move this check to a more appropriate place. + if (field.getType() == FieldDescriptor.Type.ENUM) { + ensureEnumValueDescriptor(field, value); + } + OneofDescriptor oneofDescriptor = field.getContainingOneof(); + if (oneofDescriptor != null) { + int index = oneofDescriptor.getIndex(); + FieldDescriptor oldField = oneofCases[index]; + if ((oldField != null) && (oldField != field)) { + fields.clearField(oldField); + } + oneofCases[index] = field; + } + fields.setField(field, value); + return this; + } + + public Builder clearField(FieldDescriptor field) { + verifyContainingType(field); + ensureIsMutable(); + OneofDescriptor oneofDescriptor = field.getContainingOneof(); + if (oneofDescriptor != null) { + int index = oneofDescriptor.getIndex(); + if (oneofCases[index] == field) { + oneofCases[index] = null; + } + } + fields.clearField(field); + return this; + } + + public int getRepeatedFieldCount(FieldDescriptor field) { + verifyContainingType(field); + return fields.getRepeatedFieldCount(field); + } + + public Object getRepeatedField(FieldDescriptor field, int index) { + verifyContainingType(field); + return fields.getRepeatedField(field, index); + } + + public Builder setRepeatedField(FieldDescriptor field, + int index, Object value) { + verifyContainingType(field); + ensureIsMutable(); + fields.setRepeatedField(field, index, value); + return this; + } + + public Builder addRepeatedField(FieldDescriptor field, Object value) { + verifyContainingType(field); + ensureIsMutable(); + fields.addRepeatedField(field, value); + return this; + } + + public UnknownFieldSet getUnknownFields() { + return unknownFields; + } + + 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) + .build(); + return this; + } + + /** Verifies that the field is a field of this message. */ + private void verifyContainingType(FieldDescriptor field) { + if (field.getContainingType() != type) { + throw new IllegalArgumentException( + "FieldDescriptor does not match message type."); + } + } + + /** Verifies that the oneof is an oneof of this message. */ + private void verifyOneofContainingType(OneofDescriptor oneof) { + if (oneof.getContainingType() != type) { + throw new IllegalArgumentException( + "OneofDescriptor does not match message type."); + } + } + + /** Verifies that the value is EnumValueDescriptor and matches Enum Type. */ + private void ensureSingularEnumValueDescriptor( + FieldDescriptor field, Object value) { + if (value == null) { + throw new NullPointerException(); + } + if (!(value instanceof EnumValueDescriptor)) { + throw new IllegalArgumentException( + "DynamicMessage should use EnumValueDescriptor to set Enum Value."); + } + // TODO(xiaofeng): Re-enable this check after Orgstore is fixed to not + // set incorrect EnumValueDescriptors. + // EnumDescriptor fieldType = field.getEnumType(); + // EnumDescriptor fieldValueType = ((EnumValueDescriptor) value).getType(); + // if (fieldType != fieldValueType) { + // throw new IllegalArgumentException(String.format( + // "EnumDescriptor %s of field doesn't match EnumDescriptor %s of field value", + // fieldType.getFullName(), fieldValueType.getFullName())); + // } + } + + /** Verifies the value for an enum field. */ + private void ensureEnumValueDescriptor( + FieldDescriptor field, Object value) { + if (field.isRepeated()) { + for (Object item : (List) value) { + ensureSingularEnumValueDescriptor(field, item); + } + } else { + ensureSingularEnumValueDescriptor(field, value); + } + } + + private void ensureIsMutable() { + if (fields.isImmutable()) { + fields = fields.clone(); + } + } + + @Override + public com.google.protobuf.Message.Builder getFieldBuilder(FieldDescriptor field) { + // TODO(xiangl): need implementation for dynamic message + 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/core/src/main/java/com/google/protobuf/Extension.java b/java/core/src/main/java/com/google/protobuf/Extension.java new file mode 100644 index 00000000..68d29f33 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/Extension.java @@ -0,0 +1,85 @@ +// 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; + +/** + * Interface that generated extensions implement. + * + * @author liujisi@google.com (Jisi Liu) + */ +public abstract class Extension<ContainingType extends MessageLite, Type> + extends ExtensionLite<ContainingType, Type> { + + /** Returns the descriptor of the extension. */ + public abstract Descriptors.FieldDescriptor getDescriptor(); + + /** Returns whether or not this extension is a Lite Extension. */ + final boolean isLite() { + return false; + } + + // All the methods below are extension implementation details. + + /** + * The API type that the extension is used for. + */ + protected enum ExtensionType { + IMMUTABLE, + MUTABLE, + PROTO1, + } + + protected ExtensionType getExtensionType() { + // TODO(liujisi): make this abstract after we fix proto1. + return ExtensionType.IMMUTABLE; + } + + /** + * Type of a message extension. + */ + public enum MessageType { + PROTO1, + PROTO2, + } + + /** + * If the extension is a message extension (i.e., getLiteType() == MESSAGE), + * returns the type of the message, otherwise undefined. + */ + public MessageType getMessageType() { + return MessageType.PROTO2; + } + + protected abstract Object fromReflectionType(Object value); + protected abstract Object singularFromReflectionType(Object value); + protected abstract Object toReflectionType(Object value); + protected abstract Object singularToReflectionType(Object value); +} diff --git a/java/core/src/main/java/com/google/protobuf/ExtensionLite.java b/java/core/src/main/java/com/google/protobuf/ExtensionLite.java new file mode 100644 index 00000000..f8f5bd2c --- /dev/null +++ b/java/core/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. + * <p> + * Methods are for use by generated code only. You can hold a reference to + * extensions using this type name. + */ +public abstract class ExtensionLite<ContainingType extends MessageLite, Type> { + + /** 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/core/src/main/java/com/google/protobuf/ExtensionRegistry.java b/java/core/src/main/java/com/google/protobuf/ExtensionRegistry.java new file mode 100644 index 00000000..0067392f --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/ExtensionRegistry.java @@ -0,0 +1,392 @@ +// 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 java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * A table of known extensions, searchable by name or field number. When + * parsing a protocol message that might have extensions, you must provide + * an {@code ExtensionRegistry} in which you have registered any extensions + * that you want to be able to parse. Otherwise, those extensions will just + * be treated like unknown fields. + * + * <p>For example, if you had the {@code .proto} file: + * + * <pre> + * option java_class = "MyProto"; + * + * message Foo { + * extensions 1000 to max; + * } + * + * extend Foo { + * optional int32 bar; + * } + * </pre> + * + * Then you might write code like: + * + * <pre> + * ExtensionRegistry registry = ExtensionRegistry.newInstance(); + * registry.add(MyProto.bar); + * MyProto.Foo message = MyProto.Foo.parseFrom(input, registry); + * </pre> + * + * <p>Background: + * + * <p>You might wonder why this is necessary. Two alternatives might come to + * mind. First, you might imagine a system where generated extensions are + * automatically registered when their containing classes are loaded. This + * is a popular technique, but is bad design; among other things, it creates a + * situation where behavior can change depending on what classes happen to be + * loaded. It also introduces a security vulnerability, because an + * unprivileged class could cause its code to be called unexpectedly from a + * privileged class by registering itself as an extension of the right type. + * + * <p>Another option you might consider is lazy parsing: do not parse an + * extension until it is first requested, at which point the caller must + * provide a type to use. This introduces a different set of problems. First, + * it would require a mutex lock any time an extension was accessed, which + * would be slow. Second, corrupt data would not be detected until first + * access, at which point it would be much harder to deal with it. Third, it + * could violate the expectation that message objects are immutable, since the + * type provided could be any arbitrary message class. An unprivileged user + * could take advantage of this to inject a mutable object into a message + * belonging to privileged code and create mischief. + * + * @author kenton@google.com Kenton Varda + */ +public class ExtensionRegistry extends ExtensionRegistryLite { + /** Construct a new, empty instance. */ + public static ExtensionRegistry newInstance() { + return new ExtensionRegistry(); + } + + /** Get the unmodifiable singleton empty instance. */ + public static ExtensionRegistry getEmptyRegistry() { + return EMPTY; + } + + + /** Returns an unmodifiable view of the registry. */ + @Override + public ExtensionRegistry getUnmodifiable() { + return new ExtensionRegistry(this); + } + + /** A (Descriptor, Message) pair, returned by lookup methods. */ + public static final class ExtensionInfo { + /** The extension's descriptor. */ + public final FieldDescriptor descriptor; + + /** + * A default instance of the extension's type, if it has a message type. + * Otherwise, {@code null}. + */ + public final Message defaultInstance; + + private ExtensionInfo(final FieldDescriptor descriptor) { + this.descriptor = descriptor; + defaultInstance = null; + } + private ExtensionInfo(final FieldDescriptor descriptor, + final Message defaultInstance) { + this.descriptor = descriptor; + this.defaultInstance = defaultInstance; + } + } + + /** + * Deprecated. Use {@link #findImmutableExtensionByName(String)} instead. + */ + public ExtensionInfo findExtensionByName(final String fullName) { + return findImmutableExtensionByName(fullName); + } + + /** + * Find an extension for immutable APIs by fully-qualified field name, + * in the proto namespace. i.e. {@code result.descriptor.fullName()} will + * match {@code fullName} if a match is found. + * + * @return Information about the extension if found, or {@code null} + * otherwise. + */ + public ExtensionInfo findImmutableExtensionByName(final String fullName) { + return immutableExtensionsByName.get(fullName); + } + + /** + * Find an extension for mutable APIs by fully-qualified field name, + * in the proto namespace. i.e. {@code result.descriptor.fullName()} will + * match {@code fullName} if a match is found. + * + * @return Information about the extension if found, or {@code null} + * otherwise. + */ + public ExtensionInfo findMutableExtensionByName(final String fullName) { + return mutableExtensionsByName.get(fullName); + } + + /** + * Deprecated. Use {@link #findImmutableExtensionByNumber( + * Descriptors.Descriptor, int)} + */ + public ExtensionInfo findExtensionByNumber( + final Descriptor containingType, final int fieldNumber) { + return findImmutableExtensionByNumber(containingType, fieldNumber); + } + + /** + * Find an extension by containing type and field number for immutable APIs. + * + * @return Information about the extension if found, or {@code null} + * otherwise. + */ + public ExtensionInfo findImmutableExtensionByNumber( + final Descriptor containingType, final int fieldNumber) { + return immutableExtensionsByNumber.get( + new DescriptorIntPair(containingType, fieldNumber)); + } + + /** + * Find an extension by containing type and field number for mutable APIs. + * + * @return Information about the extension if found, or {@code null} + * otherwise. + */ + public ExtensionInfo findMutableExtensionByNumber( + final Descriptor containingType, final int fieldNumber) { + return mutableExtensionsByNumber.get( + new DescriptorIntPair(containingType, fieldNumber)); + } + + /** + * Find all extensions for mutable APIs by fully-qualified name of + * extended class. Note that this method is more computationally expensive + * than getting a single extension by name or number. + * + * @return Information about the extensions found, or {@code null} if there + * are none. + */ + public Set<ExtensionInfo> getAllMutableExtensionsByExtendedType(final String fullName) { + HashSet<ExtensionInfo> extensions = new HashSet<ExtensionInfo>(); + for (DescriptorIntPair pair : mutableExtensionsByNumber.keySet()) { + if (pair.descriptor.getFullName().equals(fullName)) { + extensions.add(mutableExtensionsByNumber.get(pair)); + } + } + return extensions; + } + + /** + * Find all extensions for immutable APIs by fully-qualified name of + * extended class. Note that this method is more computationally expensive + * than getting a single extension by name or number. + * + * @return Information about the extensions found, or {@code null} if there + * are none. + */ + public Set<ExtensionInfo> getAllImmutableExtensionsByExtendedType(final String fullName) { + HashSet<ExtensionInfo> extensions = new HashSet<ExtensionInfo>(); + for (DescriptorIntPair pair : immutableExtensionsByNumber.keySet()) { + if (pair.descriptor.getFullName().equals(fullName)) { + extensions.add(immutableExtensionsByNumber.get(pair)); + } + } + return extensions; + } + + /** Add an extension from a generated file to the registry. */ + public void add(final Extension<?, ?> extension) { + if (extension.getExtensionType() != Extension.ExtensionType.IMMUTABLE && + extension.getExtensionType() != Extension.ExtensionType.MUTABLE) { + // do not support other extension types. ignore + return; + } + add(newExtensionInfo(extension), extension.getExtensionType()); + } + + static ExtensionInfo newExtensionInfo(final Extension<?, ?> extension) { + if (extension.getDescriptor().getJavaType() == + FieldDescriptor.JavaType.MESSAGE) { + if (extension.getMessageDefaultInstance() == null) { + throw new IllegalStateException( + "Registered message-type extension had null default instance: " + + extension.getDescriptor().getFullName()); + } + return new ExtensionInfo(extension.getDescriptor(), + (Message) extension.getMessageDefaultInstance()); + } else { + return new ExtensionInfo(extension.getDescriptor(), null); + } + } + + /** Add a non-message-type extension to the registry by descriptor. */ + public void add(final FieldDescriptor type) { + if (type.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + throw new IllegalArgumentException( + "ExtensionRegistry.add() must be provided a default instance when " + + "adding an embedded message extension."); + } + ExtensionInfo info = new ExtensionInfo(type, null); + add(info, Extension.ExtensionType.IMMUTABLE); + add(info, Extension.ExtensionType.MUTABLE); + } + + /** Add a message-type extension to the registry by descriptor. */ + public void add(final FieldDescriptor type, final Message defaultInstance) { + if (type.getJavaType() != FieldDescriptor.JavaType.MESSAGE) { + throw new IllegalArgumentException( + "ExtensionRegistry.add() provided a default instance for a " + + "non-message extension."); + } + add(new ExtensionInfo(type, defaultInstance), + Extension.ExtensionType.IMMUTABLE); + } + + // ================================================================= + // Private stuff. + + private ExtensionRegistry() { + this.immutableExtensionsByName = new HashMap<String, ExtensionInfo>(); + this.mutableExtensionsByName = new HashMap<String, ExtensionInfo>(); + this.immutableExtensionsByNumber = + new HashMap<DescriptorIntPair, ExtensionInfo>(); + this.mutableExtensionsByNumber = + new HashMap<DescriptorIntPair, ExtensionInfo>(); + } + + private ExtensionRegistry(ExtensionRegistry other) { + super(other); + this.immutableExtensionsByName = + Collections.unmodifiableMap(other.immutableExtensionsByName); + this.mutableExtensionsByName = + Collections.unmodifiableMap(other.mutableExtensionsByName); + this.immutableExtensionsByNumber = + Collections.unmodifiableMap(other.immutableExtensionsByNumber); + this.mutableExtensionsByNumber = + Collections.unmodifiableMap(other.mutableExtensionsByNumber); + } + + private final Map<String, ExtensionInfo> immutableExtensionsByName; + private final Map<String, ExtensionInfo> mutableExtensionsByName; + private final Map<DescriptorIntPair, ExtensionInfo> immutableExtensionsByNumber; + private final Map<DescriptorIntPair, ExtensionInfo> mutableExtensionsByNumber; + + ExtensionRegistry(boolean empty) { + super(ExtensionRegistryLite.getEmptyRegistry()); + this.immutableExtensionsByName = + Collections.<String, ExtensionInfo>emptyMap(); + this.mutableExtensionsByName = + Collections.<String, ExtensionInfo>emptyMap(); + this.immutableExtensionsByNumber = + Collections.<DescriptorIntPair, ExtensionInfo>emptyMap(); + this.mutableExtensionsByNumber = + Collections.<DescriptorIntPair, ExtensionInfo>emptyMap(); + } + private static final ExtensionRegistry EMPTY = new ExtensionRegistry(true); + + private void add( + final ExtensionInfo extension, + final Extension.ExtensionType extensionType) { + if (!extension.descriptor.isExtension()) { + throw new IllegalArgumentException( + "ExtensionRegistry.add() was given a FieldDescriptor for a regular " + + "(non-extension) field."); + } + + Map<String, ExtensionInfo> extensionsByName; + Map<DescriptorIntPair, ExtensionInfo> extensionsByNumber; + switch (extensionType) { + case IMMUTABLE: + extensionsByName = immutableExtensionsByName; + extensionsByNumber = immutableExtensionsByNumber; + break; + case MUTABLE: + extensionsByName = mutableExtensionsByName; + extensionsByNumber = mutableExtensionsByNumber; + break; + default: + // Ignore the unknown supported type. + return; + } + + extensionsByName.put(extension.descriptor.getFullName(), extension); + extensionsByNumber.put( + new DescriptorIntPair(extension.descriptor.getContainingType(), + extension.descriptor.getNumber()), + extension); + + final FieldDescriptor field = extension.descriptor; + if (field.getContainingType().getOptions().getMessageSetWireFormat() && + field.getType() == FieldDescriptor.Type.MESSAGE && + field.isOptional() && + field.getExtensionScope() == field.getMessageType()) { + // This is an extension of a MessageSet type defined within the extension + // type's own scope. For backwards-compatibility, allow it to be looked + // up by type name. + extensionsByName.put(field.getMessageType().getFullName(), extension); + } + } + + /** A (GenericDescriptor, int) pair, used as a map key. */ + private static final class DescriptorIntPair { + private final Descriptor descriptor; + private final int number; + + DescriptorIntPair(final Descriptor descriptor, final int number) { + this.descriptor = descriptor; + this.number = number; + } + + @Override + public int hashCode() { + return descriptor.hashCode() * ((1 << 16) - 1) + number; + } + @Override + public boolean equals(final Object obj) { + if (!(obj instanceof DescriptorIntPair)) { + return false; + } + final DescriptorIntPair other = (DescriptorIntPair)obj; + return descriptor == other.descriptor && number == other.number; + } + } +} diff --git a/java/core/src/main/java/com/google/protobuf/ExtensionRegistryLite.java b/java/core/src/main/java/com/google/protobuf/ExtensionRegistryLite.java new file mode 100644 index 00000000..65cf7385 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/ExtensionRegistryLite.java @@ -0,0 +1,185 @@ +// 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.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Equivalent to {@link ExtensionRegistry} but supports only "lite" types. + * <p> + * If all of your types are lite types, then you only need to use + * {@code ExtensionRegistryLite}. Similarly, if all your types are regular + * types, then you only need {@link ExtensionRegistry}. Typically it does not + * make sense to mix the two, since if you have any regular types in your + * program, you then require the full runtime and lose all the benefits of + * the lite runtime, so you might as well make all your types be regular types. + * However, in some cases (e.g. when depending on multiple third-party libraries + * where one uses lite types and one uses regular), you may find yourself + * wanting to mix the two. In this case things get more complicated. + * <p> + * There are three factors to consider: Whether the type being extended is + * lite, whether the embedded type (in the case of a message-typed extension) + * is lite, and whether the extension itself is lite. Since all three are + * declared in different files, they could all be different. Here are all + * the combinations and which type of registry to use: + * <pre> + * Extended type Inner type Extension Use registry + * ======================================================================= + * lite lite lite ExtensionRegistryLite + * lite regular lite ExtensionRegistry + * regular regular regular ExtensionRegistry + * all other combinations not supported + * </pre> + * <p> + * Note that just as regular types are not allowed to contain lite-type fields, + * they are also not allowed to contain lite-type extensions. This is because + * regular types must be fully accessible via reflection, which in turn means + * that all the inner messages must also support reflection. On the other hand, + * since regular types implement the entire lite interface, there is no problem + * with embedding regular types inside lite types. + * + * @author kenton@google.com Kenton Varda + */ +public class ExtensionRegistryLite { + + // Set true to enable lazy parsing feature for MessageSet. + // + // TODO(xiangl): Now we use a global flag to control whether enable lazy + // parsing feature for MessageSet, which may be too crude for some + // applications. Need to support this feature on smaller granularity. + private static volatile boolean eagerlyParseMessageSets = false; + + public static boolean isEagerlyParseMessageSets() { + return eagerlyParseMessageSets; + } + + public static void setEagerlyParseMessageSets(boolean isEagerlyParse) { + eagerlyParseMessageSets = isEagerlyParse; + } + + /** Construct a new, empty instance. */ + public static ExtensionRegistryLite newInstance() { + return new ExtensionRegistryLite(); + } + + /** Get the unmodifiable singleton empty instance. */ + public static ExtensionRegistryLite getEmptyRegistry() { + return EMPTY; + } + + /** Returns an unmodifiable view of the registry. */ + public ExtensionRegistryLite getUnmodifiable() { + return new ExtensionRegistryLite(this); + } + + /** + * Find an extension by containing type and field number. + * + * @return Information about the extension if found, or {@code null} + * otherwise. + */ + @SuppressWarnings("unchecked") + public <ContainingType extends MessageLite> + GeneratedMessageLite.GeneratedExtension<ContainingType, ?> + findLiteExtensionByNumber( + final ContainingType containingTypeDefaultInstance, + final int fieldNumber) { + return (GeneratedMessageLite.GeneratedExtension<ContainingType, ?>) + extensionsByNumber.get( + new ObjectIntPair(containingTypeDefaultInstance, fieldNumber)); + } + + /** Add an extension from a lite generated file to the registry. */ + public final void add( + final GeneratedMessageLite.GeneratedExtension<?, ?> extension) { + extensionsByNumber.put( + new ObjectIntPair(extension.getContainingTypeDefaultInstance(), + extension.getNumber()), + extension); + } + + // ================================================================= + // Private stuff. + + // Constructors are package-private so that ExtensionRegistry can subclass + // this. + + ExtensionRegistryLite() { + this.extensionsByNumber = + new HashMap<ObjectIntPair, + GeneratedMessageLite.GeneratedExtension<?, ?>>(); + } + + ExtensionRegistryLite(ExtensionRegistryLite other) { + if (other == EMPTY) { + this.extensionsByNumber = Collections.emptyMap(); + } else { + this.extensionsByNumber = + Collections.unmodifiableMap(other.extensionsByNumber); + } + } + + private final Map<ObjectIntPair, + GeneratedMessageLite.GeneratedExtension<?, ?>> + extensionsByNumber; + + private ExtensionRegistryLite(boolean empty) { + this.extensionsByNumber = Collections.emptyMap(); + } + private static final ExtensionRegistryLite EMPTY = + new ExtensionRegistryLite(true); + + /** A (Object, int) pair, used as a map key. */ + private static final class ObjectIntPair { + private final Object object; + private final int number; + + ObjectIntPair(final Object object, final int number) { + this.object = object; + this.number = number; + } + + @Override + public int hashCode() { + return System.identityHashCode(object) * ((1 << 16) - 1) + number; + } + @Override + public boolean equals(final Object obj) { + if (!(obj instanceof ObjectIntPair)) { + return false; + } + final ObjectIntPair other = (ObjectIntPair)obj; + return object == other.object && number == other.number; + } + } +} diff --git a/java/core/src/main/java/com/google/protobuf/FieldSet.java b/java/core/src/main/java/com/google/protobuf/FieldSet.java new file mode 100644 index 00000000..47924b65 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/FieldSet.java @@ -0,0 +1,889 @@ +// 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.LazyField.LazyIterator; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * A class which represents an arbitrary set of fields of some message type. + * This is used to implement {@link DynamicMessage}, and also to represent + * extensions in {@link GeneratedMessage}. This class is package-private, + * since outside users should probably be using {@link DynamicMessage}. + * + * @author kenton@google.com Kenton Varda + */ +final class FieldSet<FieldDescriptorType extends + FieldSet.FieldDescriptorLite<FieldDescriptorType>> { + /** + * Interface for a FieldDescriptor or lite extension descriptor. This + * prevents FieldSet from depending on {@link Descriptors.FieldDescriptor}. + */ + public interface FieldDescriptorLite<T extends FieldDescriptorLite<T>> + extends Comparable<T> { + int getNumber(); + WireFormat.FieldType getLiteType(); + WireFormat.JavaType getLiteJavaType(); + boolean isRepeated(); + boolean isPacked(); + Internal.EnumLiteMap<?> getEnumType(); + + // If getLiteJavaType() == MESSAGE, this merges a message object of the + // type into a builder of the type. Returns {@code to}. + MessageLite.Builder internalMergeFrom( + MessageLite.Builder to, MessageLite from); + } + + private final SmallSortedMap<FieldDescriptorType, Object> fields; + private boolean isImmutable; + private boolean hasLazyField = false; + + /** Construct a new FieldSet. */ + private FieldSet() { + this.fields = SmallSortedMap.newFieldMap(16); + } + + /** + * Construct an empty FieldSet. This is only used to initialize + * DEFAULT_INSTANCE. + */ + private FieldSet(final boolean dummy) { + this.fields = SmallSortedMap.newFieldMap(0); + makeImmutable(); + } + + /** Construct a new FieldSet. */ + public static <T extends FieldSet.FieldDescriptorLite<T>> + FieldSet<T> newFieldSet() { + return new FieldSet<T>(); + } + + /** Get an immutable empty FieldSet. */ + @SuppressWarnings("unchecked") + public static <T extends FieldSet.FieldDescriptorLite<T>> + FieldSet<T> emptySet() { + return DEFAULT_INSTANCE; + } + @SuppressWarnings("rawtypes") + private static final FieldSet DEFAULT_INSTANCE = new FieldSet(true); + + /** Make this FieldSet immutable from this point forward. */ + @SuppressWarnings("unchecked") + public void makeImmutable() { + if (isImmutable) { + return; + } + fields.makeImmutable(); + isImmutable = true; + } + + /** + * Returns whether the FieldSet is immutable. This is true if it is the + * {@link #emptySet} or if {@link #makeImmutable} were called. + * + * @return whether the FieldSet is immutable. + */ + public boolean isImmutable() { + return isImmutable; + } + + /** + * Clones the FieldSet. The returned FieldSet will be mutable even if the + * original FieldSet was immutable. + * + * @return the newly cloned FieldSet + */ + @Override + public FieldSet<FieldDescriptorType> clone() { + // We can't just call fields.clone because List objects in the map + // should not be shared. + FieldSet<FieldDescriptorType> clone = FieldSet.newFieldSet(); + for (int i = 0; i < fields.getNumArrayEntries(); i++) { + Map.Entry<FieldDescriptorType, Object> entry = fields.getArrayEntryAt(i); + FieldDescriptorType descriptor = entry.getKey(); + clone.setField(descriptor, entry.getValue()); + } + for (Map.Entry<FieldDescriptorType, Object> entry : + fields.getOverflowEntries()) { + FieldDescriptorType descriptor = entry.getKey(); + clone.setField(descriptor, entry.getValue()); + } + clone.hasLazyField = hasLazyField; + return clone; + } + + + // ================================================================= + + /** See {@link Message.Builder#clear()}. */ + public void clear() { + fields.clear(); + hasLazyField = false; + } + + /** + * Get a simple map containing all the fields. + */ + public Map<FieldDescriptorType, Object> getAllFields() { + if (hasLazyField) { + SmallSortedMap<FieldDescriptorType, Object> result = + SmallSortedMap.newFieldMap(16); + for (int i = 0; i < fields.getNumArrayEntries(); i++) { + cloneFieldEntry(result, fields.getArrayEntryAt(i)); + } + for (Map.Entry<FieldDescriptorType, Object> entry : + fields.getOverflowEntries()) { + cloneFieldEntry(result, entry); + } + if (fields.isImmutable()) { + result.makeImmutable(); + } + return result; + } + return fields.isImmutable() ? fields : Collections.unmodifiableMap(fields); + } + + private void cloneFieldEntry(Map<FieldDescriptorType, Object> map, + Map.Entry<FieldDescriptorType, Object> entry) { + FieldDescriptorType key = entry.getKey(); + Object value = entry.getValue(); + if (value instanceof LazyField) { + map.put(key, ((LazyField) value).getValue()); + } else { + map.put(key, value); + } + } + + /** + * Get an iterator to the field map. This iterator should not be leaked out + * of the protobuf library as it is not protected from mutation when fields + * is not immutable. + */ + public Iterator<Map.Entry<FieldDescriptorType, Object>> iterator() { + if (hasLazyField) { + return new LazyIterator<FieldDescriptorType>( + fields.entrySet().iterator()); + } + return fields.entrySet().iterator(); + } + + /** + * Useful for implementing + * {@link Message#hasField(Descriptors.FieldDescriptor)}. + */ + public boolean hasField(final FieldDescriptorType descriptor) { + if (descriptor.isRepeated()) { + throw new IllegalArgumentException( + "hasField() can only be called on non-repeated fields."); + } + + return fields.get(descriptor) != null; + } + + /** + * Useful for implementing + * {@link Message#getField(Descriptors.FieldDescriptor)}. This method + * returns {@code null} if the field is not set; in this case it is up + * to the caller to fetch the field's default value. + */ + public Object getField(final FieldDescriptorType descriptor) { + Object o = fields.get(descriptor); + if (o instanceof LazyField) { + return ((LazyField) o).getValue(); + } + return o; + } + + /** + * Useful for implementing + * {@link Message.Builder#setField(Descriptors.FieldDescriptor,Object)}. + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public void setField(final FieldDescriptorType descriptor, + Object value) { + if (descriptor.isRepeated()) { + if (!(value instanceof List)) { + throw new IllegalArgumentException( + "Wrong object type used with protocol message reflection."); + } + + // Wrap the contents in a new list so that the caller cannot change + // the list's contents after setting it. + final List newList = new ArrayList(); + newList.addAll((List) value); + for (final Object element : newList) { + verifyType(descriptor.getLiteType(), element); + } + value = newList; + } else { + verifyType(descriptor.getLiteType(), value); + } + + if (value instanceof LazyField) { + hasLazyField = true; + } + fields.put(descriptor, value); + } + + /** + * Useful for implementing + * {@link Message.Builder#clearField(Descriptors.FieldDescriptor)}. + */ + public void clearField(final FieldDescriptorType descriptor) { + fields.remove(descriptor); + if (fields.isEmpty()) { + hasLazyField = false; + } + } + + /** + * Useful for implementing + * {@link Message#getRepeatedFieldCount(Descriptors.FieldDescriptor)}. + */ + public int getRepeatedFieldCount(final FieldDescriptorType descriptor) { + if (!descriptor.isRepeated()) { + throw new IllegalArgumentException( + "getRepeatedField() can only be called on repeated fields."); + } + + final Object value = getField(descriptor); + if (value == null) { + return 0; + } else { + return ((List<?>) value).size(); + } + } + + /** + * Useful for implementing + * {@link Message#getRepeatedField(Descriptors.FieldDescriptor,int)}. + */ + public Object getRepeatedField(final FieldDescriptorType descriptor, + final int index) { + if (!descriptor.isRepeated()) { + throw new IllegalArgumentException( + "getRepeatedField() can only be called on repeated fields."); + } + + final Object value = getField(descriptor); + + if (value == null) { + throw new IndexOutOfBoundsException(); + } else { + return ((List<?>) value).get(index); + } + } + + /** + * Useful for implementing + * {@link Message.Builder#setRepeatedField(Descriptors.FieldDescriptor,int,Object)}. + */ + @SuppressWarnings("unchecked") + public void setRepeatedField(final FieldDescriptorType descriptor, + final int index, + final Object value) { + if (!descriptor.isRepeated()) { + throw new IllegalArgumentException( + "getRepeatedField() can only be called on repeated fields."); + } + + final Object list = getField(descriptor); + if (list == null) { + throw new IndexOutOfBoundsException(); + } + + verifyType(descriptor.getLiteType(), value); + ((List<Object>) list).set(index, value); + } + + /** + * Useful for implementing + * {@link Message.Builder#addRepeatedField(Descriptors.FieldDescriptor,Object)}. + */ + @SuppressWarnings("unchecked") + public void addRepeatedField(final FieldDescriptorType descriptor, + final Object value) { + if (!descriptor.isRepeated()) { + throw new IllegalArgumentException( + "addRepeatedField() can only be called on repeated fields."); + } + + verifyType(descriptor.getLiteType(), value); + + final Object existingValue = getField(descriptor); + List<Object> list; + if (existingValue == null) { + list = new ArrayList<Object>(); + fields.put(descriptor, list); + } else { + list = (List<Object>) existingValue; + } + + list.add(value); + } + + /** + * Verifies that the given object is of the correct type to be a valid + * value for the given field. (For repeated fields, this checks if the + * object is the right type to be one element of the field.) + * + * @throws IllegalArgumentException The value is not of the right type. + */ + private static void verifyType(final WireFormat.FieldType type, + final Object value) { + if (value == null) { + throw new NullPointerException(); + } + + boolean isValid = false; + switch (type.getJavaType()) { + case INT: isValid = value instanceof Integer ; break; + case LONG: isValid = value instanceof Long ; break; + case FLOAT: isValid = value instanceof Float ; break; + case DOUBLE: isValid = value instanceof Double ; break; + case BOOLEAN: isValid = value instanceof Boolean ; break; + case STRING: isValid = value instanceof String ; break; + case BYTE_STRING: + isValid = value instanceof ByteString || value instanceof byte[]; + break; + case ENUM: + // TODO(kenton): Caller must do type checking here, I guess. + isValid = + (value instanceof Integer || value instanceof Internal.EnumLite); + break; + case MESSAGE: + // TODO(kenton): Caller must do type checking here, I guess. + isValid = + (value instanceof MessageLite) || (value instanceof LazyField); + break; + } + + if (!isValid) { + // TODO(kenton): When chaining calls to setField(), it can be hard to + // tell from the stack trace which exact call failed, since the whole + // chain is considered one line of code. It would be nice to print + // more information here, e.g. naming the field. We used to do that. + // But we can't now that FieldSet doesn't use descriptors. Maybe this + // isn't a big deal, though, since it would only really apply when using + // reflection and generally people don't chain reflection setters. + throw new IllegalArgumentException( + "Wrong object type used with protocol message reflection."); + } + } + + // ================================================================= + // Parsing and serialization + + /** + * See {@link Message#isInitialized()}. Note: Since {@code FieldSet} + * itself does not have any way of knowing about required fields that + * aren't actually present in the set, it is up to the caller to check + * that all required fields are present. + */ + public boolean isInitialized() { + for (int i = 0; i < fields.getNumArrayEntries(); i++) { + if (!isInitialized(fields.getArrayEntryAt(i))) { + return false; + } + } + for (final Map.Entry<FieldDescriptorType, Object> entry : + fields.getOverflowEntries()) { + if (!isInitialized(entry)) { + return false; + } + } + return true; + } + + @SuppressWarnings("unchecked") + private boolean isInitialized( + final Map.Entry<FieldDescriptorType, Object> entry) { + final FieldDescriptorType descriptor = entry.getKey(); + if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE) { + if (descriptor.isRepeated()) { + for (final MessageLite element: + (List<MessageLite>) entry.getValue()) { + if (!element.isInitialized()) { + return false; + } + } + } else { + Object value = entry.getValue(); + if (value instanceof MessageLite) { + if (!((MessageLite) value).isInitialized()) { + return false; + } + } else if (value instanceof LazyField) { + return true; + } else { + throw new IllegalArgumentException( + "Wrong object type used with protocol message reflection."); + } + } + } + return true; + } + + /** + * Given a field type, return the wire type. + * + * @returns One of the {@code WIRETYPE_} constants defined in + * {@link WireFormat}. + */ + static int getWireFormatForFieldType(final WireFormat.FieldType type, + boolean isPacked) { + if (isPacked) { + return WireFormat.WIRETYPE_LENGTH_DELIMITED; + } else { + return type.getWireType(); + } + } + + /** + * Like {@link Message.Builder#mergeFrom(Message)}, but merges from another + * {@link FieldSet}. + */ + public void mergeFrom(final FieldSet<FieldDescriptorType> other) { + for (int i = 0; i < other.fields.getNumArrayEntries(); i++) { + mergeFromField(other.fields.getArrayEntryAt(i)); + } + for (final Map.Entry<FieldDescriptorType, Object> entry : + other.fields.getOverflowEntries()) { + mergeFromField(entry); + } + } + + private Object cloneIfMutable(Object value) { + if (value instanceof byte[]) { + byte[] bytes = (byte[]) value; + byte[] copy = new byte[bytes.length]; + System.arraycopy(bytes, 0, copy, 0, bytes.length); + return copy; + } else { + return value; + } + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private void mergeFromField( + final Map.Entry<FieldDescriptorType, Object> entry) { + final FieldDescriptorType descriptor = entry.getKey(); + Object otherValue = entry.getValue(); + if (otherValue instanceof LazyField) { + otherValue = ((LazyField) otherValue).getValue(); + } + + if (descriptor.isRepeated()) { + Object value = getField(descriptor); + if (value == null) { + value = new ArrayList(); + } + for (Object element : (List) otherValue) { + ((List) value).add(cloneIfMutable(element)); + } + fields.put(descriptor, value); + } else if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE) { + Object value = getField(descriptor); + if (value == null) { + fields.put(descriptor, cloneIfMutable(otherValue)); + } else { + // Merge the messages. + value = descriptor.internalMergeFrom( + ((MessageLite) value).toBuilder(), (MessageLite) otherValue) + .build(); + + fields.put(descriptor, value); + } + } else { + fields.put(descriptor, cloneIfMutable(otherValue)); + } + } + + // TODO(kenton): Move static parsing and serialization methods into some + // other class. Probably WireFormat. + + /** + * Read a field of any primitive type for immutable messages from a + * CodedInputStream. Enums, groups, and embedded messages are not handled by + * this method. + * + * @param input The stream from which to read. + * @param type Declared type of the field. + * @param checkUtf8 When true, check that the input is valid utf8. + * @return An object representing the field's value, of the exact + * type which would be returned by + * {@link Message#getField(Descriptors.FieldDescriptor)} for + * this field. + */ + public static Object readPrimitiveField( + CodedInputStream input, + final WireFormat.FieldType type, + boolean checkUtf8) throws IOException { + if (checkUtf8) { + return WireFormat.readPrimitiveField(input, type, + WireFormat.Utf8Validation.STRICT); + } else { + return WireFormat.readPrimitiveField(input, type, + WireFormat.Utf8Validation.LOOSE); + } + } + + + /** See {@link Message#writeTo(CodedOutputStream)}. */ + public void writeTo(final CodedOutputStream output) + throws IOException { + for (int i = 0; i < fields.getNumArrayEntries(); i++) { + final Map.Entry<FieldDescriptorType, Object> entry = + fields.getArrayEntryAt(i); + writeField(entry.getKey(), entry.getValue(), output); + } + for (final Map.Entry<FieldDescriptorType, Object> entry : + fields.getOverflowEntries()) { + writeField(entry.getKey(), entry.getValue(), output); + } + } + + /** + * Like {@link #writeTo} but uses MessageSet wire format. + */ + public void writeMessageSetTo(final CodedOutputStream output) + throws IOException { + for (int i = 0; i < fields.getNumArrayEntries(); i++) { + writeMessageSetTo(fields.getArrayEntryAt(i), output); + } + for (final Map.Entry<FieldDescriptorType, Object> entry : + fields.getOverflowEntries()) { + writeMessageSetTo(entry, output); + } + } + + private void writeMessageSetTo( + final Map.Entry<FieldDescriptorType, Object> entry, + final CodedOutputStream output) throws IOException { + final FieldDescriptorType descriptor = entry.getKey(); + if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE && + !descriptor.isRepeated() && !descriptor.isPacked()) { + Object value = entry.getValue(); + if (value instanceof LazyField) { + value = ((LazyField) value).getValue(); + } + output.writeMessageSetExtension(entry.getKey().getNumber(), + (MessageLite) value); + } else { + writeField(descriptor, entry.getValue(), output); + } + } + + /** + * Write a single tag-value pair to the stream. + * + * @param output The output stream. + * @param type The field's type. + * @param number The field's number. + * @param value Object representing the field's value. Must be of the exact + * type which would be returned by + * {@link Message#getField(Descriptors.FieldDescriptor)} for + * this field. + */ + private static void writeElement(final CodedOutputStream output, + final WireFormat.FieldType type, + final int number, + final Object value) throws IOException { + // Special case for groups, which need a start and end tag; other fields + // can just use writeTag() and writeFieldNoTag(). + if (type == WireFormat.FieldType.GROUP) { + output.writeGroup(number, (MessageLite) value); + } else { + output.writeTag(number, getWireFormatForFieldType(type, false)); + writeElementNoTag(output, type, value); + } + } + + /** + * Write a field of arbitrary type, without its tag, to the stream. + * + * @param output The output stream. + * @param type The field's type. + * @param value Object representing the field's value. Must be of the exact + * type which would be returned by + * {@link Message#getField(Descriptors.FieldDescriptor)} for + * this field. + */ + static void writeElementNoTag( + final CodedOutputStream output, + final WireFormat.FieldType type, + final Object value) throws IOException { + switch (type) { + case DOUBLE : output.writeDoubleNoTag ((Double ) value); break; + case FLOAT : output.writeFloatNoTag ((Float ) value); break; + case INT64 : output.writeInt64NoTag ((Long ) value); break; + case UINT64 : output.writeUInt64NoTag ((Long ) value); break; + case INT32 : output.writeInt32NoTag ((Integer ) value); break; + case FIXED64 : output.writeFixed64NoTag ((Long ) value); break; + case FIXED32 : output.writeFixed32NoTag ((Integer ) value); break; + case BOOL : output.writeBoolNoTag ((Boolean ) value); break; + case GROUP : output.writeGroupNoTag ((MessageLite) value); break; + case MESSAGE : output.writeMessageNoTag ((MessageLite) value); break; + case STRING: + if (value instanceof ByteString) { + output.writeBytesNoTag((ByteString) value); + } else { + output.writeStringNoTag((String) value); + } + break; + case BYTES: + if (value instanceof ByteString) { + output.writeBytesNoTag((ByteString) value); + } else { + output.writeByteArrayNoTag((byte[]) value); + } + break; + case UINT32 : output.writeUInt32NoTag ((Integer ) value); break; + case SFIXED32: output.writeSFixed32NoTag((Integer ) value); break; + case SFIXED64: output.writeSFixed64NoTag((Long ) value); break; + case SINT32 : output.writeSInt32NoTag ((Integer ) value); break; + case SINT64 : output.writeSInt64NoTag ((Long ) value); break; + + case ENUM: + if (value instanceof Internal.EnumLite) { + output.writeEnumNoTag(((Internal.EnumLite) value).getNumber()); + } else { + output.writeEnumNoTag(((Integer) value).intValue()); + } + break; + } + } + + /** Write a single field. */ + public static void writeField(final FieldDescriptorLite<?> descriptor, + final Object value, + final CodedOutputStream output) + throws IOException { + WireFormat.FieldType type = descriptor.getLiteType(); + int number = descriptor.getNumber(); + if (descriptor.isRepeated()) { + final List<?> valueList = (List<?>)value; + if (descriptor.isPacked()) { + output.writeTag(number, WireFormat.WIRETYPE_LENGTH_DELIMITED); + // Compute the total data size so the length can be written. + int dataSize = 0; + for (final Object element : valueList) { + dataSize += computeElementSizeNoTag(type, element); + } + output.writeRawVarint32(dataSize); + // Write the data itself, without any tags. + for (final Object element : valueList) { + writeElementNoTag(output, type, element); + } + } else { + for (final Object element : valueList) { + writeElement(output, type, number, element); + } + } + } else { + if (value instanceof LazyField) { + writeElement(output, type, number, ((LazyField) value).getValue()); + } else { + writeElement(output, type, number, value); + } + } + } + + /** + * See {@link Message#getSerializedSize()}. It's up to the caller to cache + * the resulting size if desired. + */ + public int getSerializedSize() { + int size = 0; + for (int i = 0; i < fields.getNumArrayEntries(); i++) { + final Map.Entry<FieldDescriptorType, Object> entry = + fields.getArrayEntryAt(i); + size += computeFieldSize(entry.getKey(), entry.getValue()); + } + for (final Map.Entry<FieldDescriptorType, Object> entry : + fields.getOverflowEntries()) { + size += computeFieldSize(entry.getKey(), entry.getValue()); + } + return size; + } + + /** + * Like {@link #getSerializedSize} but uses MessageSet wire format. + */ + public int getMessageSetSerializedSize() { + int size = 0; + for (int i = 0; i < fields.getNumArrayEntries(); i++) { + size += getMessageSetSerializedSize(fields.getArrayEntryAt(i)); + } + for (final Map.Entry<FieldDescriptorType, Object> entry : + fields.getOverflowEntries()) { + size += getMessageSetSerializedSize(entry); + } + return size; + } + + private int getMessageSetSerializedSize( + final Map.Entry<FieldDescriptorType, Object> entry) { + final FieldDescriptorType descriptor = entry.getKey(); + Object value = entry.getValue(); + if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE + && !descriptor.isRepeated() && !descriptor.isPacked()) { + if (value instanceof LazyField) { + return CodedOutputStream.computeLazyFieldMessageSetExtensionSize( + entry.getKey().getNumber(), (LazyField) value); + } else { + return CodedOutputStream.computeMessageSetExtensionSize( + entry.getKey().getNumber(), (MessageLite) value); + } + } else { + return computeFieldSize(descriptor, value); + } + } + + /** + * Compute the number of bytes that would be needed to encode a + * single tag/value pair of arbitrary type. + * + * @param type The field's type. + * @param number The field's number. + * @param value Object representing the field's value. Must be of the exact + * type which would be returned by + * {@link Message#getField(Descriptors.FieldDescriptor)} for + * this field. + */ + private static int computeElementSize( + final WireFormat.FieldType type, + final int number, final Object value) { + int tagSize = CodedOutputStream.computeTagSize(number); + if (type == WireFormat.FieldType.GROUP) { + // Only count the end group tag for proto2 messages as for proto1 the end + // group tag will be counted as a part of getSerializedSize(). + tagSize *= 2; + } + return tagSize + computeElementSizeNoTag(type, value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * particular value of arbitrary type, excluding tag. + * + * @param type The field's type. + * @param value Object representing the field's value. Must be of the exact + * type which would be returned by + * {@link Message#getField(Descriptors.FieldDescriptor)} for + * this field. + */ + static int computeElementSizeNoTag( + final WireFormat.FieldType type, final Object value) { + switch (type) { + // Note: Minor violation of 80-char limit rule here because this would + // actually be harder to read if we wrapped the lines. + case DOUBLE : return CodedOutputStream.computeDoubleSizeNoTag ((Double )value); + case FLOAT : return CodedOutputStream.computeFloatSizeNoTag ((Float )value); + case INT64 : return CodedOutputStream.computeInt64SizeNoTag ((Long )value); + case UINT64 : return CodedOutputStream.computeUInt64SizeNoTag ((Long )value); + case INT32 : return CodedOutputStream.computeInt32SizeNoTag ((Integer )value); + case FIXED64 : return CodedOutputStream.computeFixed64SizeNoTag ((Long )value); + case FIXED32 : return CodedOutputStream.computeFixed32SizeNoTag ((Integer )value); + case BOOL : return CodedOutputStream.computeBoolSizeNoTag ((Boolean )value); + case GROUP : return CodedOutputStream.computeGroupSizeNoTag ((MessageLite)value); + case BYTES : + if (value instanceof ByteString) { + return CodedOutputStream.computeBytesSizeNoTag((ByteString) value); + } else { + return CodedOutputStream.computeByteArraySizeNoTag((byte[]) value); + } + case STRING : + if (value instanceof ByteString) { + return CodedOutputStream.computeBytesSizeNoTag((ByteString) value); + } else { + return CodedOutputStream.computeStringSizeNoTag((String) value); + } + case UINT32 : return CodedOutputStream.computeUInt32SizeNoTag ((Integer )value); + case SFIXED32: return CodedOutputStream.computeSFixed32SizeNoTag((Integer )value); + case SFIXED64: return CodedOutputStream.computeSFixed64SizeNoTag((Long )value); + case SINT32 : return CodedOutputStream.computeSInt32SizeNoTag ((Integer )value); + case SINT64 : return CodedOutputStream.computeSInt64SizeNoTag ((Long )value); + + case MESSAGE: + if (value instanceof LazyField) { + return CodedOutputStream.computeLazyFieldSizeNoTag((LazyField) value); + } else { + return CodedOutputStream.computeMessageSizeNoTag((MessageLite) value); + } + + case ENUM: + if (value instanceof Internal.EnumLite) { + return CodedOutputStream.computeEnumSizeNoTag( + ((Internal.EnumLite) value).getNumber()); + } else { + return CodedOutputStream.computeEnumSizeNoTag((Integer) value); + } + } + + throw new RuntimeException( + "There is no way to get here, but the compiler thinks otherwise."); + } + + /** + * Compute the number of bytes needed to encode a particular field. + */ + public static int computeFieldSize(final FieldDescriptorLite<?> descriptor, + final Object value) { + WireFormat.FieldType type = descriptor.getLiteType(); + int number = descriptor.getNumber(); + if (descriptor.isRepeated()) { + if (descriptor.isPacked()) { + int dataSize = 0; + for (final Object element : (List<?>)value) { + dataSize += computeElementSizeNoTag(type, element); + } + return dataSize + + CodedOutputStream.computeTagSize(number) + + CodedOutputStream.computeRawVarint32Size(dataSize); + } else { + int size = 0; + for (final Object element : (List<?>)value) { + size += computeElementSize(type, number, element); + } + return size; + } + } else { + return computeElementSize(type, number, value); + } + } +} diff --git a/java/core/src/main/java/com/google/protobuf/FloatArrayList.java b/java/core/src/main/java/com/google/protobuf/FloatArrayList.java new file mode 100644 index 00000000..033b5eed --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/FloatArrayList.java @@ -0,0 +1,249 @@ +// 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.Internal.FloatList; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.RandomAccess; + +/** + * An implementation of {@link FloatList} on top of a primitive array. + * + * @author dweis@google.com (Daniel Weis) + */ +final class FloatArrayList extends AbstractProtobufList<Float> implements FloatList, RandomAccess { + + private static final int DEFAULT_CAPACITY = 10; + + private static final FloatArrayList EMPTY_LIST = new FloatArrayList(); + static { + EMPTY_LIST.makeImmutable(); + } + + public static FloatArrayList emptyList() { + return EMPTY_LIST; + } + + /** + * The backing store for the list. + */ + private float[] array; + + /** + * The size of the list distinct from the length of the array. That is, it is the number of + * elements set in the list. + */ + private int size; + + /** + * Constructs a new mutable {@code FloatArrayList} with default capacity. + */ + FloatArrayList() { + this(DEFAULT_CAPACITY); + } + + /** + * Constructs a new mutable {@code FloatArrayList} with the provided capacity. + */ + FloatArrayList(int capacity) { + array = new float[capacity]; + size = 0; + } + + /** + * Constructs a new mutable {@code FloatArrayList} containing the same elements as {@code other}. + */ + FloatArrayList(List<Float> other) { + if (other instanceof FloatArrayList) { + FloatArrayList list = (FloatArrayList) other; + array = list.array.clone(); + size = list.size; + } else { + size = other.size(); + array = new float[size]; + for (int i = 0; i < size; i++) { + array[i] = other.get(i); + } + } + } + + @Override + public Float get(int index) { + return getFloat(index); + } + + @Override + public float getFloat(int index) { + ensureIndexInRange(index); + return array[index]; + } + + @Override + public int size() { + return size; + } + + @Override + public Float set(int index, Float element) { + return setFloat(index, element); + } + + @Override + public float setFloat(int index, float element) { + ensureIsMutable(); + ensureIndexInRange(index); + float previousValue = array[index]; + array[index] = element; + return previousValue; + } + + @Override + public void add(int index, Float element) { + addFloat(index, element); + } + + /** + * Like {@link #add(Float)} but more efficient in that it doesn't box the element. + */ + @Override + public void addFloat(float element) { + addFloat(size, element); + } + + /** + * Like {@link #add(int, Float)} but more efficient in that it doesn't box the element. + */ + private void addFloat(int index, float element) { + ensureIsMutable(); + if (index < 0 || index > size) { + throw new IndexOutOfBoundsException(makeOutOfBoundsExceptionMessage(index)); + } + + if (size < array.length) { + // Shift everything over to make room + System.arraycopy(array, index, array, index + 1, size - index); + } else { + // Resize to 1.5x the size + int length = ((size * 3) / 2) + 1; + float[] newArray = new float[length]; + + // Copy the first part directly + System.arraycopy(array, 0, newArray, 0, index); + + // Copy the rest shifted over by one to make room + System.arraycopy(array, index, newArray, index + 1, size - index); + array = newArray; + } + + array[index] = element; + size++; + modCount++; + } + + @Override + public boolean addAll(Collection<? extends Float> collection) { + ensureIsMutable(); + + if (collection == null) { + throw new NullPointerException(); + } + + // We specialize when adding another FloatArrayList to avoid boxing elements. + if (!(collection instanceof FloatArrayList)) { + return super.addAll(collection); + } + + FloatArrayList list = (FloatArrayList) collection; + if (list.size == 0) { + return false; + } + + int overflow = Integer.MAX_VALUE - size; + if (overflow < list.size) { + // We can't actually represent a list this large. + throw new OutOfMemoryError(); + } + + int newSize = size + list.size; + if (newSize > array.length) { + array = Arrays.copyOf(array, newSize); + } + + System.arraycopy(list.array, 0, array, size, list.size); + size = newSize; + modCount++; + return true; + } + + @Override + public boolean remove(Object o) { + ensureIsMutable(); + for (int i = 0; i < size; i++) { + if (o.equals(array[i])) { + System.arraycopy(array, i + 1, array, i, size - i); + size--; + modCount++; + return true; + } + } + return false; + } + + @Override + public Float remove(int index) { + ensureIsMutable(); + ensureIndexInRange(index); + float value = array[index]; + System.arraycopy(array, index + 1, array, index, size - index); + size--; + modCount++; + return value; + } + + /** + * Ensures that the provided {@code index} is within the range of {@code [0, size]}. Throws an + * {@link IndexOutOfBoundsException} if it is not. + * + * @param index the index to verify is in range + */ + private void ensureIndexInRange(int index) { + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException(makeOutOfBoundsExceptionMessage(index)); + } + } + + private String makeOutOfBoundsExceptionMessage(int index) { + return "Index:" + index + ", Size:" + size; + } +} diff --git a/java/core/src/main/java/com/google/protobuf/GeneratedMessage.java b/java/core/src/main/java/com/google/protobuf/GeneratedMessage.java new file mode 100644 index 00000000..d84fa75c --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/GeneratedMessage.java @@ -0,0 +1,2778 @@ +// 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.Descriptors.FileDescriptor; +import com.google.protobuf.Descriptors.OneofDescriptor; +import com.google.protobuf.GeneratedMessageLite.ExtendableMessage; +import com.google.protobuf.GeneratedMessageLite.GeneratedExtension; + +import java.io.IOException; +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +/** + * All generated protocol message classes extend this class. This class + * implements most of the Message and Builder interfaces using Java reflection. + * Users can ignore this class and pretend that generated messages implement + * the Message interface directly. + * + * @author kenton@google.com Kenton Varda + */ +public abstract class GeneratedMessage extends AbstractMessage + implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * For testing. Allows a test to disable the optimization that avoids using + * field builders for nested messages until they are requested. By disabling + * this optimization, existing tests can be reused to test the field builders. + */ + protected static boolean alwaysUseFieldBuilders = false; + + /** For use by generated code only. */ + protected UnknownFieldSet unknownFields; + + protected GeneratedMessage() { + unknownFields = UnknownFieldSet.getDefaultInstance(); + } + + protected GeneratedMessage(Builder<?> builder) { + unknownFields = builder.getUnknownFields(); + } + + public Parser<? extends GeneratedMessage> getParserForType() { + throw new UnsupportedOperationException( + "This is supposed to be overridden by subclasses."); + } + + /** + * For testing. Allows a test to disable the optimization that avoids using + * field builders for nested messages until they are requested. By disabling + * this optimization, existing tests can be reused to test the field builders. + * See {@link RepeatedFieldBuilder} and {@link SingleFieldBuilder}. + */ + static void enableAlwaysUseFieldBuildersForTesting() { + alwaysUseFieldBuilders = true; + } + + /** + * Get the FieldAccessorTable for this type. We can't have the message + * class pass this in to the constructor because of bootstrapping trouble + * with DescriptorProtos. + */ + protected abstract FieldAccessorTable internalGetFieldAccessorTable(); + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public Descriptor getDescriptorForType() { + return internalGetFieldAccessorTable().descriptor; + } + + /** + * Internal helper to return a modifiable map containing all the fields. + * The returned Map is modifialbe so that the caller can add additional + * extension fields to implement {@link #getAllFields()}. + * + * @param getBytesForString whether to generate ByteString for string fields + */ + private Map<FieldDescriptor, Object> getAllFieldsMutable( + boolean getBytesForString) { + final TreeMap<FieldDescriptor, Object> result = + new TreeMap<FieldDescriptor, Object>(); + final Descriptor descriptor = internalGetFieldAccessorTable().descriptor; + final List<FieldDescriptor> fields = descriptor.getFields(); + + for (int i = 0; i < fields.size(); i++) { + FieldDescriptor field = fields.get(i); + final OneofDescriptor oneofDescriptor = field.getContainingOneof(); + + /* + * If the field is part of a Oneof, then at maximum one field in the Oneof is set + * and it is not repeated. There is no need to iterate through the others. + */ + if (oneofDescriptor != null) { + // Skip other fields in the Oneof we know are not set + i += oneofDescriptor.getFieldCount() - 1; + if (!hasOneof(oneofDescriptor)) { + // If no field is set in the Oneof, skip all the fields in the Oneof + continue; + } + // Get the pointer to the only field which is set in the Oneof + field = getOneofFieldDescriptor(oneofDescriptor); + } else { + // If we are not in a Oneof, we need to check if the field is set and if it is repeated + if (field.isRepeated()) { + final List<?> value = (List<?>) getField(field); + if (!value.isEmpty()) { + result.put(field, value); + } + continue; + } + if (!hasField(field)) { + continue; + } + } + // Add the field to the map + if (getBytesForString && field.getJavaType() == FieldDescriptor.JavaType.STRING) { + result.put(field, getFieldRaw(field)); + } else { + result.put(field, getField(field)); + } + } + return result; + } + + @Override + public boolean isInitialized() { + for (final FieldDescriptor field : getDescriptorForType().getFields()) { + // Check that all required fields are present. + if (field.isRequired()) { + if (!hasField(field)) { + return false; + } + } + // Check that embedded messages are initialized. + if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + if (field.isRepeated()) { + @SuppressWarnings("unchecked") final + List<Message> messageList = (List<Message>) getField(field); + for (final Message element : messageList) { + if (!element.isInitialized()) { + return false; + } + } + } else { + if (hasField(field) && !((Message) getField(field)).isInitialized()) { + return false; + } + } + } + } + + return true; + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public Map<FieldDescriptor, Object> getAllFields() { + return Collections.unmodifiableMap( + getAllFieldsMutable(/* getBytesForString = */ false)); + } + + /** + * Returns a collection of all the fields in this message which are set + * and their corresponding values. A singular ("required" or "optional") + * field is set iff hasField() returns true for that field. A "repeated" + * field is set iff getRepeatedFieldCount() is greater than zero. The + * values are exactly what would be returned by calling + * {@link #getFieldRaw(Descriptors.FieldDescriptor)} for each field. The map + * is guaranteed to be a sorted map, so iterating over it will return fields + * in order by field number. + */ + Map<FieldDescriptor, Object> getAllFieldsRaw() { + return Collections.unmodifiableMap( + getAllFieldsMutable(/* getBytesForString = */ true)); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public boolean hasOneof(final OneofDescriptor oneof) { + return internalGetFieldAccessorTable().getOneof(oneof).has(this); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public FieldDescriptor getOneofFieldDescriptor(final OneofDescriptor oneof) { + return internalGetFieldAccessorTable().getOneof(oneof).get(this); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public boolean hasField(final FieldDescriptor field) { + return internalGetFieldAccessorTable().getField(field).has(this); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public Object getField(final FieldDescriptor field) { + return internalGetFieldAccessorTable().getField(field).get(this); + } + + /** + * Obtains the value of the given field, or the default value if it is + * not set. For primitive fields, the boxed primitive value is returned. + * For enum fields, the EnumValueDescriptor for the value is returned. For + * embedded message fields, the sub-message is returned. For repeated + * fields, a java.util.List is returned. For present string fields, a + * ByteString is returned representing the bytes that the field contains. + */ + Object getFieldRaw(final FieldDescriptor field) { + return internalGetFieldAccessorTable().getField(field).getRaw(this); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public int getRepeatedFieldCount(final FieldDescriptor field) { + return internalGetFieldAccessorTable().getField(field) + .getRepeatedCount(this); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public Object getRepeatedField(final FieldDescriptor field, final int index) { + return internalGetFieldAccessorTable().getField(field) + .getRepeated(this, index); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public UnknownFieldSet getUnknownFields() { + throw new UnsupportedOperationException( + "This is supposed to be overridden by subclasses."); + } + + /** + * Called by subclasses to parse an unknown field. + * @return {@code true} unless the tag is an end-group tag. + */ + protected boolean parseUnknownField( + CodedInputStream input, + UnknownFieldSet.Builder unknownFields, + ExtensionRegistryLite extensionRegistry, + int tag) throws IOException { + return unknownFields.mergeFieldFrom(tag, input); + } + + @Override + public void writeTo(final CodedOutputStream output) throws IOException { + MessageReflection.writeMessageTo(this, getAllFieldsRaw(), output, false); + } + + @Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) { + return size; + } + + memoizedSize = MessageReflection.getSerializedSize( + this, getAllFieldsRaw()); + return memoizedSize; + } + + + + /** + * Used by parsing constructors in generated classes. + */ + protected void makeExtensionsImmutable() { + // Noop for messages without extensions. + } + + protected abstract Message.Builder newBuilderForType(BuilderParent parent); + + /** + * Interface for the parent of a Builder that allows the builder to + * communicate invalidations back to the parent for use when using nested + * builders. + */ + protected interface BuilderParent { + + /** + * A builder becomes dirty whenever a field is modified -- including fields + * in nested builders -- and becomes clean when build() is called. Thus, + * when a builder becomes dirty, all its parents become dirty as well, and + * when it becomes clean, all its children become clean. The dirtiness + * state is used to invalidate certain cached values. + * <br> + * To this end, a builder calls markAsDirty() on its parent whenever it + * transitions from clean to dirty. The parent must propagate this call to + * its own parent, unless it was already dirty, in which case the + * grandparent must necessarily already be dirty as well. The parent can + * only transition back to "clean" after calling build() on all children. + */ + void markDirty(); + } + + @SuppressWarnings("unchecked") + public abstract static class Builder <BuilderType extends Builder> + extends AbstractMessage.Builder<BuilderType> { + + private BuilderParent builderParent; + + private BuilderParentImpl meAsParent; + + // Indicates that we've built a message and so we are now obligated + // to dispatch dirty invalidations. See GeneratedMessage.BuilderListener. + private boolean isClean; + + private UnknownFieldSet unknownFields = + UnknownFieldSet.getDefaultInstance(); + + protected Builder() { + this(null); + } + + protected Builder(BuilderParent builderParent) { + this.builderParent = builderParent; + } + + void dispose() { + builderParent = null; + } + + /** + * Called by the subclass when a message is built. + */ + protected void onBuilt() { + if (builderParent != null) { + markClean(); + } + } + + /** + * Called by the subclass or a builder to notify us that a message was + * built and may be cached and therefore invalidations are needed. + */ + protected void markClean() { + this.isClean = true; + } + + /** + * Gets whether invalidations are needed + * + * @return whether invalidations are needed + */ + protected boolean isClean() { + return isClean; + } + + @Override + public BuilderType clone() { + BuilderType builder = + (BuilderType) getDefaultInstanceForType().newBuilderForType(); + builder.mergeFrom(buildPartial()); + return builder; + } + + /** + * Called by the initialization and clear code paths to allow subclasses to + * reset any of their builtin fields back to the initial values. + */ + public BuilderType clear() { + unknownFields = UnknownFieldSet.getDefaultInstance(); + onChanged(); + return (BuilderType) this; + } + + /** + * Get the FieldAccessorTable for this type. We can't have the message + * class pass this in to the constructor because of bootstrapping trouble + * with DescriptorProtos. + */ + protected abstract FieldAccessorTable internalGetFieldAccessorTable(); + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public Descriptor getDescriptorForType() { + return internalGetFieldAccessorTable().descriptor; + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public Map<FieldDescriptor, Object> getAllFields() { + return Collections.unmodifiableMap(getAllFieldsMutable()); + } + + /** Internal helper which returns a mutable map. */ + private Map<FieldDescriptor, Object> getAllFieldsMutable() { + final TreeMap<FieldDescriptor, Object> result = + new TreeMap<FieldDescriptor, Object>(); + final Descriptor descriptor = internalGetFieldAccessorTable().descriptor; + final List<FieldDescriptor> fields = descriptor.getFields(); + + for (int i = 0; i < fields.size(); i++) { + FieldDescriptor field = fields.get(i); + final OneofDescriptor oneofDescriptor = field.getContainingOneof(); + + /* + * If the field is part of a Oneof, then at maximum one field in the Oneof is set + * and it is not repeated. There is no need to iterate through the others. + */ + if (oneofDescriptor != null) { + // Skip other fields in the Oneof we know are not set + i += oneofDescriptor.getFieldCount() - 1; + if (!hasOneof(oneofDescriptor)) { + // If no field is set in the Oneof, skip all the fields in the Oneof + continue; + } + // Get the pointer to the only field which is set in the Oneof + field = getOneofFieldDescriptor(oneofDescriptor); + } else { + // If we are not in a Oneof, we need to check if the field is set and if it is repeated + if (field.isRepeated()) { + final List<?> value = (List<?>) getField(field); + if (!value.isEmpty()) { + result.put(field, value); + } + continue; + } + if (!hasField(field)) { + continue; + } + } + // Add the field to the map + result.put(field, getField(field)); + } + return result; + } + + public Message.Builder newBuilderForField( + final FieldDescriptor field) { + return internalGetFieldAccessorTable().getField(field).newBuilder(); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public Message.Builder getFieldBuilder(final FieldDescriptor field) { + 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); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public FieldDescriptor getOneofFieldDescriptor(final OneofDescriptor oneof) { + return internalGetFieldAccessorTable().getOneof(oneof).get(this); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public boolean hasField(final FieldDescriptor field) { + return internalGetFieldAccessorTable().getField(field).has(this); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public Object getField(final FieldDescriptor field) { + Object object = internalGetFieldAccessorTable().getField(field).get(this); + if (field.isRepeated()) { + // The underlying list object is still modifiable at this point. + // Make sure not to expose the modifiable list to the caller. + return Collections.unmodifiableList((List) object); + } else { + return object; + } + } + + public BuilderType setField(final FieldDescriptor field, + final Object value) { + internalGetFieldAccessorTable().getField(field).set(this, value); + return (BuilderType) this; + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public BuilderType clearField(final FieldDescriptor field) { + internalGetFieldAccessorTable().getField(field).clear(this); + return (BuilderType) this; + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public BuilderType clearOneof(final OneofDescriptor oneof) { + internalGetFieldAccessorTable().getOneof(oneof).clear(this); + return (BuilderType) this; + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public int getRepeatedFieldCount(final FieldDescriptor field) { + return internalGetFieldAccessorTable().getField(field) + .getRepeatedCount(this); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public Object getRepeatedField(final FieldDescriptor field, + final int index) { + return internalGetFieldAccessorTable().getField(field) + .getRepeated(this, index); + } + + public BuilderType setRepeatedField(final FieldDescriptor field, + final int index, final Object value) { + internalGetFieldAccessorTable().getField(field) + .setRepeated(this, index, value); + return (BuilderType) this; + } + + public BuilderType addRepeatedField(final FieldDescriptor field, + final Object value) { + internalGetFieldAccessorTable().getField(field).addRepeated(this, value); + return (BuilderType) this; + } + + public BuilderType setUnknownFields( + final UnknownFieldSet unknownFields) { + this.unknownFields = unknownFields; + onChanged(); + return (BuilderType) this; + } + + @Override + public BuilderType mergeUnknownFields( + final UnknownFieldSet unknownFields) { + this.unknownFields = + UnknownFieldSet.newBuilder(this.unknownFields) + .mergeFrom(unknownFields) + .build(); + onChanged(); + return (BuilderType) this; + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public boolean isInitialized() { + for (final FieldDescriptor field : getDescriptorForType().getFields()) { + // Check that all required fields are present. + if (field.isRequired()) { + if (!hasField(field)) { + return false; + } + } + // Check that embedded messages are initialized. + if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + if (field.isRepeated()) { + @SuppressWarnings("unchecked") final + List<Message> messageList = (List<Message>) getField(field); + for (final Message element : messageList) { + if (!element.isInitialized()) { + return false; + } + } + } else { + if (hasField(field) && + !((Message) getField(field)).isInitialized()) { + return false; + } + } + } + } + return true; + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public final UnknownFieldSet getUnknownFields() { + return unknownFields; + } + + /** + * Called by subclasses to parse an unknown field. + * @return {@code true} unless the tag is an end-group tag. + */ + protected boolean parseUnknownField( + final CodedInputStream input, + final UnknownFieldSet.Builder unknownFields, + final ExtensionRegistryLite extensionRegistry, + final int tag) throws IOException { + return unknownFields.mergeFieldFrom(tag, input); + } + + /** + * Implementation of {@link BuilderParent} for giving to our children. This + * small inner class makes it so we don't publicly expose the BuilderParent + * methods. + */ + private class BuilderParentImpl implements BuilderParent { + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public void markDirty() { + onChanged(); + } + } + + /** + * Gets the {@link BuilderParent} for giving to our children. + * @return The builder parent for our children. + */ + protected BuilderParent getParentForChildren() { + if (meAsParent == null) { + meAsParent = new BuilderParentImpl(); + } + return meAsParent; + } + + /** + * Called when a the builder or one of its nested children has changed + * and any parent should be notified of its invalidation. + */ + protected final void onChanged() { + if (isClean && builderParent != null) { + builderParent.markDirty(); + + // Don't keep dispatching invalidations until build is called again. + 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()); + } + + /** Like {@link internalGetMapField} but return a mutable version. */ + @SuppressWarnings({"unused", "rawtypes"}) + protected MapField internalGetMutableMapField(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()); + } + } + + // ================================================================= + // Extensions-related stuff + + public interface ExtendableMessageOrBuilder< + MessageType extends ExtendableMessage> extends MessageOrBuilder { + // Re-define for return type covariance. + Message getDefaultInstanceForType(); + + /** Check if a singular extension is present. */ + <Type> boolean hasExtension( + ExtensionLite<MessageType, Type> extension); + + /** Get the number of elements in a repeated extension. */ + <Type> int getExtensionCount( + ExtensionLite<MessageType, List<Type>> extension); + + /** Get the value of an extension. */ + <Type> Type getExtension( + ExtensionLite<MessageType, Type> extension); + + /** Get one element of a repeated extension. */ + <Type> Type getExtension( + ExtensionLite<MessageType, List<Type>> extension, + int index); + } + + /** + * Generated message classes for message types that contain extension ranges + * subclass this. + * + * <p>This class implements type-safe accessors for extensions. They + * implement all the same operations that you can do with normal fields -- + * e.g. "has", "get", and "getCount" -- but for extensions. The extensions + * are identified using instances of the class {@link GeneratedExtension}; + * the protocol compiler generates a static instance of this class for every + * extension in its input. Through the magic of generics, all is made + * type-safe. + * + * <p>For example, imagine you have the {@code .proto} file: + * + * <pre> + * option java_class = "MyProto"; + * + * message Foo { + * extensions 1000 to max; + * } + * + * extend Foo { + * optional int32 bar; + * } + * </pre> + * + * <p>Then you might write code like: + * + * <pre> + * MyProto.Foo foo = getFoo(); + * int i = foo.getExtension(MyProto.bar); + * </pre> + * + * <p>See also {@link ExtendableBuilder}. + */ + public abstract static class ExtendableMessage< + MessageType extends ExtendableMessage> + extends GeneratedMessage + implements ExtendableMessageOrBuilder<MessageType> { + + private final FieldSet<FieldDescriptor> extensions; + + protected ExtendableMessage() { + this.extensions = FieldSet.newFieldSet(); + } + + protected ExtendableMessage( + ExtendableBuilder<MessageType, ?> builder) { + super(builder); + this.extensions = builder.buildExtensions(); + } + + private void verifyExtensionContainingType( + final Extension<MessageType, ?> extension) { + if (extension.getDescriptor().getContainingType() != + getDescriptorForType()) { + // This can only happen if someone uses unchecked operations. + throw new IllegalArgumentException( + "Extension is for type \"" + + extension.getDescriptor().getContainingType().getFullName() + + "\" which does not match message type \"" + + getDescriptorForType().getFullName() + "\"."); + } + } + + /** Check if a singular extension is present. */ + //@Override (Java 1.6 override semantics, but we must support 1.5) + public final <Type> boolean hasExtension( + final ExtensionLite<MessageType, Type> extensionLite) { + Extension<MessageType, Type> extension = checkNotLite(extensionLite); + + verifyExtensionContainingType(extension); + return extensions.hasField(extension.getDescriptor()); + } + + /** Get the number of elements in a repeated extension. */ + //@Override (Java 1.6 override semantics, but we must support 1.5) + public final <Type> int getExtensionCount( + final ExtensionLite<MessageType, List<Type>> extensionLite) { + Extension<MessageType, List<Type>> extension = checkNotLite(extensionLite); + + verifyExtensionContainingType(extension); + final FieldDescriptor descriptor = extension.getDescriptor(); + return extensions.getRepeatedFieldCount(descriptor); + } + + /** Get the value of an extension. */ + //@Override (Java 1.6 override semantics, but we must support 1.5) + @SuppressWarnings("unchecked") + public final <Type> Type getExtension( + final ExtensionLite<MessageType, Type> extensionLite) { + Extension<MessageType, Type> extension = checkNotLite(extensionLite); + + verifyExtensionContainingType(extension); + FieldDescriptor descriptor = extension.getDescriptor(); + final Object value = extensions.getField(descriptor); + if (value == null) { + if (descriptor.isRepeated()) { + return (Type) Collections.emptyList(); + } else if (descriptor.getJavaType() == + FieldDescriptor.JavaType.MESSAGE) { + return (Type) extension.getMessageDefaultInstance(); + } else { + return (Type) extension.fromReflectionType( + descriptor.getDefaultValue()); + } + } else { + return (Type) extension.fromReflectionType(value); + } + } + + /** Get one element of a repeated extension. */ + //@Override (Java 1.6 override semantics, but we must support 1.5) + @SuppressWarnings("unchecked") + public final <Type> Type getExtension( + final ExtensionLite<MessageType, List<Type>> extensionLite, + final int index) { + Extension<MessageType, List<Type>> extension = checkNotLite(extensionLite); + + verifyExtensionContainingType(extension); + FieldDescriptor descriptor = extension.getDescriptor(); + return (Type) extension.singularFromReflectionType( + extensions.getRepeatedField(descriptor, index)); + } + + /** Called by subclasses to check if all extensions are initialized. */ + protected boolean extensionsAreInitialized() { + return extensions.isInitialized(); + } + + @Override + public boolean isInitialized() { + return super.isInitialized() && extensionsAreInitialized(); + } + + @Override + protected boolean parseUnknownField( + CodedInputStream input, + UnknownFieldSet.Builder unknownFields, + ExtensionRegistryLite extensionRegistry, + int tag) throws IOException { + return MessageReflection.mergeFieldFrom( + input, unknownFields, extensionRegistry, getDescriptorForType(), + new MessageReflection.ExtensionAdapter(extensions), tag); + } + + + /** + * Used by parsing constructors in generated classes. + */ + @Override + protected void makeExtensionsImmutable() { + extensions.makeImmutable(); + } + + /** + * Used by subclasses to serialize extensions. Extension ranges may be + * interleaved with field numbers, but we must write them in canonical + * (sorted by field number) order. ExtensionWriter helps us write + * individual ranges of extensions at once. + */ + protected class ExtensionWriter { + // Imagine how much simpler this code would be if Java iterators had + // a way to get the next element without advancing the iterator. + + private final Iterator<Map.Entry<FieldDescriptor, Object>> iter = + extensions.iterator(); + private Map.Entry<FieldDescriptor, Object> next; + private final boolean messageSetWireFormat; + + private ExtensionWriter(final boolean messageSetWireFormat) { + if (iter.hasNext()) { + next = iter.next(); + } + this.messageSetWireFormat = messageSetWireFormat; + } + + public void writeUntil(final int end, final CodedOutputStream output) + throws IOException { + while (next != null && next.getKey().getNumber() < end) { + FieldDescriptor descriptor = next.getKey(); + if (messageSetWireFormat && descriptor.getLiteJavaType() == + WireFormat.JavaType.MESSAGE && + !descriptor.isRepeated()) { + if (next instanceof LazyField.LazyEntry<?>) { + output.writeRawMessageSetExtension(descriptor.getNumber(), + ((LazyField.LazyEntry<?>) next).getField().toByteString()); + } else { + output.writeMessageSetExtension(descriptor.getNumber(), + (Message) next.getValue()); + } + } else { + // TODO(xiangl): Taken care of following code, it may cause + // problem when we use LazyField for normal fields/extensions. + // Due to the optional field can be duplicated at the end of + // serialized bytes, which will make the serialized size change + // after lazy field parsed. So when we use LazyField globally, + // we need to change the following write method to write cached + // bytes directly rather than write the parsed message. + FieldSet.writeField(descriptor, next.getValue(), output); + } + if (iter.hasNext()) { + next = iter.next(); + } else { + next = null; + } + } + } + } + + protected ExtensionWriter newExtensionWriter() { + return new ExtensionWriter(false); + } + protected ExtensionWriter newMessageSetExtensionWriter() { + return new ExtensionWriter(true); + } + + /** Called by subclasses to compute the size of extensions. */ + protected int extensionsSerializedSize() { + return extensions.getSerializedSize(); + } + protected int extensionsSerializedSizeAsMessageSet() { + return extensions.getMessageSetSerializedSize(); + } + + // --------------------------------------------------------------- + // Reflection + + protected Map<FieldDescriptor, Object> getExtensionFields() { + return extensions.getAllFields(); + } + + @Override + public Map<FieldDescriptor, Object> getAllFields() { + final Map<FieldDescriptor, Object> result = + super.getAllFieldsMutable(/* getBytesForString = */ false); + result.putAll(getExtensionFields()); + return Collections.unmodifiableMap(result); + } + + @Override + public Map<FieldDescriptor, Object> getAllFieldsRaw() { + final Map<FieldDescriptor, Object> result = + super.getAllFieldsMutable(/* getBytesForString = */ false); + result.putAll(getExtensionFields()); + return Collections.unmodifiableMap(result); + } + + @Override + public boolean hasField(final FieldDescriptor field) { + if (field.isExtension()) { + verifyContainingType(field); + return extensions.hasField(field); + } else { + return super.hasField(field); + } + } + + @Override + public Object getField(final FieldDescriptor field) { + if (field.isExtension()) { + verifyContainingType(field); + final Object value = extensions.getField(field); + if (value == null) { + if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + // Lacking an ExtensionRegistry, we have no way to determine the + // extension's real type, so we return a DynamicMessage. + return DynamicMessage.getDefaultInstance(field.getMessageType()); + } else { + return field.getDefaultValue(); + } + } else { + return value; + } + } else { + return super.getField(field); + } + } + + @Override + public int getRepeatedFieldCount(final FieldDescriptor field) { + if (field.isExtension()) { + verifyContainingType(field); + return extensions.getRepeatedFieldCount(field); + } else { + return super.getRepeatedFieldCount(field); + } + } + + @Override + public Object getRepeatedField(final FieldDescriptor field, + final int index) { + if (field.isExtension()) { + verifyContainingType(field); + return extensions.getRepeatedField(field, index); + } else { + return super.getRepeatedField(field, index); + } + } + + private void verifyContainingType(final FieldDescriptor field) { + if (field.getContainingType() != getDescriptorForType()) { + throw new IllegalArgumentException( + "FieldDescriptor does not match message type."); + } + } + } + + /** + * Generated message builders for message types that contain extension ranges + * subclass this. + * + * <p>This class implements type-safe accessors for extensions. They + * implement all the same operations that you can do with normal fields -- + * e.g. "get", "set", and "add" -- but for extensions. The extensions are + * identified using instances of the class {@link GeneratedExtension}; the + * protocol compiler generates a static instance of this class for every + * extension in its input. Through the magic of generics, all is made + * type-safe. + * + * <p>For example, imagine you have the {@code .proto} file: + * + * <pre> + * option java_class = "MyProto"; + * + * message Foo { + * extensions 1000 to max; + * } + * + * extend Foo { + * optional int32 bar; + * } + * </pre> + * + * <p>Then you might write code like: + * + * <pre> + * MyProto.Foo foo = + * MyProto.Foo.newBuilder() + * .setExtension(MyProto.bar, 123) + * .build(); + * </pre> + * + * <p>See also {@link ExtendableMessage}. + */ + @SuppressWarnings("unchecked") + public abstract static class ExtendableBuilder< + MessageType extends ExtendableMessage, + BuilderType extends ExtendableBuilder> + extends Builder<BuilderType> + implements ExtendableMessageOrBuilder<MessageType> { + + private FieldSet<FieldDescriptor> extensions = FieldSet.emptySet(); + + protected ExtendableBuilder() {} + + protected ExtendableBuilder( + BuilderParent parent) { + super(parent); + } + + // For immutable message conversion. + void internalSetExtensionSet(FieldSet<FieldDescriptor> extensions) { + this.extensions = extensions; + } + + @Override + public BuilderType clear() { + extensions = FieldSet.emptySet(); + return super.clear(); + } + + // 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 clone() implementation makes it go away. + @Override + public BuilderType clone() { + return super.clone(); + } + + private void ensureExtensionsIsMutable() { + if (extensions.isImmutable()) { + extensions = extensions.clone(); + } + } + + private void verifyExtensionContainingType( + final Extension<MessageType, ?> extension) { + if (extension.getDescriptor().getContainingType() != + getDescriptorForType()) { + // This can only happen if someone uses unchecked operations. + throw new IllegalArgumentException( + "Extension is for type \"" + + extension.getDescriptor().getContainingType().getFullName() + + "\" which does not match message type \"" + + getDescriptorForType().getFullName() + "\"."); + } + } + + /** Check if a singular extension is present. */ + //@Override (Java 1.6 override semantics, but we must support 1.5) + public final <Type> boolean hasExtension( + final ExtensionLite<MessageType, Type> extensionLite) { + Extension<MessageType, Type> extension = checkNotLite(extensionLite); + + verifyExtensionContainingType(extension); + return extensions.hasField(extension.getDescriptor()); + } + + /** Get the number of elements in a repeated extension. */ + //@Override (Java 1.6 override semantics, but we must support 1.5) + public final <Type> int getExtensionCount( + final ExtensionLite<MessageType, List<Type>> extensionLite) { + Extension<MessageType, List<Type>> extension = checkNotLite(extensionLite); + + verifyExtensionContainingType(extension); + final FieldDescriptor descriptor = extension.getDescriptor(); + return extensions.getRepeatedFieldCount(descriptor); + } + + /** Get the value of an extension. */ + //@Override (Java 1.6 override semantics, but we must support 1.5) + public final <Type> Type getExtension( + final ExtensionLite<MessageType, Type> extensionLite) { + Extension<MessageType, Type> extension = checkNotLite(extensionLite); + + verifyExtensionContainingType(extension); + FieldDescriptor descriptor = extension.getDescriptor(); + final Object value = extensions.getField(descriptor); + if (value == null) { + if (descriptor.isRepeated()) { + return (Type) Collections.emptyList(); + } else if (descriptor.getJavaType() == + FieldDescriptor.JavaType.MESSAGE) { + return (Type) extension.getMessageDefaultInstance(); + } else { + return (Type) extension.fromReflectionType( + descriptor.getDefaultValue()); + } + } else { + return (Type) extension.fromReflectionType(value); + } + } + + /** Get one element of a repeated extension. */ + //@Override (Java 1.6 override semantics, but we must support 1.5) + public final <Type> Type getExtension( + final ExtensionLite<MessageType, List<Type>> extensionLite, + final int index) { + Extension<MessageType, List<Type>> extension = checkNotLite(extensionLite); + + verifyExtensionContainingType(extension); + FieldDescriptor descriptor = extension.getDescriptor(); + return (Type) extension.singularFromReflectionType( + extensions.getRepeatedField(descriptor, index)); + } + + /** Set the value of an extension. */ + public final <Type> BuilderType setExtension( + final ExtensionLite<MessageType, Type> extensionLite, + final Type value) { + Extension<MessageType, Type> extension = checkNotLite(extensionLite); + + verifyExtensionContainingType(extension); + ensureExtensionsIsMutable(); + final FieldDescriptor descriptor = extension.getDescriptor(); + extensions.setField(descriptor, extension.toReflectionType(value)); + onChanged(); + return (BuilderType) this; + } + + /** Set the value of one element of a repeated extension. */ + public final <Type> BuilderType setExtension( + final ExtensionLite<MessageType, List<Type>> extensionLite, + final int index, final Type value) { + Extension<MessageType, List<Type>> extension = checkNotLite(extensionLite); + + verifyExtensionContainingType(extension); + ensureExtensionsIsMutable(); + final FieldDescriptor descriptor = extension.getDescriptor(); + extensions.setRepeatedField( + descriptor, index, + extension.singularToReflectionType(value)); + onChanged(); + return (BuilderType) this; + } + + /** Append a value to a repeated extension. */ + public final <Type> BuilderType addExtension( + final ExtensionLite<MessageType, List<Type>> extensionLite, + final Type value) { + Extension<MessageType, List<Type>> extension = checkNotLite(extensionLite); + + verifyExtensionContainingType(extension); + ensureExtensionsIsMutable(); + final FieldDescriptor descriptor = extension.getDescriptor(); + extensions.addRepeatedField( + descriptor, extension.singularToReflectionType(value)); + onChanged(); + return (BuilderType) this; + } + + /** Clear an extension. */ + public final <Type> BuilderType clearExtension( + final ExtensionLite<MessageType, ?> extensionLite) { + Extension<MessageType, ?> extension = checkNotLite(extensionLite); + + verifyExtensionContainingType(extension); + ensureExtensionsIsMutable(); + extensions.clearField(extension.getDescriptor()); + onChanged(); + return (BuilderType) this; + } + + /** Called by subclasses to check if all extensions are initialized. */ + protected boolean extensionsAreInitialized() { + return extensions.isInitialized(); + } + + /** + * Called by the build code path to create a copy of the extensions for + * building the message. + */ + private FieldSet<FieldDescriptor> buildExtensions() { + extensions.makeImmutable(); + return extensions; + } + + @Override + public boolean isInitialized() { + return super.isInitialized() && extensionsAreInitialized(); + } + + /** + * 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( + final CodedInputStream input, + final UnknownFieldSet.Builder unknownFields, + final ExtensionRegistryLite extensionRegistry, + final int tag) throws IOException { + return MessageReflection.mergeFieldFrom( + input, unknownFields, extensionRegistry, getDescriptorForType(), + new MessageReflection.BuilderAdapter(this), tag); + } + + // --------------------------------------------------------------- + // Reflection + + @Override + public Map<FieldDescriptor, Object> getAllFields() { + final Map<FieldDescriptor, Object> result = super.getAllFieldsMutable(); + result.putAll(extensions.getAllFields()); + return Collections.unmodifiableMap(result); + } + + @Override + public Object getField(final FieldDescriptor field) { + if (field.isExtension()) { + verifyContainingType(field); + final Object value = extensions.getField(field); + if (value == null) { + if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + // Lacking an ExtensionRegistry, we have no way to determine the + // extension's real type, so we return a DynamicMessage. + return DynamicMessage.getDefaultInstance(field.getMessageType()); + } else { + return field.getDefaultValue(); + } + } else { + return value; + } + } else { + return super.getField(field); + } + } + + @Override + public int getRepeatedFieldCount(final FieldDescriptor field) { + if (field.isExtension()) { + verifyContainingType(field); + return extensions.getRepeatedFieldCount(field); + } else { + return super.getRepeatedFieldCount(field); + } + } + + @Override + public Object getRepeatedField(final FieldDescriptor field, + final int index) { + if (field.isExtension()) { + verifyContainingType(field); + return extensions.getRepeatedField(field, index); + } else { + return super.getRepeatedField(field, index); + } + } + + @Override + public boolean hasField(final FieldDescriptor field) { + if (field.isExtension()) { + verifyContainingType(field); + return extensions.hasField(field); + } else { + return super.hasField(field); + } + } + + @Override + public BuilderType setField(final FieldDescriptor field, + final Object value) { + if (field.isExtension()) { + verifyContainingType(field); + ensureExtensionsIsMutable(); + extensions.setField(field, value); + onChanged(); + return (BuilderType) this; + } else { + return super.setField(field, value); + } + } + + @Override + public BuilderType clearField(final FieldDescriptor field) { + if (field.isExtension()) { + verifyContainingType(field); + ensureExtensionsIsMutable(); + extensions.clearField(field); + onChanged(); + return (BuilderType) this; + } else { + return super.clearField(field); + } + } + + @Override + public BuilderType setRepeatedField(final FieldDescriptor field, + final int index, final Object value) { + if (field.isExtension()) { + verifyContainingType(field); + ensureExtensionsIsMutable(); + extensions.setRepeatedField(field, index, value); + onChanged(); + return (BuilderType) this; + } else { + return super.setRepeatedField(field, index, value); + } + } + + @Override + public BuilderType addRepeatedField(final FieldDescriptor field, + final Object value) { + if (field.isExtension()) { + verifyContainingType(field); + ensureExtensionsIsMutable(); + extensions.addRepeatedField(field, value); + onChanged(); + return (BuilderType) this; + } else { + return super.addRepeatedField(field, value); + } + } + + protected final void mergeExtensionFields(final ExtendableMessage other) { + ensureExtensionsIsMutable(); + extensions.mergeFrom(other.extensions); + onChanged(); + } + + private void verifyContainingType(final FieldDescriptor field) { + if (field.getContainingType() != getDescriptorForType()) { + throw new IllegalArgumentException( + "FieldDescriptor does not match message type."); + } + } + } + + // ----------------------------------------------------------------- + + /** + * Gets the descriptor for an extension. The implementation depends on whether + * the extension is scoped in the top level of a file or scoped in a Message. + */ + static interface ExtensionDescriptorRetriever { + FieldDescriptor getDescriptor(); + } + + /** For use by generated code only. */ + public static <ContainingType extends Message, Type> + GeneratedExtension<ContainingType, Type> + newMessageScopedGeneratedExtension(final Message scope, + final int descriptorIndex, + final Class singularType, + final Message defaultInstance) { + // For extensions scoped within a Message, we use the Message to resolve + // the outer class's descriptor, from which the extension descriptor is + // obtained. + return new GeneratedExtension<ContainingType, Type>( + new CachedDescriptorRetriever() { + //@Override (Java 1.6 override semantics, but we must support 1.5) + public FieldDescriptor loadDescriptor() { + return scope.getDescriptorForType().getExtensions() + .get(descriptorIndex); + } + }, + singularType, + defaultInstance, + Extension.ExtensionType.IMMUTABLE); + } + + /** For use by generated code only. */ + public static <ContainingType extends Message, Type> + GeneratedExtension<ContainingType, Type> + newFileScopedGeneratedExtension(final Class singularType, + final Message defaultInstance) { + // For extensions scoped within a file, we rely on the outer class's + // static initializer to call internalInit() on the extension when the + // descriptor is available. + return new GeneratedExtension<ContainingType, Type>( + null, // ExtensionDescriptorRetriever is initialized in internalInit(); + singularType, + defaultInstance, + Extension.ExtensionType.IMMUTABLE); + } + + private abstract static class CachedDescriptorRetriever + implements ExtensionDescriptorRetriever { + private volatile FieldDescriptor descriptor; + protected abstract FieldDescriptor loadDescriptor(); + + public FieldDescriptor getDescriptor() { + if (descriptor == null) { + synchronized (this) { + if (descriptor == null) { + descriptor = loadDescriptor(); + } + } + } + return descriptor; + } + } + + /** + * Used in proto1 generated code only. + * + * After enabling bridge, we can define proto2 extensions (the extended type + * is a proto2 mutable message) in a proto1 .proto file. For these extensions + * we should generate proto2 GeneratedExtensions. + */ + public static <ContainingType extends Message, Type> + GeneratedExtension<ContainingType, Type> + newMessageScopedGeneratedExtension( + final Message scope, final String name, + final Class singularType, final Message defaultInstance) { + // For extensions scoped within a Message, we use the Message to resolve + // the outer class's descriptor, from which the extension descriptor is + // obtained. + return new GeneratedExtension<ContainingType, Type>( + new CachedDescriptorRetriever() { + protected FieldDescriptor loadDescriptor() { + return scope.getDescriptorForType().findFieldByName(name); + } + }, + singularType, + defaultInstance, + Extension.ExtensionType.MUTABLE); + } + + /** + * Used in proto1 generated code only. + * + * After enabling bridge, we can define proto2 extensions (the extended type + * is a proto2 mutable message) in a proto1 .proto file. For these extensions + * we should generate proto2 GeneratedExtensions. + */ + public static <ContainingType extends Message, Type> + GeneratedExtension<ContainingType, Type> + newFileScopedGeneratedExtension( + final Class singularType, final Message defaultInstance, + final String descriptorOuterClass, final String extensionName) { + // For extensions scoped within a file, we load the descriptor outer + // class and rely on it to get the FileDescriptor which then can be + // used to obtain the extension's FieldDescriptor. + return new GeneratedExtension<ContainingType, Type>( + new CachedDescriptorRetriever() { + protected FieldDescriptor loadDescriptor() { + try { + Class clazz = + singularType.getClassLoader().loadClass(descriptorOuterClass); + FileDescriptor file = + (FileDescriptor) clazz.getField("descriptor").get(null); + return file.findExtensionByName(extensionName); + } catch (Exception e) { + throw new RuntimeException( + "Cannot load descriptors: " + descriptorOuterClass + + " is not a valid descriptor class name", e); + } + } + }, + singularType, + defaultInstance, + Extension.ExtensionType.MUTABLE); + } + + /** + * Type used to represent generated extensions. The protocol compiler + * generates a static singleton instance of this class for each extension. + * + * <p>For example, imagine you have the {@code .proto} file: + * + * <pre> + * option java_class = "MyProto"; + * + * message Foo { + * extensions 1000 to max; + * } + * + * extend Foo { + * optional int32 bar; + * } + * </pre> + * + * <p>Then, {@code MyProto.Foo.bar} has type + * {@code GeneratedExtension<MyProto.Foo, Integer>}. + * + * <p>In general, users should ignore the details of this type, and simply use + * these static singletons as parameters to the extension accessors defined + * in {@link ExtendableMessage} and {@link ExtendableBuilder}. + */ + public static class GeneratedExtension< + ContainingType extends Message, Type> extends + Extension<ContainingType, Type> { + // TODO(kenton): Find ways to avoid using Java reflection within this + // class. Also try to avoid suppressing unchecked warnings. + + // We can't always initialize the descriptor of a GeneratedExtension when + // we first construct it due to initialization order difficulties (namely, + // the descriptor may not have been constructed yet, since it is often + // constructed by the initializer of a separate module). + // + // In the case of nested extensions, we initialize the + // ExtensionDescriptorRetriever with an instance that uses the scoping + // Message's default instance to retrieve the extension's descriptor. + // + // In the case of non-nested extensions, we initialize the + // ExtensionDescriptorRetriever to null and rely on the outer class's static + // initializer to call internalInit() after the descriptor has been parsed. + GeneratedExtension(ExtensionDescriptorRetriever descriptorRetriever, + Class singularType, + Message messageDefaultInstance, + ExtensionType extensionType) { + if (Message.class.isAssignableFrom(singularType) && + !singularType.isInstance(messageDefaultInstance)) { + throw new IllegalArgumentException( + "Bad messageDefaultInstance for " + singularType.getName()); + } + this.descriptorRetriever = descriptorRetriever; + this.singularType = singularType; + this.messageDefaultInstance = messageDefaultInstance; + + if (ProtocolMessageEnum.class.isAssignableFrom(singularType)) { + this.enumValueOf = getMethodOrDie(singularType, "valueOf", + EnumValueDescriptor.class); + this.enumGetValueDescriptor = + getMethodOrDie(singularType, "getValueDescriptor"); + } else { + this.enumValueOf = null; + this.enumGetValueDescriptor = null; + } + this.extensionType = extensionType; + } + + /** For use by generated code only. */ + public void internalInit(final FieldDescriptor descriptor) { + if (descriptorRetriever != null) { + throw new IllegalStateException("Already initialized."); + } + descriptorRetriever = new ExtensionDescriptorRetriever() { + //@Override (Java 1.6 override semantics, but we must support 1.5) + public FieldDescriptor getDescriptor() { + return descriptor; + } + }; + } + + private ExtensionDescriptorRetriever descriptorRetriever; + private final Class singularType; + private final Message messageDefaultInstance; + private final Method enumValueOf; + private final Method enumGetValueDescriptor; + private final ExtensionType extensionType; + + public FieldDescriptor getDescriptor() { + if (descriptorRetriever == null) { + throw new IllegalStateException( + "getDescriptor() called before internalInit()"); + } + return descriptorRetriever.getDescriptor(); + } + + /** + * If the extension is an embedded message or group, returns the default + * instance of the message. + */ + public Message getMessageDefaultInstance() { + return messageDefaultInstance; + } + + protected ExtensionType getExtensionType() { + return extensionType; + } + + /** + * Convert from the type used by the reflection accessors to the type used + * by native accessors. E.g., for enums, the reflection accessors use + * EnumValueDescriptors but the native accessors use the generated enum + * type. + */ + // @Override + @SuppressWarnings("unchecked") + protected Object fromReflectionType(final Object value) { + FieldDescriptor descriptor = getDescriptor(); + if (descriptor.isRepeated()) { + if (descriptor.getJavaType() == FieldDescriptor.JavaType.MESSAGE || + descriptor.getJavaType() == FieldDescriptor.JavaType.ENUM) { + // Must convert the whole list. + final List result = new ArrayList(); + for (final Object element : (List) value) { + result.add(singularFromReflectionType(element)); + } + return result; + } else { + return value; + } + } else { + return singularFromReflectionType(value); + } + } + + /** + * Like {@link #fromReflectionType(Object)}, but if the type is a repeated + * type, this converts a single element. + */ + // @Override + protected Object singularFromReflectionType(final Object value) { + FieldDescriptor descriptor = getDescriptor(); + switch (descriptor.getJavaType()) { + case MESSAGE: + if (singularType.isInstance(value)) { + return value; + } else { + return messageDefaultInstance.newBuilderForType() + .mergeFrom((Message) value).build(); + } + case ENUM: + return invokeOrDie(enumValueOf, null, (EnumValueDescriptor) value); + default: + return value; + } + } + + /** + * Convert from the type used by the native accessors to the type used + * by reflection accessors. E.g., for enums, the reflection accessors use + * EnumValueDescriptors but the native accessors use the generated enum + * type. + */ + // @Override + @SuppressWarnings("unchecked") + protected Object toReflectionType(final Object value) { + FieldDescriptor descriptor = getDescriptor(); + if (descriptor.isRepeated()) { + if (descriptor.getJavaType() == FieldDescriptor.JavaType.ENUM) { + // Must convert the whole list. + final List result = new ArrayList(); + for (final Object element : (List) value) { + result.add(singularToReflectionType(element)); + } + return result; + } else { + return value; + } + } else { + return singularToReflectionType(value); + } + } + + /** + * Like {@link #toReflectionType(Object)}, but if the type is a repeated + * type, this converts a single element. + */ + // @Override + protected Object singularToReflectionType(final Object value) { + FieldDescriptor descriptor = getDescriptor(); + switch (descriptor.getJavaType()) { + case ENUM: + return invokeOrDie(enumGetValueDescriptor, value); + default: + return value; + } + } + + // @Override + public int getNumber() { + return getDescriptor().getNumber(); + } + + // @Override + public WireFormat.FieldType getLiteType() { + return getDescriptor().getLiteType(); + } + + // @Override + public boolean isRepeated() { + return getDescriptor().isRepeated(); + } + + // @Override + @SuppressWarnings("unchecked") + public Type getDefaultValue() { + if (isRepeated()) { + return (Type) Collections.emptyList(); + } + if (getDescriptor().getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + return (Type) messageDefaultInstance; + } + return (Type) singularFromReflectionType( + getDescriptor().getDefaultValue()); + } + } + + // ================================================================= + + /** Calls Class.getMethod and throws a RuntimeException if it fails. */ + @SuppressWarnings("unchecked") + private static Method getMethodOrDie( + final Class clazz, final String name, final Class... params) { + try { + return clazz.getMethod(name, params); + } catch (NoSuchMethodException e) { + throw new RuntimeException( + "Generated message class \"" + clazz.getName() + + "\" missing method \"" + name + "\".", e); + } + } + + /** Calls invoke and throws a RuntimeException if it fails. */ + private static Object invokeOrDie( + final Method method, final Object object, final Object... params) { + try { + return method.invoke(object, params); + } catch (IllegalAccessException e) { + throw new RuntimeException( + "Couldn't use Java reflection to implement protocol message " + + "reflection.", e); + } catch (InvocationTargetException e) { + final Throwable cause = e.getCause(); + if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } else if (cause instanceof Error) { + throw (Error) cause; + } else { + throw new RuntimeException( + "Unexpected exception thrown by generated accessor method.", cause); + } + } + } + + /** + * 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 + * with access to the fields of a message object using Java reflection. + */ + public static final class FieldAccessorTable { + + /** + * Construct a FieldAccessorTable for a particular message class. Only + * one FieldAccessorTable should ever be constructed per class. + * + * @param descriptor The type's descriptor. + * @param camelCaseNames The camelcase names of all fields in the message. + * These are used to derive the accessor method names. + * @param messageClass The message type. + * @param builderClass The builder type. + */ + public FieldAccessorTable( + final Descriptor descriptor, + final String[] camelCaseNames, + final Class<? extends GeneratedMessage> messageClass, + final Class<? extends Builder> builderClass) { + this(descriptor, camelCaseNames); + ensureFieldAccessorsInitialized(messageClass, builderClass); + } + + /** + * Construct a FieldAccessorTable for a particular message class without + * initializing FieldAccessors. + */ + public FieldAccessorTable( + final Descriptor descriptor, + final String[] camelCaseNames) { + this.descriptor = descriptor; + this.camelCaseNames = camelCaseNames; + fields = new FieldAccessor[descriptor.getFields().size()]; + 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. + * + * @param messageClass The message type. + * @param builderClass The builder type. + * @return this + */ + public FieldAccessorTable ensureFieldAccessorsInitialized( + Class<? extends GeneratedMessage> messageClass, + Class<? extends Builder> builderClass) { + if (initialized) { return this; } + synchronized (this) { + if (initialized) { return this; } + int fieldsSize = fields.length; + for (int i = 0; i < fieldsSize; i++) { + FieldDescriptor field = descriptor.getFields().get(i); + String containingOneofCamelCaseName = null; + if (field.getContainingOneof() != null) { + containingOneofCamelCaseName = + camelCaseNames[fieldsSize + field.getContainingOneof().getIndex()]; + } + if (field.isRepeated()) { + if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + 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); + } else { + fields[i] = new RepeatedFieldAccessor( + field, camelCaseNames[i], messageClass, builderClass); + } + } else { + if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + fields[i] = new SingularMessageFieldAccessor( + field, camelCaseNames[i], messageClass, builderClass, + containingOneofCamelCaseName); + } else if (field.getJavaType() == FieldDescriptor.JavaType.ENUM) { + fields[i] = new SingularEnumFieldAccessor( + field, camelCaseNames[i], messageClass, builderClass, + containingOneofCamelCaseName); + } else if (field.getJavaType() == FieldDescriptor.JavaType.STRING) { + fields[i] = new SingularStringFieldAccessor( + field, camelCaseNames[i], messageClass, builderClass, + containingOneofCamelCaseName); + } else { + fields[i] = new SingularFieldAccessor( + field, camelCaseNames[i], messageClass, builderClass, + containingOneofCamelCaseName); + } + } + } + + int oneofsSize = oneofs.length; + for (int i = 0; i < oneofsSize; i++) { + oneofs[i] = new OneofAccessor( + descriptor, camelCaseNames[i + fieldsSize], + messageClass, builderClass); + } + initialized = true; + camelCaseNames = null; + return this; + } + } + + private final Descriptor descriptor; + private final FieldAccessor[] fields; + private String[] camelCaseNames; + private final OneofAccessor[] oneofs; + private volatile boolean initialized; + + /** Get the FieldAccessor for a particular field. */ + private FieldAccessor getField(final FieldDescriptor field) { + if (field.getContainingType() != descriptor) { + throw new IllegalArgumentException( + "FieldDescriptor does not match message type."); + } else if (field.isExtension()) { + // If this type had extensions, it would subclass ExtendableMessage, + // which overrides the reflection interface to handle extensions. + throw new IllegalArgumentException( + "This type does not have extensions."); + } + return fields[field.getIndex()]; + } + + /** Get the OneofAccessor for a particular oneof. */ + private OneofAccessor getOneof(final OneofDescriptor oneof) { + if (oneof.getContainingType() != descriptor) { + throw new IllegalArgumentException( + "OneofDescriptor does not match message type."); + } + return oneofs[oneof.getIndex()]; + } + + /** + * Abstract interface that provides access to a single field. This is + * implemented differently depending on the field type and cardinality. + */ + private interface FieldAccessor { + Object get(GeneratedMessage message); + Object get(GeneratedMessage.Builder builder); + Object getRaw(GeneratedMessage message); + Object getRaw(GeneratedMessage.Builder builder); + void set(Builder builder, Object value); + Object getRepeated(GeneratedMessage message, int index); + Object getRepeated(GeneratedMessage.Builder builder, int index); + Object getRepeatedRaw(GeneratedMessage message, int index); + Object getRepeatedRaw(GeneratedMessage.Builder builder, int index); + void setRepeated(Builder builder, + int index, Object value); + void addRepeated(Builder builder, Object value); + boolean has(GeneratedMessage message); + boolean has(GeneratedMessage.Builder builder); + int getRepeatedCount(GeneratedMessage message); + int getRepeatedCount(GeneratedMessage.Builder builder); + 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. */ + private static class OneofAccessor { + OneofAccessor( + final Descriptor descriptor, final String camelCaseName, + final Class<? extends GeneratedMessage> messageClass, + final Class<? extends Builder> builderClass) { + this.descriptor = descriptor; + caseMethod = + getMethodOrDie(messageClass, "get" + camelCaseName + "Case"); + caseMethodBuilder = + getMethodOrDie(builderClass, "get" + camelCaseName + "Case"); + clearMethod = getMethodOrDie(builderClass, "clear" + camelCaseName); + } + + private final Descriptor descriptor; + private final Method caseMethod; + private final Method caseMethodBuilder; + private final Method clearMethod; + + public boolean has(final GeneratedMessage message) { + if (((Internal.EnumLite) invokeOrDie(caseMethod, message)).getNumber() == 0) { + return false; + } + return true; + } + + public boolean has(GeneratedMessage.Builder builder) { + if (((Internal.EnumLite) invokeOrDie(caseMethodBuilder, builder)).getNumber() == 0) { + return false; + } + return true; + } + + public FieldDescriptor get(final GeneratedMessage message) { + int fieldNumber = ((Internal.EnumLite) invokeOrDie(caseMethod, message)).getNumber(); + if (fieldNumber > 0) { + return descriptor.findFieldByNumber(fieldNumber); + } + return null; + } + + public FieldDescriptor get(GeneratedMessage.Builder builder) { + int fieldNumber = ((Internal.EnumLite) invokeOrDie(caseMethodBuilder, builder)).getNumber(); + if (fieldNumber > 0) { + return descriptor.findFieldByNumber(fieldNumber); + } + return null; + } + + public void clear(final Builder builder) { + invokeOrDie(clearMethod, builder); + } + } + + private static boolean supportFieldPresence(FileDescriptor file) { + return file.getSyntax() == FileDescriptor.Syntax.PROTO2; + } + + // --------------------------------------------------------------- + + private static class SingularFieldAccessor implements FieldAccessor { + SingularFieldAccessor( + final FieldDescriptor descriptor, final String camelCaseName, + final Class<? extends GeneratedMessage> messageClass, + final Class<? extends Builder> builderClass, + final String containingOneofCamelCaseName) { + field = descriptor; + isOneofField = descriptor.getContainingOneof() != null; + hasHasMethod = supportFieldPresence(descriptor.getFile()) + || (!isOneofField && descriptor.getJavaType() == FieldDescriptor.JavaType.MESSAGE); + getMethod = getMethodOrDie(messageClass, "get" + camelCaseName); + getMethodBuilder = getMethodOrDie(builderClass, "get" + camelCaseName); + type = getMethod.getReturnType(); + setMethod = getMethodOrDie(builderClass, "set" + camelCaseName, type); + hasMethod = + hasHasMethod ? getMethodOrDie(messageClass, "has" + camelCaseName) : null; + hasMethodBuilder = + hasHasMethod ? getMethodOrDie(builderClass, "has" + camelCaseName) : null; + clearMethod = getMethodOrDie(builderClass, "clear" + camelCaseName); + caseMethod = isOneofField ? getMethodOrDie( + messageClass, "get" + containingOneofCamelCaseName + "Case") : null; + caseMethodBuilder = isOneofField ? getMethodOrDie( + builderClass, "get" + containingOneofCamelCaseName + "Case") : null; + } + + // Note: We use Java reflection to call public methods rather than + // access private fields directly as this avoids runtime security + // checks. + protected final Class<?> type; + protected final Method getMethod; + protected final Method getMethodBuilder; + protected final Method setMethod; + protected final Method hasMethod; + protected final Method hasMethodBuilder; + protected final Method clearMethod; + protected final Method caseMethod; + protected final Method caseMethodBuilder; + protected final FieldDescriptor field; + protected final boolean isOneofField; + protected final boolean hasHasMethod; + + private int getOneofFieldNumber(final GeneratedMessage message) { + return ((Internal.EnumLite) invokeOrDie(caseMethod, message)).getNumber(); + } + + private int getOneofFieldNumber(final GeneratedMessage.Builder builder) { + return ((Internal.EnumLite) invokeOrDie(caseMethodBuilder, builder)).getNumber(); + } + + public Object get(final GeneratedMessage message) { + return invokeOrDie(getMethod, message); + } + public Object get(GeneratedMessage.Builder builder) { + return invokeOrDie(getMethodBuilder, builder); + } + public Object getRaw(final GeneratedMessage message) { + return get(message); + } + public Object getRaw(GeneratedMessage.Builder builder) { + return get(builder); + } + public void set(final Builder builder, final Object value) { + invokeOrDie(setMethod, builder, value); + } + public Object getRepeated(final GeneratedMessage message, + final int index) { + throw new UnsupportedOperationException( + "getRepeatedField() called on a singular field."); + } + public Object getRepeatedRaw(final GeneratedMessage message, + final int index) { + throw new UnsupportedOperationException( + "getRepeatedFieldRaw() called on a singular field."); + } + public Object getRepeated(GeneratedMessage.Builder builder, int index) { + throw new UnsupportedOperationException( + "getRepeatedField() called on a singular field."); + } + public Object getRepeatedRaw(GeneratedMessage.Builder builder, + int index) { + throw new UnsupportedOperationException( + "getRepeatedFieldRaw() called on a singular field."); + } + public void setRepeated(final Builder builder, final int index, + final Object value) { + throw new UnsupportedOperationException( + "setRepeatedField() called on a singular field."); + } + public void addRepeated(final Builder builder, final Object value) { + throw new UnsupportedOperationException( + "addRepeatedField() called on a singular field."); + } + public boolean has(final GeneratedMessage message) { + if (!hasHasMethod) { + if (isOneofField) { + return getOneofFieldNumber(message) == field.getNumber(); + } + return !get(message).equals(field.getDefaultValue()); + } + return (Boolean) invokeOrDie(hasMethod, message); + } + public boolean has(GeneratedMessage.Builder builder) { + if (!hasHasMethod) { + if (isOneofField) { + return getOneofFieldNumber(builder) == field.getNumber(); + } + return !get(builder).equals(field.getDefaultValue()); + } + return (Boolean) invokeOrDie(hasMethodBuilder, builder); + } + public int getRepeatedCount(final GeneratedMessage message) { + throw new UnsupportedOperationException( + "getRepeatedFieldSize() called on a singular field."); + } + public int getRepeatedCount(GeneratedMessage.Builder builder) { + throw new UnsupportedOperationException( + "getRepeatedFieldSize() called on a singular field."); + } + public void clear(final Builder builder) { + invokeOrDie(clearMethod, builder); + } + public Message.Builder newBuilder() { + throw new UnsupportedOperationException( + "newBuilderForField() called on a non-Message type."); + } + public Message.Builder getBuilder(GeneratedMessage.Builder builder) { + 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 { + protected final Class type; + protected final Method getMethod; + protected final Method getMethodBuilder; + protected final Method getRepeatedMethod; + protected final Method getRepeatedMethodBuilder; + protected final Method setRepeatedMethod; + protected final Method addRepeatedMethod; + protected final Method getCountMethod; + protected final Method getCountMethodBuilder; + protected final Method clearMethod; + + RepeatedFieldAccessor( + final FieldDescriptor descriptor, final String camelCaseName, + final Class<? extends GeneratedMessage> messageClass, + final Class<? extends Builder> builderClass) { + getMethod = getMethodOrDie(messageClass, + "get" + camelCaseName + "List"); + getMethodBuilder = getMethodOrDie(builderClass, + "get" + camelCaseName + "List"); + getRepeatedMethod = + getMethodOrDie(messageClass, "get" + camelCaseName, Integer.TYPE); + getRepeatedMethodBuilder = + getMethodOrDie(builderClass, "get" + camelCaseName, Integer.TYPE); + type = getRepeatedMethod.getReturnType(); + setRepeatedMethod = + getMethodOrDie(builderClass, "set" + camelCaseName, + Integer.TYPE, type); + addRepeatedMethod = + getMethodOrDie(builderClass, "add" + camelCaseName, type); + getCountMethod = + getMethodOrDie(messageClass, "get" + camelCaseName + "Count"); + getCountMethodBuilder = + getMethodOrDie(builderClass, "get" + camelCaseName + "Count"); + + clearMethod = getMethodOrDie(builderClass, "clear" + camelCaseName); + } + + public Object get(final GeneratedMessage message) { + return invokeOrDie(getMethod, message); + } + public Object get(GeneratedMessage.Builder builder) { + return invokeOrDie(getMethodBuilder, builder); + } + public Object getRaw(final GeneratedMessage message) { + return get(message); + } + public Object getRaw(GeneratedMessage.Builder builder) { + return get(builder); + } + public void set(final Builder builder, final Object value) { + // Add all the elements individually. This serves two purposes: + // 1) Verifies that each element has the correct type. + // 2) Insures that the caller cannot modify the list later on and + // have the modifications be reflected in the message. + clear(builder); + for (final Object element : (List<?>) value) { + addRepeated(builder, element); + } + } + public Object getRepeated(final GeneratedMessage message, + final int index) { + return invokeOrDie(getRepeatedMethod, message, index); + } + public Object getRepeated(GeneratedMessage.Builder builder, int index) { + return invokeOrDie(getRepeatedMethodBuilder, builder, index); + } + public Object getRepeatedRaw(GeneratedMessage message, int index) { + return getRepeated(message, index); + } + public Object getRepeatedRaw(GeneratedMessage.Builder builder, + int index) { + return getRepeated(builder, index); + } + public void setRepeated(final Builder builder, + final int index, final Object value) { + invokeOrDie(setRepeatedMethod, builder, index, value); + } + public void addRepeated(final Builder builder, final Object value) { + invokeOrDie(addRepeatedMethod, builder, value); + } + public boolean has(final GeneratedMessage message) { + throw new UnsupportedOperationException( + "hasField() called on a repeated field."); + } + public boolean has(GeneratedMessage.Builder builder) { + throw new UnsupportedOperationException( + "hasField() called on a repeated field."); + } + public int getRepeatedCount(final GeneratedMessage message) { + return (Integer) invokeOrDie(getCountMethod, message); + } + public int getRepeatedCount(GeneratedMessage.Builder builder) { + return (Integer) invokeOrDie(getCountMethodBuilder, builder); + } + public void clear(final Builder builder) { + invokeOrDie(clearMethod, builder); + } + public Message.Builder newBuilder() { + throw new UnsupportedOperationException( + "newBuilderForField() called on a non-Message type."); + } + public Message.Builder getBuilder(GeneratedMessage.Builder builder) { + 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<? extends GeneratedMessage> messageClass, + final Class<? extends Builder> 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()); + } + + private MapField<?, ?> getMutableMapField( + GeneratedMessage.Builder builder) { + return (MapField<?, ?>) builder.internalGetMutableMapField( + 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 Object getRaw(GeneratedMessage message) { + return get(message); + } + + public Object getRaw(GeneratedMessage.Builder builder) { + return get(builder); + } + + 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 Object getRepeatedRaw(GeneratedMessage message, int index) { + return getRepeated(message, index); + } + + public Object getRepeatedRaw(Builder builder, int index) { + return getRepeated(builder, index); + } + + public void setRepeated(Builder builder, int index, Object value) { + getMutableMapField(builder).getMutableList().set(index, (Message) value); + } + + public void addRepeated(Builder builder, Object value) { + getMutableMapField(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) { + getMutableMapField(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."); + } + } + + // --------------------------------------------------------------- + + private static final class SingularEnumFieldAccessor + extends SingularFieldAccessor { + SingularEnumFieldAccessor( + final FieldDescriptor descriptor, final String camelCaseName, + final Class<? extends GeneratedMessage> messageClass, + final Class<? extends Builder> 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)); + } + } + + private static final class RepeatedEnumFieldAccessor + extends RepeatedFieldAccessor { + RepeatedEnumFieldAccessor( + final FieldDescriptor descriptor, final String camelCaseName, + final Class<? extends GeneratedMessage> messageClass, + final Class<? extends Builder> 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(); + final int size = getRepeatedCount(message); + for (int i = 0; i < size; i++) { + newList.add(getRepeated(message, i)); + } + return Collections.unmodifiableList(newList); + } + + @Override + @SuppressWarnings("unchecked") + public Object get(final GeneratedMessage.Builder builder) { + final List newList = new ArrayList(); + final int size = getRepeatedCount(builder); + for (int i = 0; i < size; i++) { + newList.add(getRepeated(builder, i)); + } + return Collections.unmodifiableList(newList); + } + + @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)); + } + } + + // --------------------------------------------------------------- + + /** + * Field accessor for string fields. + * + * <p>This class makes getFooBytes() and setFooBytes() available for + * reflection API so that reflection based serialize/parse functions can + * access the raw bytes of the field to preserve non-UTF8 bytes in the + * string. + * + * <p>This ensures the serialize/parse round-trip safety, which is important + * for servers which forward messages. + */ + private static final class SingularStringFieldAccessor + extends SingularFieldAccessor { + SingularStringFieldAccessor( + final FieldDescriptor descriptor, final String camelCaseName, + final Class<? extends GeneratedMessage> messageClass, + final Class<? extends Builder> builderClass, + final String containingOneofCamelCaseName) { + super(descriptor, camelCaseName, messageClass, builderClass, + containingOneofCamelCaseName); + getBytesMethod = getMethodOrDie(messageClass, + "get" + camelCaseName + "Bytes"); + getBytesMethodBuilder = getMethodOrDie(builderClass, + "get" + camelCaseName + "Bytes"); + setBytesMethodBuilder = getMethodOrDie(builderClass, + "set" + camelCaseName + "Bytes", ByteString.class); + } + + private final Method getBytesMethod; + private final Method getBytesMethodBuilder; + private final Method setBytesMethodBuilder; + + @Override + public Object getRaw(final GeneratedMessage message) { + return invokeOrDie(getBytesMethod, message); + } + + @Override + public Object getRaw(GeneratedMessage.Builder builder) { + return invokeOrDie(getBytesMethodBuilder, builder); + } + + @Override + public void set(GeneratedMessage.Builder builder, Object value) { + if (value instanceof ByteString) { + invokeOrDie(setBytesMethodBuilder, builder, value); + } else { + super.set(builder, value); + } + } + } + + // --------------------------------------------------------------- + + private static final class SingularMessageFieldAccessor + extends SingularFieldAccessor { + SingularMessageFieldAccessor( + final FieldDescriptor descriptor, final String camelCaseName, + final Class<? extends GeneratedMessage> messageClass, + final Class<? extends Builder> builderClass, + final String containingOneofCamelCaseName) { + super(descriptor, camelCaseName, messageClass, builderClass, + containingOneofCamelCaseName); + + newBuilderMethod = getMethodOrDie(type, "newBuilder"); + getBuilderMethodBuilder = + getMethodOrDie(builderClass, "get" + camelCaseName + "Builder"); + } + + private final Method newBuilderMethod; + private final Method getBuilderMethodBuilder; + + private Object coerceType(final Object value) { + if (type.isInstance(value)) { + return value; + } else { + // The value is not the exact right message type. However, if it + // is an alternative implementation of the same type -- e.g. a + // DynamicMessage -- we should accept it. In this case we can make + // a copy of the message. + return ((Message.Builder) invokeOrDie(newBuilderMethod, null)) + .mergeFrom((Message) value).buildPartial(); + } + } + + @Override + public void set(final Builder builder, final Object value) { + super.set(builder, coerceType(value)); + } + @Override + public Message.Builder newBuilder() { + return (Message.Builder) invokeOrDie(newBuilderMethod, null); + } + @Override + public Message.Builder getBuilder(GeneratedMessage.Builder builder) { + return (Message.Builder) invokeOrDie(getBuilderMethodBuilder, builder); + } + } + + private static final class RepeatedMessageFieldAccessor + extends RepeatedFieldAccessor { + RepeatedMessageFieldAccessor( + final FieldDescriptor descriptor, final String camelCaseName, + final Class<? extends GeneratedMessage> messageClass, + final Class<? extends Builder> builderClass) { + 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)) { + return value; + } else { + // The value is not the exact right message type. However, if it + // is an alternative implementation of the same type -- e.g. a + // DynamicMessage -- we should accept it. In this case we can make + // a copy of the message. + return ((Message.Builder) invokeOrDie(newBuilderMethod, null)) + .mergeFrom((Message) value).build(); + } + } + + @Override + public void setRepeated(final Builder builder, + final int index, final Object value) { + super.setRepeated(builder, index, coerceType(value)); + } + @Override + public void addRepeated(final Builder builder, final Object value) { + super.addRepeated(builder, coerceType(value)); + } + @Override + 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); + } + } + } + + /** + * Replaces this object in the output stream with a serialized form. + * Part of Java's serialization magic. Generated sub-classes must override + * this method by calling {@code return super.writeReplace();} + * @return a SerializedForm of this message + */ + 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 <MessageType extends ExtendableMessage<MessageType>, T> + Extension<MessageType, T> checkNotLite( + ExtensionLite<MessageType, T> extension) { + if (extension.isLite()) { + throw new IllegalArgumentException("Expected non-lite extension."); + } + + return (Extension<MessageType, T>) extension; + } + + protected static int computeStringSize(final int fieldNumber, final Object value) { + if (value instanceof String) { + return CodedOutputStream.computeStringSize(fieldNumber, (String) value); + } else { + return CodedOutputStream.computeBytesSize(fieldNumber, (ByteString) value); + } + } + + protected static int computeStringSizeNoTag(final Object value) { + if (value instanceof String) { + return CodedOutputStream.computeStringSizeNoTag((String) value); + } else { + return CodedOutputStream.computeBytesSizeNoTag((ByteString) value); + } + } + + protected static void writeString( + CodedOutputStream output, final int fieldNumber, final Object value) throws IOException { + if (value instanceof String) { + output.writeString(fieldNumber, (String) value); + } else { + output.writeBytes(fieldNumber, (ByteString) value); + } + } + + protected static void writeStringNoTag( + CodedOutputStream output, final Object value) throws IOException { + if (value instanceof String) { + output.writeStringNoTag((String) value); + } else { + output.writeBytesNoTag((ByteString) value); + } + } +} diff --git a/java/core/src/main/java/com/google/protobuf/GeneratedMessageLite.java b/java/core/src/main/java/com/google/protobuf/GeneratedMessageLite.java new file mode 100644 index 00000000..81e1862c --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/GeneratedMessageLite.java @@ -0,0 +1,1276 @@ +// 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.Internal.BooleanList; +import com.google.protobuf.Internal.DoubleList; +import com.google.protobuf.Internal.FloatList; +import com.google.protobuf.Internal.IntList; +import com.google.protobuf.Internal.LongList; +import com.google.protobuf.Internal.ProtobufList; +import com.google.protobuf.WireFormat.FieldType; + +import java.io.IOException; +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * Lite version of {@link GeneratedMessage}. + * + * @author kenton@google.com Kenton Varda + */ +public abstract class GeneratedMessageLite< + MessageType extends GeneratedMessageLite<MessageType, BuilderType>, + BuilderType extends GeneratedMessageLite.Builder<MessageType, BuilderType>> + extends AbstractMessageLite + implements Serializable { + + private static final long serialVersionUID = 1L; + + /** For use by generated code only. Lazily initialized to reduce allocations. */ + protected UnknownFieldSetLite unknownFields = null; + + /** For use by generated code only. */ + protected int memoizedSerializedSize = -1; + + @SuppressWarnings("unchecked") // Guaranteed by runtime. + public final Parser<MessageType> getParserForType() { + return (Parser<MessageType>) dynamicMethod(MethodToInvoke.GET_PARSER); + } + + @SuppressWarnings("unchecked") // Guaranteed by runtime. + public final MessageType getDefaultInstanceForType() { + return (MessageType) dynamicMethod(MethodToInvoke.GET_DEFAULT_INSTANCE); + } + + @SuppressWarnings("unchecked") // Guaranteed by runtime. + public final BuilderType newBuilderForType() { + return (BuilderType) dynamicMethod(MethodToInvoke.NEW_BUILDER); + } + + // The general strategy for unknown fields is to use an UnknownFieldSetLite that is treated as + // mutable during the parsing constructor and immutable after. This allows us to avoid + // any unnecessary intermediary allocations while reducing the generated code size. + + /** + * Lazily initializes unknown fields. + */ + private final void ensureUnknownFieldsInitialized() { + if (unknownFields == null) { + unknownFields = UnknownFieldSetLite.newInstance(); + } + } + + /** + * 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(int tag, CodedInputStream input) throws IOException { + // This will avoid the allocation of unknown fields when a group tag is encountered. + if (WireFormat.getTagWireType(tag) == WireFormat.WIRETYPE_END_GROUP) { + return false; + } + + ensureUnknownFieldsInitialized(); + return unknownFields.mergeFieldFrom(tag, input); + } + + /** + * Called by subclasses to parse an unknown field. For use by generated code only. + */ + protected void mergeVarintField(int tag, int value) { + ensureUnknownFieldsInitialized(); + unknownFields.mergeVarintField(tag, value); + } + + /** + * Called by subclasses to parse an unknown field. For use by generated code only. + */ + protected void mergeLengthDelimitedField(int fieldNumber, ByteString value) { + ensureUnknownFieldsInitialized(); + unknownFields.mergeLengthDelimitedField(fieldNumber, value); + } + + /** + * Called by subclasses to complete parsing. For use by generated code only. + */ + protected void doneParsing() { + if (unknownFields == null) { + unknownFields = UnknownFieldSetLite.getDefaultInstance(); + } else { + unknownFields.makeImmutable(); + } + } + + public final boolean isInitialized() { + return dynamicMethod(MethodToInvoke.IS_INITIALIZED, Boolean.TRUE) != null; + } + + public final BuilderType toBuilder() { + BuilderType builder = (BuilderType) dynamicMethod(MethodToInvoke.NEW_BUILDER); + builder.mergeFrom((MessageType) this); + return builder; + } + + /** + * Defines which method path to invoke in {@link GeneratedMessageLite + * #dynamicMethod(MethodToInvoke, Object...)}. + * <p> + * For use by generated code only. + */ + public static enum MethodToInvoke { + IS_INITIALIZED, + PARSE_PARTIAL_FROM, + MERGE_FROM, + MAKE_IMMUTABLE, + NEW_INSTANCE, + NEW_BUILDER, + GET_DEFAULT_INSTANCE, + GET_PARSER; + } + + /** + * A method that implements different types of operations described in {@link MethodToInvoke}. + * Theses different kinds of operations are required to implement message-level operations for + * builders in the runtime. This method bundles those operations to reduce the generated methods + * count. + * <ul> + * <li>{@code PARSE_PARTIAL_FROM} is parameterized with an {@link CodedInputStream} and + * {@link ExtensionRegistryLite}. It consumes the input stream, parsing the contents into the + * returned protocol buffer. If parsing throws an {@link InvalidProtocolBufferException}, the + * implementation wraps it in a RuntimeException + * <li>{@code NEW_INSTANCE} returns a new instance of the protocol buffer + * <li>{@code IS_INITIALIZED} is parameterized with a {@code Boolean} detailing whether to + * memoize. It returns {@code null} for false and the default instance for true. We optionally + * memoize to support the Builder case, where memoization is not desired. + * <li>{@code NEW_BUILDER} returns a {@code BuilderType} instance. + * <li>{@code MERGE_FROM} is parameterized with a {@code MessageType} and merges the fields from + * that instance into this instance. + * <li>{@code MAKE_IMMUTABLE} sets all internal fields to an immutable state. + * </ul> + * This method, plus the implementation of the Builder, enables the Builder class to be proguarded + * away entirely on Android. + * <p> + * For use by generated code only. + */ + protected abstract Object dynamicMethod(MethodToInvoke method, Object arg0, Object arg1); + + /** + * Same as {@link #dynamicMethod(MethodToInvoke, Object, Object)} with {@code null} padding. + */ + protected Object dynamicMethod(MethodToInvoke method, Object arg0) { + return dynamicMethod(method, arg0, null); + } + + /** + * Same as {@link #dynamicMethod(MethodToInvoke, Object, Object)} with {@code null} padding. + */ + protected Object dynamicMethod(MethodToInvoke method) { + return dynamicMethod(method, null, null); + } + + /** + * Merge some unknown fields into the {@link UnknownFieldSetLite} for this + * message. + * + * <p>For use by generated code only. + */ + protected final void mergeUnknownFields(UnknownFieldSetLite unknownFields) { + this.unknownFields = UnknownFieldSetLite.mutableCopyOf(this.unknownFields, unknownFields); + } + + @SuppressWarnings("unchecked") + public abstract static class Builder< + MessageType extends GeneratedMessageLite<MessageType, BuilderType>, + BuilderType extends Builder<MessageType, BuilderType>> + extends AbstractMessageLite.Builder<BuilderType> { + + private final MessageType defaultInstance; + protected MessageType instance; + protected boolean isBuilt; + + protected Builder(MessageType defaultInstance) { + this.defaultInstance = defaultInstance; + this.instance = (MessageType) defaultInstance.dynamicMethod(MethodToInvoke.NEW_INSTANCE); + isBuilt = false; + } + + /** + * Called before any method that would mutate the builder to ensure that it correctly copies + * any state before the write happens to preserve immutability guarantees. + */ + protected void copyOnWrite() { + if (isBuilt) { + MessageType newInstance = (MessageType) instance.dynamicMethod(MethodToInvoke.NEW_INSTANCE); + newInstance.dynamicMethod(MethodToInvoke.MERGE_FROM, instance); + instance = newInstance; + isBuilt = false; + } + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public final boolean isInitialized() { + return GeneratedMessageLite.isInitialized(instance, false /* shouldMemoize */); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public final BuilderType clear() { + // No need to copy on write since we're dropping the instance anyways. + instance = (MessageType) instance.dynamicMethod(MethodToInvoke.NEW_INSTANCE); + return (BuilderType) this; + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public BuilderType clone() { + BuilderType builder = + (BuilderType) getDefaultInstanceForType().newBuilderForType(); + builder.mergeFrom(buildPartial()); + return builder; + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public MessageType buildPartial() { + if (isBuilt) { + return instance; + } + + instance.dynamicMethod(MethodToInvoke.MAKE_IMMUTABLE); + instance.unknownFields.makeImmutable(); + + isBuilt = true; + return instance; + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public final MessageType build() { + MessageType result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + /** All subclasses implement this. */ + public BuilderType mergeFrom(MessageType message) { + copyOnWrite(); + instance.dynamicMethod(MethodToInvoke.MERGE_FROM, message); + return (BuilderType) this; + } + + public MessageType getDefaultInstanceForType() { + return defaultInstance; + } + + 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; + } + } + + + // ================================================================= + // Extensions-related stuff + + /** + * Lite equivalent of {@link com.google.protobuf.GeneratedMessage.ExtendableMessageOrBuilder}. + */ + public interface ExtendableMessageOrBuilder< + MessageType extends ExtendableMessage<MessageType, BuilderType>, + BuilderType extends ExtendableBuilder<MessageType, BuilderType>> + extends MessageLiteOrBuilder { + + /** Check if a singular extension is present. */ + <Type> boolean hasExtension( + ExtensionLite<MessageType, Type> extension); + + /** Get the number of elements in a repeated extension. */ + <Type> int getExtensionCount( + ExtensionLite<MessageType, List<Type>> extension); + + /** Get the value of an extension. */ + <Type> Type getExtension(ExtensionLite<MessageType, Type> extension); + + /** Get one element of a repeated extension. */ + <Type> Type getExtension( + ExtensionLite<MessageType, List<Type>> extension, + int index); + } + + /** + * Lite equivalent of {@link GeneratedMessage.ExtendableMessage}. + */ + public abstract static class ExtendableMessage< + MessageType extends ExtendableMessage<MessageType, BuilderType>, + BuilderType extends ExtendableBuilder<MessageType, BuilderType>> + extends GeneratedMessageLite<MessageType, BuilderType> + implements ExtendableMessageOrBuilder<MessageType, BuilderType> { + + /** + * Represents the set of extensions on this message. For use by generated + * code only. + */ + protected FieldSet<ExtensionDescriptor> extensions = FieldSet.newFieldSet(); + + protected final void mergeExtensionFields(final MessageType other) { + if (extensions.isImmutable()) { + extensions = extensions.clone(); + } + extensions.mergeFrom(((ExtendableMessage) other).extensions); + } + + /** + * Parse an unknown field or an extension. For use by generated code only. + * + * <p>For use by generated code only. + * + * @return {@code true} unless the tag is an end-group tag. + */ + protected <MessageType extends MessageLite> boolean parseUnknownField( + MessageType defaultInstance, + CodedInputStream input, + ExtensionRegistryLite extensionRegistry, + int tag) throws IOException { + int wireType = WireFormat.getTagWireType(tag); + int fieldNumber = WireFormat.getTagFieldNumber(tag); + + // TODO(dweis): How much bytecode would be saved by not requiring the generated code to + // provide the default instance? + GeneratedExtension<MessageType, ?> extension = extensionRegistry.findLiteExtensionByNumber( + defaultInstance, fieldNumber); + + boolean unknown = false; + boolean packed = false; + if (extension == null) { + unknown = true; // Unknown field. + } else if (wireType == FieldSet.getWireFormatForFieldType( + extension.descriptor.getLiteType(), + false /* isPacked */)) { + packed = false; // Normal, unpacked value. + } else if (extension.descriptor.isRepeated && + extension.descriptor.type.isPackable() && + wireType == FieldSet.getWireFormatForFieldType( + extension.descriptor.getLiteType(), + true /* isPacked */)) { + packed = true; // Packed value. + } else { + unknown = true; // Wrong wire type. + } + + if (unknown) { // Unknown field or wrong wire type. Skip. + return parseUnknownField(tag, input); + } + + if (packed) { + int length = input.readRawVarint32(); + int limit = input.pushLimit(length); + if (extension.descriptor.getLiteType() == WireFormat.FieldType.ENUM) { + while (input.getBytesUntilLimit() > 0) { + int rawValue = input.readEnum(); + Object value = + extension.descriptor.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; + } + extensions.addRepeatedField(extension.descriptor, + extension.singularToFieldSetType(value)); + } + } else { + while (input.getBytesUntilLimit() > 0) { + Object value = + FieldSet.readPrimitiveField(input, + extension.descriptor.getLiteType(), + /*checkUtf8=*/ false); + extensions.addRepeatedField(extension.descriptor, value); + } + } + input.popLimit(limit); + } else { + Object value; + switch (extension.descriptor.getLiteJavaType()) { + case MESSAGE: { + MessageLite.Builder subBuilder = null; + if (!extension.descriptor.isRepeated()) { + MessageLite existingValue = + (MessageLite) extensions.getField(extension.descriptor); + if (existingValue != null) { + subBuilder = existingValue.toBuilder(); + } + } + if (subBuilder == null) { + subBuilder = extension.getMessageDefaultInstance() + .newBuilderForType(); + } + if (extension.descriptor.getLiteType() == + WireFormat.FieldType.GROUP) { + input.readGroup(extension.getNumber(), + subBuilder, extensionRegistry); + } else { + input.readMessage(subBuilder, extensionRegistry); + } + value = subBuilder.build(); + break; + } + case ENUM: + int rawValue = input.readEnum(); + value = extension.descriptor.getEnumType() + .findValueByNumber(rawValue); + // If the number isn't recognized as a valid value for this enum, + // write it to unknown fields object. + if (value == null) { + mergeVarintField(fieldNumber, rawValue); + return true; + } + break; + default: + value = FieldSet.readPrimitiveField(input, + extension.descriptor.getLiteType(), + /*checkUtf8=*/ false); + break; + } + + if (extension.descriptor.isRepeated()) { + extensions.addRepeatedField(extension.descriptor, + extension.singularToFieldSetType(value)); + } else { + extensions.setField(extension.descriptor, + extension.singularToFieldSetType(value)); + } + } + + return true; + } + + private void verifyExtensionContainingType( + final GeneratedExtension<MessageType, ?> extension) { + if (extension.getContainingTypeDefaultInstance() != + getDefaultInstanceForType()) { + // This can only happen if someone uses unchecked operations. + throw new IllegalArgumentException( + "This extension is for a different message type. Please make " + + "sure that you are not suppressing any generics type warnings."); + } + } + + /** Check if a singular extension is present. */ + //@Override (Java 1.6 override semantics, but we must support 1.5) + public final <Type> boolean hasExtension( + final ExtensionLite<MessageType, Type> extension) { + GeneratedExtension<MessageType, Type> 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 <Type> int getExtensionCount( + final ExtensionLite<MessageType, List<Type>> extension) { + GeneratedExtension<MessageType, List<Type>> 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> Type getExtension( + final ExtensionLite<MessageType, Type> extension) { + GeneratedExtension<MessageType, Type> extensionLite = + checkIsLite(extension); + + verifyExtensionContainingType(extensionLite); + final Object value = extensions.getField(extensionLite.descriptor); + if (value == null) { + return extensionLite.defaultValue; + } else { + return (Type) extensionLite.fromFieldSetType(value); + } + } + + /** Get one element of a repeated extension. */ + //@Override (Java 1.6 override semantics, but we must support 1.5) + @SuppressWarnings("unchecked") + public final <Type> Type getExtension( + final ExtensionLite<MessageType, List<Type>> extension, + final int index) { + GeneratedExtension<MessageType, List<Type>> extensionLite = + checkIsLite(extension); + + verifyExtensionContainingType(extensionLite); + return (Type) extensionLite.singularFromFieldSetType( + extensions.getRepeatedField(extensionLite.descriptor, index)); + } + + /** Called by subclasses to check if all extensions are initialized. */ + protected boolean extensionsAreInitialized() { + return extensions.isInitialized(); + } + + + @Override + protected final void doneParsing() { + super.doneParsing(); + + extensions.makeImmutable(); + } + + /** + * Used by subclasses to serialize extensions. Extension ranges may be + * interleaved with field numbers, but we must write them in canonical + * (sorted by field number) order. ExtensionWriter helps us write + * individual ranges of extensions at once. + */ + protected class ExtensionWriter { + // Imagine how much simpler this code would be if Java iterators had + // a way to get the next element without advancing the iterator. + + private final Iterator<Map.Entry<ExtensionDescriptor, Object>> iter = + extensions.iterator(); + private Map.Entry<ExtensionDescriptor, Object> next; + private final boolean messageSetWireFormat; + + private ExtensionWriter(boolean messageSetWireFormat) { + if (iter.hasNext()) { + next = iter.next(); + } + this.messageSetWireFormat = messageSetWireFormat; + } + + public void writeUntil(final int end, final CodedOutputStream output) + throws IOException { + while (next != null && next.getKey().getNumber() < end) { + ExtensionDescriptor extension = next.getKey(); + if (messageSetWireFormat && extension.getLiteJavaType() == + WireFormat.JavaType.MESSAGE && + !extension.isRepeated()) { + output.writeMessageSetExtension(extension.getNumber(), + (MessageLite) next.getValue()); + } else { + FieldSet.writeField(extension, next.getValue(), output); + } + if (iter.hasNext()) { + next = iter.next(); + } else { + next = null; + } + } + } + } + + protected ExtensionWriter newExtensionWriter() { + return new ExtensionWriter(false); + } + protected ExtensionWriter newMessageSetExtensionWriter() { + return new ExtensionWriter(true); + } + + /** Called by subclasses to compute the size of extensions. */ + protected int extensionsSerializedSize() { + return extensions.getSerializedSize(); + } + protected int extensionsSerializedSizeAsMessageSet() { + return extensions.getMessageSetSerializedSize(); + } + } + + /** + * Lite equivalent of {@link GeneratedMessage.ExtendableBuilder}. + */ + @SuppressWarnings("unchecked") + public abstract static class ExtendableBuilder< + MessageType extends ExtendableMessage<MessageType, BuilderType>, + BuilderType extends ExtendableBuilder<MessageType, BuilderType>> + extends Builder<MessageType, BuilderType> + implements ExtendableMessageOrBuilder<MessageType, BuilderType> { + protected ExtendableBuilder(MessageType defaultInstance) { + super(defaultInstance); + + // TODO(dweis): This is kind of an unnecessary clone since we construct a + // new instance in the parent constructor which makes the extensions + // immutable. This extra allocation shouldn't matter in practice + // though. + instance.extensions = instance.extensions.clone(); + } + + // For immutable message conversion. + void internalSetExtensionSet(FieldSet<ExtensionDescriptor> extensions) { + copyOnWrite(); + instance.extensions = extensions; + } + + // @Override (Java 1.6 override semantics, but we must support 1.5) + protected void copyOnWrite() { + if (!isBuilt) { + return; + } + + super.copyOnWrite(); + instance.extensions = instance.extensions.clone(); + } + + // @Override (Java 1.6 override semantics, but we must support 1.5) + public final MessageType buildPartial() { + if (isBuilt) { + return instance; + } + + instance.extensions.makeImmutable(); + return super.buildPartial(); + } + + private void verifyExtensionContainingType( + final GeneratedExtension<MessageType, ?> extension) { + if (extension.getContainingTypeDefaultInstance() != + getDefaultInstanceForType()) { + // This can only happen if someone uses unchecked operations. + throw new IllegalArgumentException( + "This extension is for a different message type. Please make " + + "sure that you are not suppressing any generics type warnings."); + } + } + + /** Check if a singular extension is present. */ + //@Override (Java 1.6 override semantics, but we must support 1.5) + public final <Type> boolean hasExtension( + final ExtensionLite<MessageType, Type> extension) { + return instance.hasExtension(extension); + } + + /** Get the number of elements in a repeated extension. */ + //@Override (Java 1.6 override semantics, but we must support 1.5) + public final <Type> int getExtensionCount( + final ExtensionLite<MessageType, List<Type>> extension) { + return instance.getExtensionCount(extension); + } + + /** Get the value of an extension. */ + //@Override (Java 1.6 override semantics, but we must support 1.5) + @SuppressWarnings("unchecked") + public final <Type> Type getExtension( + final ExtensionLite<MessageType, Type> extension) { + return instance.getExtension(extension); + } + + /** Get one element of a repeated extension. */ + @SuppressWarnings("unchecked") + //@Override (Java 1.6 override semantics, but we must support 1.5) + public final <Type> Type getExtension( + final ExtensionLite<MessageType, List<Type>> extension, + final int index) { + return instance.getExtension(extension, index); + } + + // 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() { + return super.clone(); + } + + /** Set the value of an extension. */ + public final <Type> BuilderType setExtension( + final ExtensionLite<MessageType, Type> extension, + final Type value) { + GeneratedExtension<MessageType, Type> extensionLite = + checkIsLite(extension); + + verifyExtensionContainingType(extensionLite); + copyOnWrite(); + instance.extensions.setField(extensionLite.descriptor, extensionLite.toFieldSetType(value)); + return (BuilderType) this; + } + + /** Set the value of one element of a repeated extension. */ + public final <Type> BuilderType setExtension( + final ExtensionLite<MessageType, List<Type>> extension, + final int index, final Type value) { + GeneratedExtension<MessageType, List<Type>> extensionLite = + checkIsLite(extension); + + verifyExtensionContainingType(extensionLite); + copyOnWrite(); + instance.extensions.setRepeatedField( + extensionLite.descriptor, index, extensionLite.singularToFieldSetType(value)); + return (BuilderType) this; + } + + /** Append a value to a repeated extension. */ + public final <Type> BuilderType addExtension( + final ExtensionLite<MessageType, List<Type>> extension, + final Type value) { + GeneratedExtension<MessageType, List<Type>> extensionLite = + checkIsLite(extension); + + verifyExtensionContainingType(extensionLite); + copyOnWrite(); + instance.extensions.addRepeatedField( + extensionLite.descriptor, extensionLite.singularToFieldSetType(value)); + return (BuilderType) this; + } + + /** Clear an extension. */ + public final <Type> BuilderType clearExtension( + final ExtensionLite<MessageType, ?> extension) { + GeneratedExtension<MessageType, ?> extensionLite = checkIsLite(extension); + + verifyExtensionContainingType(extensionLite); + copyOnWrite(); + instance.extensions.clearField(extensionLite.descriptor); + return (BuilderType) this; + } + } + + // ----------------------------------------------------------------- + + /** For use by generated code only. */ + public static <ContainingType extends MessageLite, Type> + GeneratedExtension<ContainingType, Type> + newSingularGeneratedExtension( + final ContainingType containingTypeDefaultInstance, + final Type defaultValue, + final MessageLite messageDefaultInstance, + final Internal.EnumLiteMap<?> enumTypeMap, + final int number, + final WireFormat.FieldType type, + final Class singularType) { + return new GeneratedExtension<ContainingType, Type>( + containingTypeDefaultInstance, + defaultValue, + messageDefaultInstance, + new ExtensionDescriptor(enumTypeMap, number, type, + false /* isRepeated */, + false /* isPacked */), + singularType); + } + + /** For use by generated code only. */ + public static <ContainingType extends MessageLite, Type> + GeneratedExtension<ContainingType, Type> + newRepeatedGeneratedExtension( + final ContainingType containingTypeDefaultInstance, + final MessageLite messageDefaultInstance, + final Internal.EnumLiteMap<?> enumTypeMap, + final int number, + final WireFormat.FieldType type, + final boolean isPacked, + final Class singularType) { + @SuppressWarnings("unchecked") // Subclasses ensure Type is a List + Type emptyList = (Type) Collections.emptyList(); + return new GeneratedExtension<ContainingType, Type>( + containingTypeDefaultInstance, + emptyList, + messageDefaultInstance, + new ExtensionDescriptor( + enumTypeMap, number, type, true /* isRepeated */, isPacked), + singularType); + } + + static final class ExtensionDescriptor + implements FieldSet.FieldDescriptorLite< + ExtensionDescriptor> { + ExtensionDescriptor( + final Internal.EnumLiteMap<?> enumTypeMap, + final int number, + final WireFormat.FieldType type, + final boolean isRepeated, + final boolean isPacked) { + this.enumTypeMap = enumTypeMap; + this.number = number; + this.type = type; + this.isRepeated = isRepeated; + this.isPacked = isPacked; + } + + final Internal.EnumLiteMap<?> enumTypeMap; + final int number; + final WireFormat.FieldType type; + final boolean isRepeated; + final boolean isPacked; + + public int getNumber() { + return number; + } + + public WireFormat.FieldType getLiteType() { + return type; + } + + public WireFormat.JavaType getLiteJavaType() { + return type.getJavaType(); + } + + public boolean isRepeated() { + return isRepeated; + } + + public boolean isPacked() { + return isPacked; + } + + public Internal.EnumLiteMap<?> getEnumType() { + return enumTypeMap; + } + + @SuppressWarnings("unchecked") + public MessageLite.Builder internalMergeFrom( + MessageLite.Builder to, MessageLite from) { + return ((Builder) to).mergeFrom((GeneratedMessageLite) from); + } + + + public int compareTo(ExtensionDescriptor other) { + return number - other.number; + } + } + + // ================================================================= + + /** Calls Class.getMethod and throws a RuntimeException if it fails. */ + @SuppressWarnings("unchecked") + static Method getMethodOrDie(Class clazz, String name, Class... params) { + try { + return clazz.getMethod(name, params); + } catch (NoSuchMethodException e) { + throw new RuntimeException( + "Generated message class \"" + clazz.getName() + + "\" missing method \"" + name + "\".", e); + } + } + + /** Calls invoke and throws a RuntimeException if it fails. */ + static Object invokeOrDie(Method method, Object object, Object... params) { + try { + return method.invoke(object, params); + } catch (IllegalAccessException e) { + throw new RuntimeException( + "Couldn't use Java reflection to implement protocol message " + + "reflection.", e); + } catch (InvocationTargetException e) { + final Throwable cause = e.getCause(); + if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } else if (cause instanceof Error) { + throw (Error) cause; + } else { + throw new RuntimeException( + "Unexpected exception thrown by generated accessor method.", cause); + } + } + } + + /** + * Lite equivalent to {@link GeneratedMessage.GeneratedExtension}. + * + * Users should ignore the contents of this class and only use objects of + * this type as parameters to extension accessors and ExtensionRegistry.add(). + */ + public static class GeneratedExtension< + ContainingType extends MessageLite, Type> + extends ExtensionLite<ContainingType, Type> { + + /** + * Create a new instance with the given parameters. + * + * The last parameter {@code singularType} is only needed for enum types. + * We store integer values for enum types in a {@link ExtendableMessage} + * and use Java reflection to convert an integer value back into a concrete + * enum object. + */ + GeneratedExtension( + final ContainingType containingTypeDefaultInstance, + final Type defaultValue, + final MessageLite messageDefaultInstance, + final ExtensionDescriptor descriptor, + final Class singularType) { + // Defensive checks to verify the correct initialization order of + // GeneratedExtensions and their related GeneratedMessages. + if (containingTypeDefaultInstance == null) { + throw new IllegalArgumentException( + "Null containingTypeDefaultInstance"); + } + if (descriptor.getLiteType() == WireFormat.FieldType.MESSAGE && + messageDefaultInstance == null) { + throw new IllegalArgumentException( + "Null messageDefaultInstance"); + } + this.containingTypeDefaultInstance = containingTypeDefaultInstance; + this.defaultValue = defaultValue; + this.messageDefaultInstance = messageDefaultInstance; + this.descriptor = descriptor; + } + + final ContainingType containingTypeDefaultInstance; + final Type defaultValue; + final MessageLite messageDefaultInstance; + final ExtensionDescriptor descriptor; + + /** + * Default instance of the type being extended, used to identify that type. + */ + public ContainingType getContainingTypeDefaultInstance() { + return containingTypeDefaultInstance; + } + + /** Get the field number. */ + public int getNumber() { + return descriptor.getNumber(); + } + + + /** + * If the extension is an embedded message or group, returns the default + * instance of the message. + */ + public MessageLite getMessageDefaultInstance() { + return messageDefaultInstance; + } + + @SuppressWarnings("unchecked") + Object fromFieldSetType(final Object value) { + if (descriptor.isRepeated()) { + if (descriptor.getLiteJavaType() == WireFormat.JavaType.ENUM) { + final List result = new ArrayList(); + for (final Object element : (List) value) { + result.add(singularFromFieldSetType(element)); + } + return result; + } else { + return value; + } + } else { + return singularFromFieldSetType(value); + } + } + + Object singularFromFieldSetType(final Object value) { + if (descriptor.getLiteJavaType() == WireFormat.JavaType.ENUM) { + return descriptor.enumTypeMap.findValueByNumber((Integer) value); + } else { + return value; + } + } + + @SuppressWarnings("unchecked") + Object toFieldSetType(final Object value) { + if (descriptor.isRepeated()) { + if (descriptor.getLiteJavaType() == WireFormat.JavaType.ENUM) { + final List result = new ArrayList(); + for (final Object element : (List) value) { + result.add(singularToFieldSetType(element)); + } + return result; + } else { + return value; + } + } else { + return singularToFieldSetType(value); + } + } + + Object singularToFieldSetType(final Object value) { + if (descriptor.getLiteJavaType() == WireFormat.JavaType.ENUM) { + return ((Internal.EnumLite) value).getNumber(); + } else { + return value; + } + } + + public FieldType getLiteType() { + return descriptor.getLiteType(); + } + + public boolean isRepeated() { + return descriptor.isRepeated; + } + + public Type getDefaultValue() { + return defaultValue; + } + } + + /** + * A serialized (serializable) form of the generated message. Stores the + * message as a class name and a byte array. + */ + static final class SerializedForm implements Serializable { + private static final long serialVersionUID = 0L; + + private final String messageClassName; + private final byte[] asBytes; + + /** + * Creates the serialized form by calling {@link com.google.protobuf.MessageLite#toByteArray}. + * @param regularForm the message to serialize + */ + SerializedForm(MessageLite regularForm) { + messageClassName = regularForm.getClass().getName(); + asBytes = regularForm.toByteArray(); + } + + /** + * When read from an ObjectInputStream, this method converts this object + * back to the regular form. Part of Java's serialization magic. + * @return a GeneratedMessage of the type that was serialized + */ + @SuppressWarnings("unchecked") + protected Object readResolve() throws ObjectStreamException { + try { + Class<?> messageClass = Class.forName(messageClassName); + java.lang.reflect.Field defaultInstanceField = + messageClass.getDeclaredField("DEFAULT_INSTANCE"); + defaultInstanceField.setAccessible(true); + MessageLite defaultInstance = (MessageLite) defaultInstanceField.get(null); + return defaultInstance.newBuilderForType() + .mergeFrom(asBytes) + .buildPartial(); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Unable to find proto buffer class: " + messageClassName, e); + } catch (NoSuchFieldException e) { + throw new RuntimeException("Unable to find DEFAULT_INSTANCE in " + messageClassName, e); + } catch (SecurityException e) { + throw new RuntimeException("Unable to call DEFAULT_INSTANCE in " + messageClassName, e); + } catch (IllegalAccessException e) { + throw new RuntimeException("Unable to call parsePartialFrom", e); + } catch (InvalidProtocolBufferException e) { + throw new RuntimeException("Unable to understand proto buffer", e); + } + } + } + + /** + * Replaces this object in the output stream with a serialized form. + * Part of Java's serialization magic. Generated sub-classes must override + * this method by calling {@code return super.writeReplace();} + * @return a SerializedForm of this message + */ + 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 < + MessageType extends ExtendableMessage<MessageType, BuilderType>, + BuilderType extends ExtendableBuilder<MessageType, BuilderType>, + T> + GeneratedExtension<MessageType, T> checkIsLite( + ExtensionLite<MessageType, T> extension) { + if (!extension.isLite()) { + throw new IllegalArgumentException("Expected a lite extension."); + } + + return (GeneratedExtension<MessageType, T>) extension; + } + + /** + * A static helper method for checking if a message is initialized, optionally memoizing. + * <p> + * For use by generated code only. + */ + protected static final <T extends GeneratedMessageLite<T, ?>> boolean isInitialized( + T message, boolean shouldMemoize) { + return message.dynamicMethod(MethodToInvoke.IS_INITIALIZED, shouldMemoize) != null; + } + + protected static final <T extends GeneratedMessageLite<T, ?>> void makeImmutable(T message) { + message.dynamicMethod(MethodToInvoke.MAKE_IMMUTABLE); + } + + /** + * A static helper method for parsing a partial from input using the extension registry and the + * instance. + */ + static <T extends GeneratedMessageLite<T, ?>> T parsePartialFrom( + T instance, CodedInputStream input, ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + try { + return (T) instance.dynamicMethod( + MethodToInvoke.PARSE_PARTIAL_FROM, input, extensionRegistry); + } catch (RuntimeException e) { + if (e.getCause() instanceof InvalidProtocolBufferException) { + throw (InvalidProtocolBufferException) e.getCause(); + } + throw e; + } + } + + /** + * A {@link Parser} implementation that delegates to the default instance. + * <p> + * For use by generated code only. + */ + protected static class DefaultInstanceBasedParser<T extends GeneratedMessageLite<T, ?>> + extends AbstractParser<T> { + + private T defaultInstance; + + public DefaultInstanceBasedParser(T defaultInstance) { + this.defaultInstance = defaultInstance; + } + + @Override + public T parsePartialFrom(CodedInputStream input, ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + return GeneratedMessageLite.parsePartialFrom(defaultInstance, input, extensionRegistry); + } + } + + protected static IntList newIntList() { + return new IntArrayList(); + } + + protected static IntList newIntListWithCapacity(int capacity) { + return new IntArrayList(capacity); + } + + protected static IntList newIntList(List<Integer> toCopy) { + return new IntArrayList(toCopy); + } + + protected static IntList emptyIntList() { + return IntArrayList.emptyList(); + } + + protected static LongList newLongList() { + return new LongArrayList(); + } + + protected static LongList newLongListWithCapacity(int capacity) { + return new LongArrayList(capacity); + } + + protected static LongList newLongList(List<Long> toCopy) { + return new LongArrayList(toCopy); + } + + protected static LongList emptyLongList() { + return LongArrayList.emptyList(); + } + + protected static FloatList newFloatList() { + return new FloatArrayList(); + } + + protected static FloatList newFloatListWithCapacity(int capacity) { + return new FloatArrayList(capacity); + } + + protected static FloatList newFloatList(List<Float> toCopy) { + return new FloatArrayList(toCopy); + } + + protected static FloatList emptyFloatList() { + return FloatArrayList.emptyList(); + } + + protected static DoubleList newDoubleList() { + return new DoubleArrayList(); + } + + protected static DoubleList newDoubleListWithCapacity(int capacity) { + return new DoubleArrayList(capacity); + } + + protected static DoubleList newDoubleList(List<Double> toCopy) { + return new DoubleArrayList(toCopy); + } + + protected static DoubleList emptyDoubleList() { + return DoubleArrayList.emptyList(); + } + + protected static BooleanList newBooleanList() { + return new BooleanArrayList(); + } + + protected static BooleanList newBooleanListWithCapacity(int capacity) { + return new BooleanArrayList(capacity); + } + + protected static BooleanList newBooleanList(List<Boolean> toCopy) { + return new BooleanArrayList(toCopy); + } + + protected static BooleanList emptyBooleanList() { + return BooleanArrayList.emptyList(); + } + + protected static <E> ProtobufList<E> newProtobufList() { + return new ProtobufArrayList<E>(); + } + + protected static <E> ProtobufList<E> newProtobufList(List<E> toCopy) { + return new ProtobufArrayList<E>(toCopy); + } + + protected static <E> ProtobufList<E> newProtobufListWithCapacity(int capacity) { + return new ProtobufArrayList<E>(capacity); + } + + protected static <E> ProtobufList<E> emptyProtobufList() { + return ProtobufArrayList.emptyList(); + } + + protected static LazyStringArrayList emptyLazyStringArrayList() { + return LazyStringArrayList.emptyList(); + } +} diff --git a/java/core/src/main/java/com/google/protobuf/IntArrayList.java b/java/core/src/main/java/com/google/protobuf/IntArrayList.java new file mode 100644 index 00000000..f4e68ed8 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/IntArrayList.java @@ -0,0 +1,249 @@ +// 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.Internal.IntList; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.RandomAccess; + +/** + * An implementation of {@link IntList} on top of a primitive array. + * + * @author dweis@google.com (Daniel Weis) + */ +final class IntArrayList extends AbstractProtobufList<Integer> implements IntList, RandomAccess { + + private static final int DEFAULT_CAPACITY = 10; + + private static final IntArrayList EMPTY_LIST = new IntArrayList(); + static { + EMPTY_LIST.makeImmutable(); + } + + public static IntArrayList emptyList() { + return EMPTY_LIST; + } + + /** + * The backing store for the list. + */ + private int[] array; + + /** + * The size of the list distinct from the length of the array. That is, it is the number of + * elements set in the list. + */ + private int size; + + /** + * Constructs a new mutable {@code IntArrayList} with default capacity. + */ + IntArrayList() { + this(DEFAULT_CAPACITY); + } + + /** + * Constructs a new mutable {@code IntArrayList} with the provided capacity. + */ + IntArrayList(int capacity) { + array = new int[capacity]; + size = 0; + } + + /** + * Constructs a new mutable {@code IntArrayList} containing the same elements as {@code other}. + */ + IntArrayList(List<Integer> other) { + if (other instanceof IntArrayList) { + IntArrayList list = (IntArrayList) other; + array = list.array.clone(); + size = list.size; + } else { + size = other.size(); + array = new int[size]; + for (int i = 0; i < size; i++) { + array[i] = other.get(i); + } + } + } + + @Override + public Integer get(int index) { + return getInt(index); + } + + @Override + public int getInt(int index) { + ensureIndexInRange(index); + return array[index]; + } + + @Override + public int size() { + return size; + } + + @Override + public Integer set(int index, Integer element) { + return setInt(index, element); + } + + @Override + public int setInt(int index, int element) { + ensureIsMutable(); + ensureIndexInRange(index); + int previousValue = array[index]; + array[index] = element; + return previousValue; + } + + @Override + public void add(int index, Integer element) { + addInt(index, element); + } + + /** + * Like {@link #add(Integer)} but more efficient in that it doesn't box the element. + */ + @Override + public void addInt(int element) { + addInt(size, element); + } + + /** + * Like {@link #add(int, Integer)} but more efficient in that it doesn't box the element. + */ + private void addInt(int index, int element) { + ensureIsMutable(); + if (index < 0 || index > size) { + throw new IndexOutOfBoundsException(makeOutOfBoundsExceptionMessage(index)); + } + + if (size < array.length) { + // Shift everything over to make room + System.arraycopy(array, index, array, index + 1, size - index); + } else { + // Resize to 1.5x the size + int length = ((size * 3) / 2) + 1; + int[] newArray = new int[length]; + + // Copy the first part directly + System.arraycopy(array, 0, newArray, 0, index); + + // Copy the rest shifted over by one to make room + System.arraycopy(array, index, newArray, index + 1, size - index); + array = newArray; + } + + array[index] = element; + size++; + modCount++; + } + + @Override + public boolean addAll(Collection<? extends Integer> collection) { + ensureIsMutable(); + + if (collection == null) { + throw new NullPointerException(); + } + + // We specialize when adding another IntArrayList to avoid boxing elements. + if (!(collection instanceof IntArrayList)) { + return super.addAll(collection); + } + + IntArrayList list = (IntArrayList) collection; + if (list.size == 0) { + return false; + } + + int overflow = Integer.MAX_VALUE - size; + if (overflow < list.size) { + // We can't actually represent a list this large. + throw new OutOfMemoryError(); + } + + int newSize = size + list.size; + if (newSize > array.length) { + array = Arrays.copyOf(array, newSize); + } + + System.arraycopy(list.array, 0, array, size, list.size); + size = newSize; + modCount++; + return true; + } + + @Override + public boolean remove(Object o) { + ensureIsMutable(); + for (int i = 0; i < size; i++) { + if (o.equals(array[i])) { + System.arraycopy(array, i + 1, array, i, size - i); + size--; + modCount++; + return true; + } + } + return false; + } + + @Override + public Integer remove(int index) { + ensureIsMutable(); + ensureIndexInRange(index); + int value = array[index]; + System.arraycopy(array, index + 1, array, index, size - index); + size--; + modCount++; + return value; + } + + /** + * Ensures that the provided {@code index} is within the range of {@code [0, size]}. Throws an + * {@link IndexOutOfBoundsException} if it is not. + * + * @param index the index to verify is in range + */ + private void ensureIndexInRange(int index) { + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException(makeOutOfBoundsExceptionMessage(index)); + } + } + + private String makeOutOfBoundsExceptionMessage(int index) { + return "Index:" + index + ", Size:" + size; + } +} diff --git a/java/core/src/main/java/com/google/protobuf/Internal.java b/java/core/src/main/java/com/google/protobuf/Internal.java new file mode 100644 index 00000000..11901998 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/Internal.java @@ -0,0 +1,677 @@ +// 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; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +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 + * library and generated message implementations. They are public only because + * those generated messages do not reside in the {@code protobuf} package. + * Others should not use this class directly. + * + * @author kenton@google.com (Kenton Varda) + */ +public class Internal { + + protected static final Charset UTF_8 = Charset.forName("UTF-8"); + protected static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); + + /** + * Helper called by generated code to construct default values for string + * fields. + * <p> + * The protocol compiler does not actually contain a UTF-8 decoder -- it + * just pushes UTF-8-encoded text around without touching it. The one place + * where this presents a problem is when generating Java string literals. + * Unicode characters in the string literal would normally need to be encoded + * using a Unicode escape sequence, which would require decoding them. + * To get around this, protoc instead embeds the UTF-8 bytes into the + * generated code and leaves it to the runtime library to decode them. + * <p> + * It gets worse, though. If protoc just generated a byte array, like: + * new byte[] {0x12, 0x34, 0x56, 0x78} + * Java actually generates *code* which allocates an array and then fills + * in each value. This is much less efficient than just embedding the bytes + * directly into the bytecode. To get around this, we need another + * work-around. String literals are embedded directly, so protoc actually + * generates a string literal corresponding to the bytes. The easiest way + * to do this is to use the ISO-8859-1 character set, which corresponds to + * the first 256 characters of the Unicode range. Protoc can then use + * good old CEscape to generate the string. + * <p> + * So we have a string literal which represents a set of bytes which + * represents another string. This function -- stringDefaultValue -- + * converts from the generated string to the string we actually want. The + * generated code calls this automatically. + */ + public static String stringDefaultValue(String bytes) { + return new String(bytes.getBytes(ISO_8859_1), UTF_8); + } + + /** + * Helper called by generated code to construct default values for bytes + * fields. + * <p> + * This is a lot like {@link #stringDefaultValue}, but for bytes fields. + * In this case we only need the second of the two hacks -- allowing us to + * embed raw bytes as a string literal with ISO-8859-1 encoding. + */ + public static ByteString bytesDefaultValue(String bytes) { + return ByteString.copyFrom(bytes.getBytes(ISO_8859_1)); + } + /** + * Helper called by generated code to construct default values for bytes + * fields. + * <p> + * This is like {@link #bytesDefaultValue}, but returns a byte array. + */ + public static byte[] byteArrayDefaultValue(String bytes) { + return bytes.getBytes(ISO_8859_1); + } + + /** + * Helper called by generated code to construct default values for bytes + * fields. + * <p> + * This is like {@link #bytesDefaultValue}, but returns a ByteBuffer. + */ + public static ByteBuffer byteBufferDefaultValue(String bytes) { + return ByteBuffer.wrap(byteArrayDefaultValue(bytes)); + } + + /** + * Create a new ByteBuffer and copy all the content of {@code source} + * ByteBuffer to the new ByteBuffer. The new ByteBuffer's limit and + * capacity will be source.capacity(), and its position will be 0. + * Note that the state of {@code source} ByteBuffer won't be changed. + */ + public static ByteBuffer copyByteBuffer(ByteBuffer source) { + // Make a duplicate of the source ByteBuffer and read data from the + // duplicate. This is to avoid affecting the source ByteBuffer's state. + ByteBuffer temp = source.duplicate(); + // We want to copy all the data in the source ByteBuffer, not just the + // remaining bytes. + temp.clear(); + ByteBuffer result = ByteBuffer.allocate(temp.capacity()); + result.put(temp); + result.clear(); + return result; + } + + /** + * Helper called by generated code to determine if a byte array is a valid + * UTF-8 encoded string such that the original bytes can be converted to + * a String object and then back to a byte array round tripping the bytes + * without loss. More precisely, returns {@code true} whenever: + * <pre> {@code + * Arrays.equals(byteString.toByteArray(), + * new String(byteString.toByteArray(), "UTF-8").getBytes("UTF-8")) + * }</pre> + * + * <p>This method rejects "overlong" byte sequences, as well as + * 3-byte sequences that would map to a surrogate character, in + * accordance with the restricted definition of UTF-8 introduced in + * Unicode 3.1. Note that the UTF-8 decoder included in Oracle's + * JDK has been modified to also reject "overlong" byte sequences, + * but currently (2011) still accepts 3-byte surrogate character + * byte sequences. + * + * <p>See the Unicode Standard,<br> + * Table 3-6. <em>UTF-8 Bit Distribution</em>,<br> + * Table 3-7. <em>Well Formed UTF-8 Byte Sequences</em>. + * + * <p>As of 2011-02, this method simply returns the result of {@link + * ByteString#isValidUtf8()}. Calling that method directly is preferred. + * + * @param byteString the string to check + * @return whether the byte array is round trippable + */ + public static boolean isValidUtf8(ByteString byteString) { + return byteString.isValidUtf8(); + } + + /** + * Like {@link #isValidUtf8(ByteString)} but for byte arrays. + */ + public static boolean isValidUtf8(byte[] byteArray) { + return Utf8.isValidUtf8(byteArray); + } + + /** + * Helper method to get the UTF-8 bytes of a string. + */ + public static byte[] toByteArray(String value) { + return value.getBytes(UTF_8); + } + + /** + * Helper method to convert a byte array to a string using UTF-8 encoding. + */ + public static String toStringUtf8(byte[] bytes) { + return new String(bytes, UTF_8); + } + + /** + * Interface for an enum value or value descriptor, to be used in FieldSet. + * The lite library stores enum values directly in FieldSets but the full + * library stores EnumValueDescriptors in order to better support reflection. + */ + public interface EnumLite { + int getNumber(); + } + + /** + * Interface for an object which maps integers to {@link EnumLite}s. + * {@link Descriptors.EnumDescriptor} implements this interface by mapping + * numbers to {@link Descriptors.EnumValueDescriptor}s. Additionally, + * every generated enum type has a static method internalGetValueMap() which + * returns an implementation of this type that maps numbers to enum values. + */ + public interface EnumLiteMap<T extends EnumLite> { + T findValueByNumber(int number); + } + + /** + * Helper method for implementing {@link Message#hashCode()} for longs. + * @see Long#hashCode() + */ + public static int hashLong(long n) { + return (int) (n ^ (n >>> 32)); + } + + /** + * Helper method for implementing {@link Message#hashCode()} for + * booleans. + * @see Boolean#hashCode() + */ + public static int hashBoolean(boolean b) { + return b ? 1231 : 1237; + } + + /** + * Helper method for implementing {@link Message#hashCode()} for enums. + * <p> + * This is needed because {@link java.lang.Enum#hashCode()} is final, but we + * need to use the field number as the hash code to ensure compatibility + * between statically and dynamically generated enum objects. + */ + public static int hashEnum(EnumLite e) { + return e.getNumber(); + } + + /** + * Helper method for implementing {@link Message#hashCode()} for + * enum lists. + */ + public static int hashEnumList(List<? extends EnumLite> list) { + int hash = 1; + for (EnumLite e : list) { + hash = 31 * hash + hashEnum(e); + } + return hash; + } + + /** + * Helper method for implementing {@link Message#equals(Object)} for bytes field. + */ + public static boolean equals(List<byte[]> a, List<byte[]> b) { + if (a.size() != b.size()) return false; + for (int i = 0; i < a.size(); ++i) { + if (!Arrays.equals(a.get(i), b.get(i))) { + return false; + } + } + return true; + } + + /** + * Helper method for implementing {@link Message#hashCode()} for bytes field. + */ + public static int hashCode(List<byte[]> list) { + int hash = 1; + for (byte[] bytes : list) { + hash = 31 * hash + hashCode(bytes); + } + return hash; + } + + /** + * Helper method for implementing {@link Message#hashCode()} for bytes field. + */ + public static int hashCode(byte[] bytes) { + // The hash code for a byte array should be the same as the hash code for a + // ByteString with the same content. This is to ensure that the generated + // hashCode() method will return the same value as the pure reflection + // based hashCode() method. + return LiteralByteString.hashCode(bytes); + } + + /** + * Helper method for implementing {@link Message#equals(Object)} for bytes + * field. + */ + public static boolean equalsByteBuffer(ByteBuffer a, ByteBuffer b) { + if (a.capacity() != b.capacity()) { + return false; + } + // ByteBuffer.equals() will only compare the remaining bytes, but we want to + // compare all the content. + return a.duplicate().clear().equals(b.duplicate().clear()); + } + + /** + * Helper method for implementing {@link Message#equals(Object)} for bytes + * field. + */ + public static boolean equalsByteBuffer( + List<ByteBuffer> a, List<ByteBuffer> b) { + if (a.size() != b.size()) { + return false; + } + for (int i = 0; i < a.size(); ++i) { + if (!equalsByteBuffer(a.get(i), b.get(i))) { + return false; + } + } + return true; + } + + /** + * Helper method for implementing {@link Message#hashCode()} for bytes + * field. + */ + public static int hashCodeByteBuffer(List<ByteBuffer> list) { + int hash = 1; + for (ByteBuffer bytes : list) { + hash = 31 * hash + hashCodeByteBuffer(bytes); + } + return hash; + } + + private static final int DEFAULT_BUFFER_SIZE = 4096; + + /** + * Helper method for implementing {@link Message#hashCode()} for bytes + * field. + */ + public static int hashCodeByteBuffer(ByteBuffer bytes) { + if (bytes.hasArray()) { + // Fast path. + int h = LiteralByteString.hashCode(bytes.capacity(), bytes.array(), + bytes.arrayOffset(), bytes.capacity()); + return h == 0 ? 1 : h; + } else { + // Read the data into a temporary byte array before calculating the + // hash value. + final int bufferSize = bytes.capacity() > DEFAULT_BUFFER_SIZE + ? DEFAULT_BUFFER_SIZE : bytes.capacity(); + final byte[] buffer = new byte[bufferSize]; + final ByteBuffer duplicated = bytes.duplicate(); + duplicated.clear(); + int h = bytes.capacity(); + while (duplicated.remaining() > 0) { + final int length = duplicated.remaining() <= bufferSize ? + duplicated.remaining() : bufferSize; + duplicated.get(buffer, 0, length); + h = LiteralByteString.hashCode(h, buffer, 0, length); + } + return h == 0 ? 1 : h; + } + } + + @SuppressWarnings("unchecked") + public static <T extends MessageLite> T getDefaultInstance(Class<T> clazz) { + try { + Method method = clazz.getMethod("getDefaultInstance"); + return (T) method.invoke(method); + } catch (Exception e) { + throw new RuntimeException( + "Failed to get default instance for " + clazz, e); + } + } + + /** + * An empty byte array constant used in generated code. + */ + public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + + /** + * An empty byte array constant used in generated code. + */ + public static final ByteBuffer EMPTY_BYTE_BUFFER = + ByteBuffer.wrap(EMPTY_BYTE_ARRAY); + + /** An empty coded input stream constant used in generated code. */ + public static final CodedInputStream EMPTY_CODED_INPUT_STREAM = + CodedInputStream.newInstance(EMPTY_BYTE_ARRAY); + + + /** + * Provides an immutable view of {@code List<T>} around a {@code List<F>}. + * + * Protobuf internal. Used in protobuf generated code only. + */ + public static class ListAdapter<F, T> extends AbstractList<T> { + /** + * Convert individual elements of the List from F to T. + */ + public interface Converter<F, T> { + T convert(F from); + } + + private final List<F> fromList; + private final Converter<F, T> converter; + + public ListAdapter(List<F> fromList, Converter<F, T> 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 {@code Map<K, RealValue>} and provide a {@code Map<K, V>} + * interface. + */ + public static class MapAdapter<K, V, RealValue> extends AbstractMap<K, V> { + /** + * An interface used to convert between two types. + */ + public interface Converter<A, B> { + B doForward(A object); + A doBackward(B object); + } + + public static <T extends EnumLite> Converter<Integer, T> newEnumConverter( + final EnumLiteMap<T> enumMap, final T unrecognizedValue) { + return new Converter<Integer, T>() { + 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<K, RealValue> realMap; + private final Converter<RealValue, V> valueConverter; + + public MapAdapter(Map<K, RealValue> realMap, + Converter<RealValue, V> 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<java.util.Map.Entry<K, V>> entrySet() { + return new SetAdapter(realMap.entrySet()); + } + + private class SetAdapter extends AbstractSet<Map.Entry<K, V>> { + private final Set<Map.Entry<K, RealValue>> realSet; + public SetAdapter(Set<Map.Entry<K, RealValue>> realSet) { + this.realSet = realSet; + } + + @Override + public Iterator<java.util.Map.Entry<K, V>> iterator() { + return new IteratorAdapter(realSet.iterator()); + } + + @Override + public int size() { + return realSet.size(); + } + } + + private class IteratorAdapter implements Iterator<Map.Entry<K, V>> { + private final Iterator<Map.Entry<K, RealValue>> realIterator; + + public IteratorAdapter( + Iterator<Map.Entry<K, RealValue>> realIterator) { + this.realIterator = realIterator; + } + + @Override + public boolean hasNext() { + return realIterator.hasNext(); + } + + @Override + public java.util.Map.Entry<K, V> next() { + return new EntryAdapter(realIterator.next()); + } + + @Override + public void remove() { + realIterator.remove(); + } + } + + private class EntryAdapter implements Map.Entry<K, V> { + private final Map.Entry<K, RealValue> realEntry; + + public EntryAdapter(Map.Entry<K, RealValue> 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); + } + } + } + + /** + * Extends {@link List} to add the capability to make the list immutable and inspect if it is + * modifiable. + */ + public static interface ProtobufList<E> extends List<E> { + + /** + * Makes this list immutable. All subsequent modifications will throw an + * {@link UnsupportedOperationException}. + */ + void makeImmutable(); + + /** + * Returns whether this list can be modified via the publicly accessible {@link List} methods. + */ + boolean isModifiable(); + } + + /** + * A {@link java.util.List} implementation that avoids boxing the elements into Integers if + * possible. Does not support null elements. + */ + public static interface IntList extends ProtobufList<Integer> { + + /** + * Like {@link #get(int)} but more efficient in that it doesn't box the returned value. + */ + int getInt(int index); + + /** + * Like {@link #add(Object)} but more efficient in that it doesn't box the element. + */ + void addInt(int element); + + /** + * Like {@link #set(int, Object)} but more efficient in that it doesn't box the element. + */ + int setInt(int index, int element); + } + + /** + * A {@link java.util.List} implementation that avoids boxing the elements into Booleans if + * possible. Does not support null elements. + */ + public static interface BooleanList extends ProtobufList<Boolean> { + + /** + * Like {@link #get(int)} but more efficient in that it doesn't box the returned value. + */ + boolean getBoolean(int index); + + /** + * Like {@link #add(Object)} but more efficient in that it doesn't box the element. + */ + void addBoolean(boolean element); + + /** + * Like {@link #set(int, Object)} but more efficient in that it doesn't box the element. + */ + boolean setBoolean(int index, boolean element); + } + + /** + * A {@link java.util.List} implementation that avoids boxing the elements into Longs if + * possible. Does not support null elements. + */ + public static interface LongList extends ProtobufList<Long> { + + /** + * Like {@link #get(int)} but more efficient in that it doesn't box the returned value. + */ + long getLong(int index); + + /** + * Like {@link #add(Object)} but more efficient in that it doesn't box the element. + */ + void addLong(long element); + + /** + * Like {@link #set(int, Object)} but more efficient in that it doesn't box the element. + */ + long setLong(int index, long element); + } + + /** + * A {@link java.util.List} implementation that avoids boxing the elements into Doubles if + * possible. Does not support null elements. + */ + public static interface DoubleList extends ProtobufList<Double> { + + /** + * Like {@link #get(int)} but more efficient in that it doesn't box the returned value. + */ + double getDouble(int index); + + /** + * Like {@link #add(Object)} but more efficient in that it doesn't box the element. + */ + void addDouble(double element); + + /** + * Like {@link #set(int, Object)} but more efficient in that it doesn't box the element. + */ + double setDouble(int index, double element); + } + + /** + * A {@link java.util.List} implementation that avoids boxing the elements into Floats if + * possible. Does not support null elements. + */ + public static interface FloatList extends ProtobufList<Float> { + + /** + * Like {@link #get(int)} but more efficient in that it doesn't box the returned value. + */ + float getFloat(int index); + + /** + * Like {@link #add(Object)} but more efficient in that it doesn't box the element. + */ + void addFloat(float element); + + /** + * Like {@link #set(int, Object)} but more efficient in that it doesn't box the element. + */ + float setFloat(int index, float element); + } +} diff --git a/java/core/src/main/java/com/google/protobuf/InvalidProtocolBufferException.java b/java/core/src/main/java/com/google/protobuf/InvalidProtocolBufferException.java new file mode 100644 index 00000000..0a761052 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/InvalidProtocolBufferException.java @@ -0,0 +1,122 @@ +// 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; + +/** + * Thrown when a protocol message being parsed is invalid in some way, + * e.g. it contains a malformed varint or a negative byte length. + * + * @author kenton@google.com Kenton Varda + */ +public class InvalidProtocolBufferException extends IOException { + private static final long serialVersionUID = -1616151763072450476L; + private MessageLite unfinishedMessage = null; + + public InvalidProtocolBufferException(final String description) { + super(description); + } + + /** + * Attaches an unfinished message to the exception to support best-effort + * parsing in {@code Parser} interface. + * + * @return this + */ + public InvalidProtocolBufferException setUnfinishedMessage( + MessageLite unfinishedMessage) { + this.unfinishedMessage = unfinishedMessage; + return this; + } + + /** + * Returns the unfinished message attached to the exception, or null if + * no message is attached. + */ + public MessageLite getUnfinishedMessage() { + return unfinishedMessage; + } + + static InvalidProtocolBufferException truncatedMessage() { + return new InvalidProtocolBufferException( + "While parsing a protocol message, the input ended unexpectedly " + + "in the middle of a field. This could mean either that the " + + "input has been truncated or that an embedded message " + + "misreported its own length."); + } + + static InvalidProtocolBufferException negativeSize() { + return new InvalidProtocolBufferException( + "CodedInputStream encountered an embedded string or message " + + "which claimed to have negative size."); + } + + static InvalidProtocolBufferException malformedVarint() { + return new InvalidProtocolBufferException( + "CodedInputStream encountered a malformed varint."); + } + + static InvalidProtocolBufferException invalidTag() { + return new InvalidProtocolBufferException( + "Protocol message contained an invalid tag (zero)."); + } + + static InvalidProtocolBufferException invalidEndTag() { + return new InvalidProtocolBufferException( + "Protocol message end-group tag did not match expected tag."); + } + + static InvalidProtocolBufferException invalidWireType() { + return new InvalidProtocolBufferException( + "Protocol message tag had invalid wire type."); + } + + static InvalidProtocolBufferException recursionLimitExceeded() { + return new InvalidProtocolBufferException( + "Protocol message had too many levels of nesting. May be malicious. " + + "Use CodedInputStream.setRecursionLimit() to increase the depth limit."); + } + + static InvalidProtocolBufferException sizeLimitExceeded() { + return new InvalidProtocolBufferException( + "Protocol message was too large. May be malicious. " + + "Use CodedInputStream.setSizeLimit() to increase the size limit."); + } + + static InvalidProtocolBufferException parseFailure() { + return new InvalidProtocolBufferException("Failed to parse the message."); + } + + static InvalidProtocolBufferException invalidUtf8() { + return new InvalidProtocolBufferException("Protocol message had invalid UTF-8."); + } +} diff --git a/java/core/src/main/java/com/google/protobuf/LazyField.java b/java/core/src/main/java/com/google/protobuf/LazyField.java new file mode 100644 index 00000000..5e0a485c --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/LazyField.java @@ -0,0 +1,154 @@ +// 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.Iterator; +import java.util.Map.Entry; + +/** + * LazyField encapsulates the logic of lazily parsing message fields. It stores + * the message in a ByteString initially and then parse it on-demand. + * + * Most of key methods are implemented in {@link LazyFieldLite} but this class + * can contain default instance of the message to provide {@code hashCode()}, + * {@code equals()} and {@code toString()}. + * + * @author xiangl@google.com (Xiang Li) + */ +public class LazyField extends LazyFieldLite { + + /** + * Carry a message's default instance which is used by {@code hashCode()}, {@code equals()} and + * {@code toString()}. + */ + private final MessageLite defaultInstance; + + public LazyField(MessageLite defaultInstance, + ExtensionRegistryLite extensionRegistry, ByteString bytes) { + super(extensionRegistry, bytes); + + this.defaultInstance = defaultInstance; + } + + @Override + public boolean containsDefaultInstance() { + return super.containsDefaultInstance() || value == defaultInstance; + } + + public MessageLite getValue() { + return getValue(defaultInstance); + } + + @Override + public int hashCode() { + return getValue().hashCode(); + } + + @Override + public boolean equals(Object obj) { + return getValue().equals(obj); + } + + @Override + public String toString() { + return getValue().toString(); + } + + // ==================================================== + + /** + * LazyEntry and LazyIterator are used to encapsulate the LazyField, when + * users iterate all fields from FieldSet. + */ + static class LazyEntry<K> implements Entry<K, Object> { + private Entry<K, LazyField> entry; + + private LazyEntry(Entry<K, LazyField> entry) { + this.entry = entry; + } + + // @Override + public K getKey() { + return entry.getKey(); + } + + // @Override + public Object getValue() { + LazyField field = entry.getValue(); + if (field == null) { + return null; + } + return field.getValue(); + } + + public LazyField getField() { + return entry.getValue(); + } + + // @Override + public Object setValue(Object value) { + if (!(value instanceof MessageLite)) { + throw new IllegalArgumentException( + "LazyField now only used for MessageSet, " + + "and the value of MessageSet must be an instance of MessageLite"); + } + return entry.getValue().setValue((MessageLite) value); + } + } + + static class LazyIterator<K> implements Iterator<Entry<K, Object>> { + private Iterator<Entry<K, Object>> iterator; + + public LazyIterator(Iterator<Entry<K, Object>> iterator) { + this.iterator = iterator; + } + + // @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @SuppressWarnings("unchecked") + // @Override + public Entry<K, Object> next() { + Entry<K, ?> entry = iterator.next(); + if (entry.getValue() instanceof LazyField) { + return new LazyEntry<K>((Entry<K, LazyField>) entry); + } + return (Entry<K, Object>) entry; + } + + // @Override + public void remove() { + iterator.remove(); + } + } +} diff --git a/java/core/src/main/java/com/google/protobuf/LazyFieldLite.java b/java/core/src/main/java/com/google/protobuf/LazyFieldLite.java new file mode 100644 index 00000000..eea1fe3c --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/LazyFieldLite.java @@ -0,0 +1,339 @@ +// 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; + +/** + * LazyFieldLite encapsulates the logic of lazily parsing message fields. It stores + * the message in a ByteString initially and then parse it on-demand. + * + * LazyField is thread-compatible e.g. concurrent read are safe, however, + * synchronizations are needed under read/write situations. + * + * This class is internal implementation detail, so you don't need to use it directly. + * + * @author xiangl@google.com (Xiang Li) + */ +public class LazyFieldLite { + private static final ExtensionRegistryLite EMPTY_REGISTRY = + ExtensionRegistryLite.getEmptyRegistry(); + + /** + * A delayed-parsed version of the bytes. When this is non-null then {@code extensionRegistry } is + * also non-null and {@code value} and {@code memoizedBytes} are null. + */ + private ByteString delayedBytes; + + /** + * An {@code ExtensionRegistryLite} for parsing bytes. It is non-null on a best-effort basis. It + * is only guaranteed to be non-null if this message was initialized using bytes and an + * {@code ExtensionRegistry}. If it directly had a value set then it will be null, unless it has + * been merged with another {@code LazyFieldLite} that had an {@code ExtensionRegistry}. + */ + private ExtensionRegistryLite extensionRegistry; + + /** + * The parsed value. When this is non-null then {@code delayedBytes} will be null. + */ + protected volatile MessageLite value; + + /** + * The memoized bytes for {@code value}. Will be null when {@code value} is null. + */ + private volatile ByteString memoizedBytes; + + /** + * Constructs a LazyFieldLite with bytes that will be parsed lazily. + */ + public LazyFieldLite(ExtensionRegistryLite extensionRegistry, ByteString bytes) { + checkArguments(extensionRegistry, bytes); + this.extensionRegistry = extensionRegistry; + this.delayedBytes = bytes; + } + + /** + * Constructs a LazyFieldLite with no contents, and no ability to parse extensions. + */ + public LazyFieldLite() { + } + + /** + * Constructs a LazyFieldLite instance with a value. The LazyFieldLite may not be able to parse + * the extensions in the value as it has no ExtensionRegistry. + */ + public static LazyFieldLite fromValue(MessageLite value) { + LazyFieldLite lf = new LazyFieldLite(); + lf.setValue(value); + return lf; + } + + /** + * Determines whether this LazyFieldLite instance represents the default instance of this type. + */ + public boolean containsDefaultInstance() { + return memoizedBytes == ByteString.EMPTY + || value == null && (delayedBytes == null || delayedBytes == ByteString.EMPTY); + } + + /** + * Clears the value state of this instance. + * + * <p>LazyField is not thread-safe for write access. Synchronizations are needed + * under read/write situations. + */ + public void clear() { + // Don't clear the ExtensionRegistry. It might prove useful later on when merging in another + // value, but there is no guarantee that it will contain all extensions that were directly set + // on the values that need to be merged. + delayedBytes = null; + value = null; + memoizedBytes = null; + } + + /** + * Overrides the contents of this LazyField. + * + * <p>LazyField is not thread-safe for write access. Synchronizations are needed + * under read/write situations. + */ + public void set(LazyFieldLite other) { + this.delayedBytes = other.delayedBytes; + this.value = other.value; + this.memoizedBytes = other.memoizedBytes; + // If the other LazyFieldLite was created by directly setting the value rather than first by + // parsing, then it will not have an extensionRegistry. In this case we hold on to the existing + // extensionRegistry, which has no guarantees that it has all the extensions that will be + // directly set on the value. + if (other.extensionRegistry != null) { + this.extensionRegistry = other.extensionRegistry; + } + } + + /** + * Returns message instance. It may do some thread-safe delayed parsing of bytes. + * + * @param defaultInstance its message's default instance. It's also used to get parser for the + * message type. + */ + public MessageLite getValue(MessageLite defaultInstance) { + ensureInitialized(defaultInstance); + return value; + } + + /** + * Sets the value of the instance and returns the old value without delay parsing anything. + * + * <p>LazyField is not thread-safe for write access. Synchronizations are needed + * under read/write situations. + */ + public MessageLite setValue(MessageLite value) { + MessageLite originalValue = this.value; + this.delayedBytes = null; + this.memoizedBytes = null; + this.value = value; + return originalValue; + } + + /** + * Merges another instance's contents. In some cases may drop some extensions if both fields + * contain data. If the other field has an {@code ExtensionRegistry} but this does not, then this + * field will copy over that {@code ExtensionRegistry}. + * + * <p>LazyField is not thread-safe for write access. Synchronizations are needed + * under read/write situations. + */ + public void merge(LazyFieldLite other) { + if (other.containsDefaultInstance()) { + return; + } + + if (this.containsDefaultInstance()) { + set(other); + return; + } + + // If the other field has an extension registry but this does not, copy over the other extension + // registry. + if (this.extensionRegistry == null) { + this.extensionRegistry = other.extensionRegistry; + } + + // In the case that both of them are not parsed we simply concatenate the bytes to save time. In + // the (probably rare) case that they have different extension registries there is a chance that + // some of the extensions may be dropped, but the tradeoff of making this operation fast seems + // to outway the benefits of combining the extension registries, which is not normally done for + // lite protos anyways. + if (this.delayedBytes != null && other.delayedBytes != null) { + this.delayedBytes = this.delayedBytes.concat(other.delayedBytes); + return; + } + + // At least one is parsed and both contain data. We won't drop any extensions here directly, but + // in the case that the extension registries are not the same then we might in the future if we + // need to serialze and parse a message again. + if (this.value == null && other.value != null) { + setValue(mergeValueAndBytes(other.value, this.delayedBytes, this.extensionRegistry)); + return; + } else if (this.value != null && other.value == null) { + setValue(mergeValueAndBytes(this.value, other.delayedBytes, other.extensionRegistry)); + return; + } + + // At this point we have two fully parsed messages. We can't merge directly from one to the + // other because only generated builder code contains methods to mergeFrom another parsed + // message. We have to serialize one instance and then merge the bytes into the other. This may + // drop extensions from one of the messages if one of the values had an extension set on it + // directly. + // + // To mitigate this we prefer serializing a message that has an extension registry, and + // therefore a chance that all extensions set on it are in that registry. + // + // NOTE: The check for other.extensionRegistry not being null must come first because at this + // point in time if other.extensionRegistry is not null then this.extensionRegistry will not be + // null either. + if (other.extensionRegistry != null) { + setValue(mergeValueAndBytes(this.value, other.toByteString(), other.extensionRegistry)); + return; + } else if (this.extensionRegistry != null) { + setValue(mergeValueAndBytes(other.value, this.toByteString(), this.extensionRegistry)); + return; + } else { + // All extensions from the other message will be dropped because we have no registry. + setValue(mergeValueAndBytes(this.value, other.toByteString(), EMPTY_REGISTRY)); + return; + } + } + + private static MessageLite mergeValueAndBytes( + MessageLite value, ByteString otherBytes, ExtensionRegistryLite extensionRegistry) { + try { + return value.toBuilder().mergeFrom(otherBytes, extensionRegistry).build(); + } catch (InvalidProtocolBufferException e) { + // Nothing is logged and no exceptions are thrown. Clients will be unaware that a proto + // was invalid. + return value; + } + } + + /** + * Sets this field with bytes to delay-parse. + */ + public void setByteString(ByteString bytes, ExtensionRegistryLite extensionRegistry) { + checkArguments(extensionRegistry, bytes); + this.delayedBytes = bytes; + this.extensionRegistry = extensionRegistry; + this.value = null; + this.memoizedBytes = null; + } + + /** + * Due to the optional field can be duplicated at the end of serialized + * bytes, which will make the serialized size changed after LazyField + * parsed. Be careful when using this method. + */ + public int getSerializedSize() { + if (delayedBytes != null) { + return delayedBytes.size(); + } else if (memoizedBytes != null) { + return memoizedBytes.size(); + } else if (value != null) { + return value.getSerializedSize(); + } else { + return 0; + } + } + + /** + * Returns a BytesString for this field in a thread-safe way. + */ + public ByteString toByteString() { + if (delayedBytes != null) { + return delayedBytes; + } + if (memoizedBytes != null) { + return memoizedBytes; + } + synchronized (this) { + if (memoizedBytes != null) { + return memoizedBytes; + } + if (value == null) { + memoizedBytes = ByteString.EMPTY; + } else { + memoizedBytes = value.toByteString(); + } + return memoizedBytes; + } + } + + /** + * Might lazily parse the bytes that were previously passed in. Is thread-safe. + */ + protected void ensureInitialized(MessageLite defaultInstance) { + if (value != null) { + return; + } + synchronized (this) { + if (value != null) { + return; + } + try { + if (delayedBytes != null) { + // The extensionRegistry shouldn't be null here since we have delayedBytes. + MessageLite parsedValue = defaultInstance.getParserForType() + .parseFrom(delayedBytes, extensionRegistry); + this.value = parsedValue; + this.memoizedBytes = delayedBytes; + this.delayedBytes = null; + } else { + this.value = defaultInstance; + this.memoizedBytes = ByteString.EMPTY; + this.delayedBytes = null; + } + } catch (InvalidProtocolBufferException e) { + // Nothing is logged and no exceptions are thrown. Clients will be unaware that this proto + // was invalid. + this.value = defaultInstance; + this.memoizedBytes = ByteString.EMPTY; + this.delayedBytes = null; + } + } + } + + + private static void checkArguments(ExtensionRegistryLite extensionRegistry, ByteString bytes) { + if (extensionRegistry == null) { + throw new NullPointerException("found null ExtensionRegistry"); + } + if (bytes == null) { + throw new NullPointerException("found null ByteString"); + } + } +} diff --git a/java/core/src/main/java/com/google/protobuf/LazyStringArrayList.java b/java/core/src/main/java/com/google/protobuf/LazyStringArrayList.java new file mode 100644 index 00000000..c3be3cca --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/LazyStringArrayList.java @@ -0,0 +1,409 @@ +// 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.List; +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.RandomAccess; + +/** + * An implementation of {@link LazyStringList} that wraps an ArrayList. Each + * element is one of String, ByteString, or byte[]. It caches the last one + * requested which is most likely the one needed next. This minimizes memory + * usage while satisfying the most common use cases. + * <p> + * <strong>Note that this implementation is not synchronized.</strong> + * If multiple threads access an <tt>ArrayList</tt> instance concurrently, + * and at least one of the threads modifies the list structurally, it + * <i>must</i> be synchronized externally. (A structural modification is + * any operation that adds or deletes one or more elements, or explicitly + * resizes the backing array; merely setting the value of an element is not + * a structural modification.) This is typically accomplished by + * synchronizing on some object that naturally encapsulates the list. + * <p> + * If the implementation is accessed via concurrent reads, this is thread safe. + * Conversions are done in a thread safe manner. It's possible that the + * conversion may happen more than once if two threads attempt to access the + * same element and the modifications were not visible to each other, but this + * will not result in any corruption of the list or change in behavior other + * than performance. + * + * @author jonp@google.com (Jon Perlow) + */ +public class LazyStringArrayList extends AbstractProtobufList<String> + implements LazyStringList, RandomAccess { + + private static final LazyStringArrayList EMPTY_LIST = new LazyStringArrayList(); + static { + EMPTY_LIST.makeImmutable(); + } + + static LazyStringArrayList emptyList() { + return EMPTY_LIST; + } + + // For compatibility with older runtimes. + public static final LazyStringList EMPTY = EMPTY_LIST; + + private final List<Object> list; + + public LazyStringArrayList() { + list = new ArrayList<Object>(); + } + + public LazyStringArrayList(int intialCapacity) { + list = new ArrayList<Object>(intialCapacity); + } + + public LazyStringArrayList(LazyStringList from) { + list = new ArrayList<Object>(from.size()); + addAll(from); + } + + public LazyStringArrayList(List<String> from) { + list = new ArrayList<Object>(from); + } + + @Override + public String get(int index) { + Object o = list.get(index); + if (o instanceof String) { + return (String) o; + } else if (o instanceof ByteString) { + ByteString bs = (ByteString) o; + String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + list.set(index, s); + } + return s; + } else { + byte[] ba = (byte[]) o; + String s = Internal.toStringUtf8(ba); + if (Internal.isValidUtf8(ba)) { + list.set(index, s); + } + return s; + } + } + + @Override + public int size() { + return list.size(); + } + + @Override + public String set(int index, String s) { + ensureIsMutable(); + Object o = list.set(index, s); + return asString(o); + } + + @Override + public void add(int index, String element) { + ensureIsMutable(); + list.add(index, element); + modCount++; + } + + private void add(int index, ByteString element) { + ensureIsMutable(); + list.add(index, element); + modCount++; + } + + private void add(int index, byte[] element) { + ensureIsMutable(); + list.add(index, element); + modCount++; + } + + @Override + public boolean addAll(Collection<? extends String> c) { + // The default implementation of AbstractCollection.addAll(Collection) + // delegates to add(Object). This implementation instead delegates to + // addAll(int, Collection), which makes a special case for Collections + // which are instances of LazyStringList. + return addAll(size(), c); + } + + @Override + public boolean addAll(int index, Collection<? extends String> c) { + ensureIsMutable(); + // When copying from another LazyStringList, directly copy the underlying + // elements rather than forcing each element to be decoded to a String. + Collection<?> collection = c instanceof LazyStringList + ? ((LazyStringList) c).getUnderlyingElements() : c; + boolean ret = list.addAll(index, collection); + modCount++; + return ret; + } + + // @Override + public boolean addAllByteString(Collection<? extends ByteString> values) { + ensureIsMutable(); + boolean ret = list.addAll(values); + modCount++; + return ret; + } + + // @Override + public boolean addAllByteArray(Collection<byte[]> c) { + ensureIsMutable(); + boolean ret = list.addAll(c); + modCount++; + return ret; + } + + @Override + public String remove(int index) { + ensureIsMutable(); + Object o = list.remove(index); + modCount++; + return asString(o); + } + + @Override + public void clear() { + ensureIsMutable(); + list.clear(); + modCount++; + } + + // @Override + public void add(ByteString element) { + ensureIsMutable(); + list.add(element); + modCount++; + } + + // @Override + public void add(byte[] element) { + ensureIsMutable(); + list.add(element); + modCount++; + } + + @Override + public Object getRaw(int index) { + return list.get(index); + } + + // @Override + public ByteString getByteString(int index) { + Object o = list.get(index); + ByteString b = asByteString(o); + if (b != o) { + list.set(index, b); + } + return b; + } + + // @Override + public byte[] getByteArray(int index) { + Object o = list.get(index); + byte[] b = asByteArray(o); + if (b != o) { + list.set(index, b); + } + return b; + } + + // @Override + public void set(int index, ByteString s) { + setAndReturn(index, s); + } + + private Object setAndReturn(int index, ByteString s) { + ensureIsMutable(); + return list.set(index, s); + } + + // @Override + public void set(int index, byte[] s) { + setAndReturn(index, s); + } + + private Object setAndReturn(int index, byte[] s) { + ensureIsMutable(); + return list.set(index, s); + } + + private static String asString(Object o) { + if (o instanceof String) { + return (String) o; + } else if (o instanceof ByteString) { + return ((ByteString) o).toStringUtf8(); + } else { + return Internal.toStringUtf8((byte[]) o); + } + } + + private static ByteString asByteString(Object o) { + if (o instanceof ByteString) { + return (ByteString) o; + } else if (o instanceof String) { + return ByteString.copyFromUtf8((String) o); + } else { + return ByteString.copyFrom((byte[]) o); + } + } + + private static byte[] asByteArray(Object o) { + if (o instanceof byte[]) { + return (byte[]) o; + } else if (o instanceof String) { + return Internal.toByteArray((String) o); + } else { + return ((ByteString) o).toByteArray(); + } + } + + // @Override + public List<?> getUnderlyingElements() { + return Collections.unmodifiableList(list); + } + + // @Override + public void mergeFrom(LazyStringList other) { + ensureIsMutable(); + for (Object o : other.getUnderlyingElements()) { + if (o instanceof byte[]) { + byte[] b = (byte[]) o; + // Byte array's content is mutable so they should be copied rather than + // shared when merging from one message to another. + list.add(Arrays.copyOf(b, b.length)); + } else { + list.add(o); + } + } + } + + private static class ByteArrayListView extends AbstractList<byte[]> + implements RandomAccess { + private final LazyStringArrayList list; + + ByteArrayListView(LazyStringArrayList list) { + this.list = list; + } + + @Override + public byte[] get(int index) { + return list.getByteArray(index); + } + + @Override + public int size() { + return list.size(); + } + + @Override + public byte[] set(int index, byte[] s) { + Object o = list.setAndReturn(index, s); + modCount++; + return asByteArray(o); + } + + @Override + public void add(int index, byte[] s) { + list.add(index, s); + modCount++; + } + + @Override + public byte[] remove(int index) { + Object o = list.remove(index); + modCount++; + return asByteArray(o); + } + } + + // @Override + public List<byte[]> asByteArrayList() { + return new ByteArrayListView(this); + } + + private static class ByteStringListView extends AbstractList<ByteString> + implements RandomAccess { + private final LazyStringArrayList list; + + ByteStringListView(LazyStringArrayList list) { + this.list = list; + } + + @Override + public ByteString get(int index) { + return list.getByteString(index); + } + + @Override + public int size() { + return list.size(); + } + + @Override + public ByteString set(int index, ByteString s) { + Object o = list.setAndReturn(index, s); + modCount++; + return asByteString(o); + } + + @Override + public void add(int index, ByteString s) { + list.add(index, s); + modCount++; + } + + @Override + public ByteString remove(int index) { + Object o = list.remove(index); + modCount++; + return asByteString(o); + } + } + + // @Override + public List<ByteString> asByteStringList() { + return new ByteStringListView(this); + } + + // @Override + public LazyStringList getUnmodifiableView() { + if (isModifiable()) { + return new UnmodifiableLazyStringList(this); + } + return this; + } + +} diff --git a/java/core/src/main/java/com/google/protobuf/LazyStringList.java b/java/core/src/main/java/com/google/protobuf/LazyStringList.java new file mode 100644 index 00000000..3eeedca1 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/LazyStringList.java @@ -0,0 +1,174 @@ +// 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.Collection; +import java.util.List; + +/** + * An interface extending {@code List<String>} that also provides access to the + * items of the list as UTF8-encoded ByteString or byte[] objects. This is + * used by the protocol buffer implementation to support lazily converting bytes + * parsed over the wire to String objects until needed and also increases the + * efficiency of serialization if the String was never requested as the + * ByteString or byte[] is already cached. The ByteString methods are used in + * immutable API only and byte[] methods used in mutable API only for they use + * different representations for string/bytes fields. + * + * @author jonp@google.com (Jon Perlow) + */ +public interface LazyStringList extends ProtocolStringList { + + /** + * Returns the element at the specified position in this list as a ByteString. + * + * @param index index of the element to return + * @return the element at the specified position in this list + * @throws IndexOutOfBoundsException if the index is out of range + * ({@code index < 0 || index >= size()}) + */ + ByteString getByteString(int index); + + /** + * Returns the element at the specified position in this list as an Object + * that will either be a String or a ByteString. + * + * @param index index of the element to return + * @return the element at the specified position in this list + * @throws IndexOutOfBoundsException if the index is out of range + * ({@code index < 0 || index >= size()}) + */ + Object getRaw(int index); + + /** + * Returns the element at the specified position in this list as byte[]. + * + * @param index index of the element to return + * @return the element at the specified position in this list + * @throws IndexOutOfBoundsException if the index is out of range + * ({@code index < 0 || index >= size()}) + */ + byte[] getByteArray(int index); + + /** + * Appends the specified element to the end of this list (optional + * operation). + * + * @param element element to be appended to this list + * @throws UnsupportedOperationException if the <tt>add</tt> operation + * is not supported by this list + */ + void add(ByteString element); + + /** + * Appends the specified element to the end of this list (optional + * operation). + * + * @param element element to be appended to this list + * @throws UnsupportedOperationException if the <tt>add</tt> operation + * is not supported by this list + */ + void add(byte[] element); + + /** + * Replaces the element at the specified position in this list with the + * specified element (optional operation). + * + * @param index index of the element to replace + * @param element the element to be stored at the specified position + * @throws UnsupportedOperationException if the <tt>set</tt> operation + * is not supported by this list + * IndexOutOfBoundsException if the index is out of range + * ({@code index < 0 || index >= size()}) + */ + void set(int index, ByteString element); + + /** + * Replaces the element at the specified position in this list with the + * specified element (optional operation). + * + * @param index index of the element to replace + * @param element the element to be stored at the specified position + * @throws UnsupportedOperationException if the <tt>set</tt> operation + * is not supported by this list + * IndexOutOfBoundsException if the index is out of range + * ({@code index < 0 || index >= size()}) + */ + void set(int index, byte[] element); + + /** + * Appends all elements in the specified ByteString collection to the end of + * this list. + * + * @param c collection whose elements are to be added to this list + * @return true if this list changed as a result of the call + * @throws UnsupportedOperationException if the <tt>addAllByteString</tt> + * operation is not supported by this list + */ + boolean addAllByteString(Collection<? extends ByteString> c); + + /** + * Appends all elements in the specified byte[] collection to the end of + * this list. + * + * @param c collection whose elements are to be added to this list + * @return true if this list changed as a result of the call + * @throws UnsupportedOperationException if the <tt>addAllByteArray</tt> + * operation is not supported by this list + */ + boolean addAllByteArray(Collection<byte[]> c); + + /** + * Returns an unmodifiable List of the underlying elements, each of which is + * either a {@code String} or its equivalent UTF-8 encoded {@code ByteString} + * or byte[]. It is an error for the caller to modify the returned + * List, and attempting to do so will result in an + * {@link UnsupportedOperationException}. + */ + List<?> getUnderlyingElements(); + + /** + * Merges all elements from another LazyStringList into this one. This method + * differs from {@link #addAll(Collection)} on that underlying byte arrays are + * copied instead of reference shared. Immutable API doesn't need to use this + * method as byte[] is not used there at all. + */ + void mergeFrom(LazyStringList other); + + /** + * Returns a mutable view of this list. Changes to the view will be made into + * the original list. This method is used in mutable API only. + */ + List<byte[]> asByteArrayList(); + + /** Returns an unmodifiable view of the list. */ + LazyStringList getUnmodifiableView(); +} diff --git a/java/core/src/main/java/com/google/protobuf/LiteralByteString.java b/java/core/src/main/java/com/google/protobuf/LiteralByteString.java new file mode 100644 index 00000000..a18c2792 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/LiteralByteString.java @@ -0,0 +1,267 @@ +// 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.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.Collections; +import java.util.List; + +/** + * This class implements a {@link com.google.protobuf.ByteString} backed by a + * single array of bytes, contiguous in memory. It supports substring by + * pointing to only a sub-range of the underlying byte array, meaning that a + * substring will reference the full byte-array of the string it's made from, + * exactly as with {@link String}. + * + * @author carlanton@google.com (Carl Haverl) + */ +class LiteralByteString extends ByteString.LeafByteString { + private static final long serialVersionUID = 1L; + + protected final byte[] bytes; + + /** + * Creates a {@code LiteralByteString} backed by the given array, without + * copying. + * + * @param bytes array to wrap + */ + LiteralByteString(byte[] bytes) { + this.bytes = bytes; + } + + @Override + public byte byteAt(int index) { + // Unlike most methods in this class, this one is a direct implementation + // ignoring the potential offset because we need to do range-checking in the + // substring case anyway. + return bytes[index]; + } + + @Override + public int size() { + return bytes.length; + } + + // ================================================================= + // ByteString -> substring + + @Override + public final ByteString substring(int beginIndex, int endIndex) { + final int length = checkRange(beginIndex, endIndex, size()); + + if (length == 0) { + return ByteString.EMPTY; + } + + return new BoundedByteString(bytes, getOffsetIntoBytes() + beginIndex, length); + } + + // ================================================================= + // ByteString -> byte[] + + @Override + protected void copyToInternal( + byte[] target, int sourceOffset, int targetOffset, int numberToCopy) { + // Optimized form, not for subclasses, since we don't call + // getOffsetIntoBytes() or check the 'numberToCopy' parameter. + // TODO(nathanmittler): Is not calling getOffsetIntoBytes really saving that much? + System.arraycopy(bytes, sourceOffset, target, targetOffset, numberToCopy); + } + + @Override + public final void copyTo(ByteBuffer target) { + target.put(bytes, getOffsetIntoBytes(), size()); // Copies bytes + } + + @Override + public final ByteBuffer asReadOnlyByteBuffer() { + return ByteBuffer.wrap(bytes, getOffsetIntoBytes(), size()).asReadOnlyBuffer(); + } + + @Override + public final List<ByteBuffer> asReadOnlyByteBufferList() { + return Collections.singletonList(asReadOnlyByteBuffer()); + } + + @Override + public final void writeTo(OutputStream outputStream) throws IOException { + outputStream.write(toByteArray()); + } + + @Override + final void writeToInternal(OutputStream outputStream, int sourceOffset, int numberToWrite) + throws IOException { + outputStream.write(bytes, getOffsetIntoBytes() + sourceOffset, numberToWrite); + } + + @Override + protected final String toStringInternal(Charset charset) { + return new String(bytes, getOffsetIntoBytes(), size(), charset); + } + + // ================================================================= + // UTF-8 decoding + + @Override + public final boolean isValidUtf8() { + int offset = getOffsetIntoBytes(); + return Utf8.isValidUtf8(bytes, offset, offset + size()); + } + + @Override + protected final int partialIsValidUtf8(int state, int offset, int length) { + int index = getOffsetIntoBytes() + offset; + return Utf8.partialIsValidUtf8(state, bytes, index, index + length); + } + + // ================================================================= + // equals() and hashCode() + + @Override + public final boolean equals(Object other) { + if (other == this) { + return true; + } + if (!(other instanceof ByteString)) { + return false; + } + + if (size() != ((ByteString) other).size()) { + return false; + } + if (size() == 0) { + return true; + } + + 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. + int thisHash = peekCachedHashCode(); + int thatHash = otherAsLiteral.peekCachedHashCode(); + if (thisHash != 0 && thatHash != 0 && thisHash != thatHash) { + return false; + } + + return equalsRange((LiteralByteString) other, 0, size()); + } else { + // RopeByteString and NioByteString. + return other.equals(this); + } + } + + /** + * Check equality of the substring of given length of this object starting at + * zero with another {@code LiteralByteString} substring starting at offset. + * + * @param other what to compare a substring in + * @param offset offset into other + * @param length number of bytes to compare + * @return true for equality of substrings, else false. + */ + @Override + final boolean equalsRange(ByteString other, int offset, int length) { + if (length > other.size()) { + throw new IllegalArgumentException("Length too large: " + length + size()); + } + if (offset + length > other.size()) { + throw new IllegalArgumentException( + "Ran off end of other: " + offset + ", " + length + ", " + other.size()); + } + + if (other instanceof LiteralByteString) { + LiteralByteString lbsOther = (LiteralByteString) other; + byte[] thisBytes = bytes; + byte[] otherBytes = lbsOther.bytes; + int thisLimit = getOffsetIntoBytes() + length; + for ( + int thisIndex = getOffsetIntoBytes(), otherIndex = lbsOther.getOffsetIntoBytes() + offset; + (thisIndex < thisLimit); ++thisIndex, ++otherIndex) { + if (thisBytes[thisIndex] != otherBytes[otherIndex]) { + return false; + } + } + return true; + } + + return other.substring(offset, offset + length).equals(substring(0, length)); + } + + @Override + protected final int partialHash(int h, int offset, int length) { + return hashCode(h, bytes, getOffsetIntoBytes() + offset, length); + } + + static int hashCode(int h, byte[] bytes, int offset, int length) { + for (int i = offset; i < offset + length; i++) { + h = h * 31 + bytes[i]; + } + return h; + } + + static int hashCode(byte[] bytes) { + int h = hashCode(bytes.length, bytes, 0, bytes.length); + return h == 0 ? 1 : h; + } + + // ================================================================= + // Input stream + + @Override + public final InputStream newInput() { + return new ByteArrayInputStream(bytes, getOffsetIntoBytes(), size()); // No copy + } + + @Override + public final CodedInputStream newCodedInput() { + // We trust CodedInputStream not to modify the bytes, or to give anyone + // else access to them. + return CodedInputStream.newInstance(this); + } + + // ================================================================= + // Internal methods + + /** + * Offset into {@code bytes[]} to use, non-zero for substrings. + * + * @return always 0 for this class + */ + protected int getOffsetIntoBytes() { + return 0; + } +} diff --git a/java/core/src/main/java/com/google/protobuf/LongArrayList.java b/java/core/src/main/java/com/google/protobuf/LongArrayList.java new file mode 100644 index 00000000..ebe62029 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/LongArrayList.java @@ -0,0 +1,249 @@ +// 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.Internal.LongList; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.RandomAccess; + +/** + * An implementation of {@link LongList} on top of a primitive array. + * + * @author dweis@google.com (Daniel Weis) + */ +final class LongArrayList extends AbstractProtobufList<Long> implements LongList, RandomAccess { + + private static final int DEFAULT_CAPACITY = 10; + + private static final LongArrayList EMPTY_LIST = new LongArrayList(); + static { + EMPTY_LIST.makeImmutable(); + } + + public static LongArrayList emptyList() { + return EMPTY_LIST; + } + + /** + * The backing store for the list. + */ + private long[] array; + + /** + * The size of the list distinct from the length of the array. That is, it is the number of + * elements set in the list. + */ + private int size; + + /** + * Constructs a new mutable {@code LongArrayList} with default capacity. + */ + LongArrayList() { + this(DEFAULT_CAPACITY); + } + + /** + * Constructs a new mutable {@code LongArrayList} with the provided capacity. + */ + LongArrayList(int capacity) { + array = new long[capacity]; + size = 0; + } + + /** + * Constructs a new mutable {@code LongArrayList} containing the same elements as {@code other}. + */ + LongArrayList(List<Long> other) { + if (other instanceof LongArrayList) { + LongArrayList list = (LongArrayList) other; + array = list.array.clone(); + size = list.size; + } else { + size = other.size(); + array = new long[size]; + for (int i = 0; i < size; i++) { + array[i] = other.get(i); + } + } + } + + @Override + public Long get(int index) { + return getLong(index); + } + + @Override + public long getLong(int index) { + ensureIndexInRange(index); + return array[index]; + } + + @Override + public int size() { + return size; + } + + @Override + public Long set(int index, Long element) { + return setLong(index, element); + } + + @Override + public long setLong(int index, long element) { + ensureIsMutable(); + ensureIndexInRange(index); + long previousValue = array[index]; + array[index] = element; + return previousValue; + } + + @Override + public void add(int index, Long element) { + addLong(index, element); + } + + /** + * Like {@link #add(Long)} but more efficient in that it doesn't box the element. + */ + @Override + public void addLong(long element) { + addLong(size, element); + } + + /** + * Like {@link #add(int, Long)} but more efficient in that it doesn't box the element. + */ + private void addLong(int index, long element) { + ensureIsMutable(); + if (index < 0 || index > size) { + throw new IndexOutOfBoundsException(makeOutOfBoundsExceptionMessage(index)); + } + + if (size < array.length) { + // Shift everything over to make room + System.arraycopy(array, index, array, index + 1, size - index); + } else { + // Resize to 1.5x the size + int length = ((size * 3) / 2) + 1; + long[] newArray = new long[length]; + + // Copy the first part directly + System.arraycopy(array, 0, newArray, 0, index); + + // Copy the rest shifted over by one to make room + System.arraycopy(array, index, newArray, index + 1, size - index); + array = newArray; + } + + array[index] = element; + size++; + modCount++; + } + + @Override + public boolean addAll(Collection<? extends Long> collection) { + ensureIsMutable(); + + if (collection == null) { + throw new NullPointerException(); + } + + // We specialize when adding another LongArrayList to avoid boxing elements. + if (!(collection instanceof LongArrayList)) { + return super.addAll(collection); + } + + LongArrayList list = (LongArrayList) collection; + if (list.size == 0) { + return false; + } + + int overflow = Integer.MAX_VALUE - size; + if (overflow < list.size) { + // We can't actually represent a list this large. + throw new OutOfMemoryError(); + } + + int newSize = size + list.size; + if (newSize > array.length) { + array = Arrays.copyOf(array, newSize); + } + + System.arraycopy(list.array, 0, array, size, list.size); + size = newSize; + modCount++; + return true; + } + + @Override + public boolean remove(Object o) { + ensureIsMutable(); + for (int i = 0; i < size; i++) { + if (o.equals(array[i])) { + System.arraycopy(array, i + 1, array, i, size - i); + size--; + modCount++; + return true; + } + } + return false; + } + + @Override + public Long remove(int index) { + ensureIsMutable(); + ensureIndexInRange(index); + long value = array[index]; + System.arraycopy(array, index + 1, array, index, size - index); + size--; + modCount++; + return value; + } + + /** + * Ensures that the provided {@code index} is within the range of {@code [0, size]}. Throws an + * {@link IndexOutOfBoundsException} if it is not. + * + * @param index the index to verify is in range + */ + private void ensureIndexInRange(int index) { + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException(makeOutOfBoundsExceptionMessage(index)); + } + } + + private String makeOutOfBoundsExceptionMessage(int index) { + return "Index:" + index + ", Size:" + size; + } +} diff --git a/java/core/src/main/java/com/google/protobuf/MapEntry.java b/java/core/src/main/java/com/google/protobuf/MapEntry.java new file mode 100644 index 00000000..31414bb4 --- /dev/null +++ b/java/core/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<K, V> extends AbstractMessage { + private static class Metadata<K, V> { + public final Descriptor descriptor; + public final MapEntry<K, V> defaultInstance; + public final AbstractParser<MapEntry<K, V>> parser; + + public Metadata( + final Descriptor descriptor, final MapEntry<K, V> defaultInstance) { + this.descriptor = descriptor; + this.defaultInstance = defaultInstance; + final Metadata<K, V> thisMetadata = this; + this.parser = new AbstractParser<MapEntry<K, V>>() { + private final Parser<MapEntryLite<K, V>> dataParser = + defaultInstance.data.getParserForType(); + @Override + public MapEntry<K, V> parsePartialFrom( + CodedInputStream input, ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + MapEntryLite<K, V> data = + dataParser.parsePartialFrom(input, extensionRegistry); + return new MapEntry<K, V>(thisMetadata, data); + } + + }; + } + } + + private final Metadata<K, V> metadata; + private final MapEntryLite<K, V> 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<K, V>(descriptor, this); + } + + /** Create a new MapEntry message. */ + private MapEntry(Metadata<K, V> metadata, MapEntryLite<K, V> 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 <K, V> MapEntry<K, V> newDefaultInstance( + Descriptor descriptor, + WireFormat.FieldType keyType, K defaultKey, + WireFormat.FieldType valueType, V defaultValue) { + return new MapEntry<K, V>( + 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<MapEntry<K, V>> getParserForType() { + return metadata.parser; + } + + @Override + public Builder<K, V> newBuilderForType() { + return new Builder<K, V>(metadata); + } + + @Override + public Builder<K, V> toBuilder() { + return new Builder<K, V>(metadata, data); + } + + @Override + public MapEntry<K, V> getDefaultInstanceForType() { + return metadata.defaultInstance; + } + + @Override + public Descriptor getDescriptorForType() { + return metadata.descriptor; + } + + @Override + public Map<FieldDescriptor, Object> getAllFields() { + final TreeMap<FieldDescriptor, Object> result = + new TreeMap<FieldDescriptor, Object>(); + 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<K, V> + extends AbstractMessage.Builder<Builder<K, V>> { + private final Metadata<K, V> metadata; + private MapEntryLite<K, V> data; + private MapEntryLite.Builder<K, V> dataBuilder; + + private Builder(Metadata<K, V> metadata) { + this.metadata = metadata; + this.data = metadata.defaultInstance.data; + this.dataBuilder = null; + } + + private Builder(Metadata<K, V> metadata, MapEntryLite<K, V> 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<K, V> setKey(K key) { + ensureMutable(); + dataBuilder.setKey(key); + return this; + } + + public Builder<K, V> clearKey() { + ensureMutable(); + dataBuilder.clearKey(); + return this; + } + + public Builder<K, V> setValue(V value) { + ensureMutable(); + dataBuilder.setValue(value); + return this; + } + + public Builder<K, V> clearValue() { + ensureMutable(); + dataBuilder.clearValue(); + return this; + } + + @Override + public MapEntry<K, V> build() { + MapEntry<K, V> result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @Override + public MapEntry<K, V> buildPartial() { + if (dataBuilder != null) { + data = dataBuilder.buildPartial(); + dataBuilder = null; + } + return new MapEntry<K, V>(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<K, V> 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<K, V> clearField(FieldDescriptor field) { + checkFieldDescriptor(field); + if (field.getNumber() == 1) { + clearKey(); + } else { + clearValue(); + } + return this; + } + + @Override + public Builder<K, V> setRepeatedField(FieldDescriptor field, int index, + Object value) { + throw new RuntimeException( + "There is no repeated field in a map entry message."); + } + + @Override + public Builder<K, V> addRepeatedField(FieldDescriptor field, Object value) { + throw new RuntimeException( + "There is no repeated field in a map entry message."); + } + + @Override + public Builder<K, V> setUnknownFields(UnknownFieldSet unknownFields) { + // Unknown fields are discarded for MapEntry message. + return this; + } + + @Override + public MapEntry<K, V> getDefaultInstanceForType() { + return metadata.defaultInstance; + } + + @Override + public boolean isInitialized() { + if (dataBuilder != null) { + return dataBuilder.isInitialized(); + } else { + return data.isInitialized(); + } + } + + @Override + public Map<FieldDescriptor, Object> getAllFields() { + final TreeMap<FieldDescriptor, Object> result = + new TreeMap<FieldDescriptor, Object>(); + 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<K, V> clone() { + if (dataBuilder == null) { + return new Builder<K, V>(metadata, data); + } else { + return new Builder<K, V>(metadata, dataBuilder.build()); + } + } + } +} diff --git a/java/core/src/main/java/com/google/protobuf/MapEntryLite.java b/java/core/src/main/java/com/google/protobuf/MapEntryLite.java new file mode 100644 index 00000000..bcffa946 --- /dev/null +++ b/java/core/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<K, V> extends AbstractMessageLite { + private static class Metadata<K, V> { + public final MapEntryLite<K, V> defaultInstance; + public final WireFormat.FieldType keyType; + public final WireFormat.FieldType valueType; + public final Parser<MapEntryLite<K, V>> parser; + public Metadata( + MapEntryLite<K, V> defaultInstance, + WireFormat.FieldType keyType, + WireFormat.FieldType valueType) { + this.defaultInstance = defaultInstance; + this.keyType = keyType; + this.valueType = valueType; + final Metadata<K, V> finalThis = this; + this.parser = new AbstractParser<MapEntryLite<K, V>>() { + @Override + public MapEntryLite<K, V> parsePartialFrom( + CodedInputStream input, ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + return new MapEntryLite<K, V>(finalThis, input, extensionRegistry); + } + }; + } + } + + private static final int KEY_FIELD_NUMBER = 1; + private static final int VALUE_FIELD_NUMBER = 2; + + private final Metadata<K, V> 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<K, V>(this, keyType, valueType); + this.key = defaultKey; + this.value = defaultValue; + } + + /** Creates a new MapEntryLite message. */ + private MapEntryLite(Metadata<K, V> 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 <K, V> MapEntryLite<K, V> newDefaultInstance( + WireFormat.FieldType keyType, K defaultKey, + WireFormat.FieldType valueType, V defaultValue) { + return new MapEntryLite<K, V>( + 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<K, V> 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> 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<MapEntryLite<K, V>> getParserForType() { + return metadata.parser; + } + + @Override + public Builder<K, V> newBuilderForType() { + return new Builder<K, V>(metadata); + } + + @Override + public Builder<K, V> toBuilder() { + return new Builder<K, V>(metadata, key, value); + } + + @Override + public MapEntryLite<K, V> 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<K, V> + extends AbstractMessageLite.Builder<Builder<K, V>> { + private final Metadata<K, V> metadata; + private K key; + private V value; + + private Builder(Metadata<K, V> 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<K, V> setKey(K key) { + this.key = key; + return this; + } + + public Builder<K, V> setValue(V value) { + this.value = value; + return this; + } + + public Builder<K, V> clearKey() { + this.key = metadata.defaultInstance.key; + return this; + } + + public Builder<K, V> clearValue() { + this.value = metadata.defaultInstance.value; + return this; + } + + @Override + public Builder<K, V> clear() { + this.key = metadata.defaultInstance.key; + this.value = metadata.defaultInstance.value; + return this; + } + + @Override + public MapEntryLite<K, V> build() { + MapEntryLite<K, V> result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @Override + public MapEntryLite<K, V> buildPartial() { + return new MapEntryLite<K, V>(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<K, V> metadata, K key, V value) { + this.metadata = metadata; + this.key = key; + this.value = value; + } + + @Override + public Builder<K, V> clone() { + return new Builder<K, V>(metadata, key, value); + } + + @Override + public Builder<K, V> mergeFrom( + CodedInputStream input, ExtensionRegistryLite extensionRegistry) + throws IOException { + MapEntryLite<K, V> entry = + new MapEntryLite<K, V>(metadata, input, extensionRegistry); + this.key = entry.key; + this.value = entry.value; + return this; + } + } +} diff --git a/java/core/src/main/java/com/google/protobuf/MapField.java b/java/core/src/main/java/com/google/protobuf/MapField.java new file mode 100644 index 00000000..b290993c --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/MapField.java @@ -0,0 +1,286 @@ +// 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.MapFieldLite.MutatabilityAwareMap; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +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<K, V> implements MutabilityOracle { + /** + * 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 boolean isMutable; + private volatile StorageMode mode; + private MutatabilityAwareMap<K, V> mapData; + private List<Message> listData; + + // Convert between a map entry Message and a key-value pair. + private static interface Converter<K, V> { + Message convertKeyAndValueToMessage(K key, V value); + void convertMessageToKeyAndValue(Message message, Map<K, V> map); + + Message getMessageDefaultInstance(); + } + + private static class ImmutableMessageConverter<K, V> implements Converter<K, V> { + private final MapEntry<K, V> defaultEntry; + public ImmutableMessageConverter(MapEntry<K, V> defaultEntry) { + this.defaultEntry = defaultEntry; + } + + public Message convertKeyAndValueToMessage(K key, V value) { + return defaultEntry.newBuilderForType().setKey(key).setValue(value).buildPartial(); + } + + public void convertMessageToKeyAndValue(Message message, Map<K, V> map) { + MapEntry<K, V> entry = (MapEntry<K, V>) message; + map.put(entry.getKey(), entry.getValue()); + } + + public Message getMessageDefaultInstance() { + return defaultEntry; + } + } + + + private final Converter<K, V> converter; + + private MapField( + Converter<K, V> converter, + StorageMode mode, + Map<K, V> mapData) { + this.converter = converter; + this.isMutable = true; + this.mode = mode; + this.mapData = new MutatabilityAwareMap<K, V>(this, mapData); + this.listData = null; + } + + private MapField( + MapEntry<K, V> defaultEntry, + StorageMode mode, + Map<K, V> mapData) { + this(new ImmutableMessageConverter<K, V>(defaultEntry), mode, mapData); + } + + + /** Returns an immutable empty MapField. */ + public static <K, V> MapField<K, V> emptyMapField( + MapEntry<K, V> defaultEntry) { + return new MapField<K, V>( + defaultEntry, StorageMode.MAP, Collections.<K, V>emptyMap()); + } + + + /** Creates a new mutable empty MapField. */ + public static <K, V> MapField<K, V> newMapField(MapEntry<K, V> defaultEntry) { + return new MapField<K, V>( + defaultEntry, StorageMode.MAP, new LinkedHashMap<K, V>()); + } + + + private Message convertKeyAndValueToMessage(K key, V value) { + return converter.convertKeyAndValueToMessage(key, value); + } + + @SuppressWarnings("unchecked") + private void convertMessageToKeyAndValue(Message message, Map<K, V> map) { + converter.convertMessageToKeyAndValue(message, map); + } + + private List<Message> convertMapToList(MutatabilityAwareMap<K, V> mapData) { + List<Message> listData = new ArrayList<Message>(); + for (Map.Entry<K, V> entry : mapData.entrySet()) { + listData.add( + convertKeyAndValueToMessage( + entry.getKey(), entry.getValue())); + } + return listData; + } + + private MutatabilityAwareMap<K, V> convertListToMap(List<Message> listData) { + Map<K, V> mapData = new LinkedHashMap<K, V>(); + for (Message item : listData) { + convertMessageToKeyAndValue(item, mapData); + } + return new MutatabilityAwareMap<K, V>(this, mapData); + } + + /** Returns the content of this MapField as a read-only Map. */ + public Map<K, V> 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<K, V> getMutableMap() { + if (mode != StorageMode.MAP) { + if (mode == StorageMode.LIST) { + mapData = convertListToMap(listData); + } + listData = null; + mode = StorageMode.MAP; + } + return mapData; + } + + public void mergeFrom(MapField<K, V> other) { + getMutableMap().putAll(MapFieldLite.copy(other.getMap())); + } + + public void clear() { + mapData = new MutatabilityAwareMap<K, V>(this, new LinkedHashMap<K, V>()); + mode = StorageMode.MAP; + } + + @SuppressWarnings("unchecked") + @Override + public boolean equals(Object object) { + if (!(object instanceof MapField)) { + return false; + } + MapField<K, V> other = (MapField<K, V>) object; + return MapFieldLite.<K, V>equals(getMap(), other.getMap()); + } + + @Override + public int hashCode() { + return MapFieldLite.<K, V>calculateHashCodeForMap(getMap()); + } + + /** Returns a deep copy of this MapField. */ + public MapField<K, V> copy() { + return new MapField<K, V>( + converter, StorageMode.MAP, MapFieldLite.copy(getMap())); + } + + /** Gets the content of this MapField as a read-only List. */ + List<Message> 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<Message> 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(); + } + + /** + * Makes this list immutable. All subsequent modifications will throw an + * {@link UnsupportedOperationException}. + */ + public void makeImmutable() { + isMutable = false; + } + + /** + * Returns whether this field can be modified. + */ + public boolean isMutable() { + return isMutable; + } + + /* (non-Javadoc) + * @see com.google.protobuf.MutabilityOracle#ensureMutable() + */ + @Override + public void ensureMutable() { + if (!isMutable()) { + throw new UnsupportedOperationException(); + } + } +} diff --git a/java/core/src/main/java/com/google/protobuf/MapFieldLite.java b/java/core/src/main/java/com/google/protobuf/MapFieldLite.java new file mode 100644 index 00000000..16d3e6d2 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/MapFieldLite.java @@ -0,0 +1,549 @@ +// 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.Internal.EnumLite; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +/** + * 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 final class MapFieldLite<K, V> implements MutabilityOracle { + private MutatabilityAwareMap<K, V> mapData; + private boolean isMutable; + + private MapFieldLite(Map<K, V> mapData) { + this.mapData = new MutatabilityAwareMap<K, V>(this, mapData); + this.isMutable = true; + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + private static final MapFieldLite EMPTY_MAP_FIELD = + new MapFieldLite(Collections.emptyMap()); + static { + EMPTY_MAP_FIELD.makeImmutable(); + } + + /** Returns an singleton immutable empty MapFieldLite instance. */ + @SuppressWarnings({"unchecked", "cast"}) + public static <K, V> MapFieldLite<K, V> emptyMapField() { + return (MapFieldLite<K, V>) EMPTY_MAP_FIELD; + } + + /** Creates a new MapFieldLite instance. */ + public static <K, V> MapFieldLite<K, V> newMapField() { + return new MapFieldLite<K, V>(new LinkedHashMap<K, V>()); + } + + /** Gets the content of this MapField as a read-only Map. */ + public Map<K, V> getMap() { + return Collections.unmodifiableMap(mapData); + } + + /** Gets a mutable Map view of this MapField. */ + public Map<K, V> getMutableMap() { + return mapData; + } + + public void mergeFrom(MapFieldLite<K, V> 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 <K, V> boolean equals(Map<K, V> a, Map<K, V> b) { + if (a == b) { + return true; + } + if (a.size() != b.size()) { + return false; + } + for (Map.Entry<K, V> 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<K, V> other = (MapFieldLite<K, V>) object; + return equals(mapData, other.mapData); + } + + private static int calculateHashCodeForObject(Object a) { + if (a instanceof byte[]) { + return LiteralByteString.hashCode((byte[]) a); + } + // Enums should be stored as integers internally. + if (a instanceof EnumLite) { + throw new UnsupportedOperationException(); + } + 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 <K, V> int calculateHashCodeForMap(Map<K, V> a) { + int result = 0; + for (Map.Entry<K, V> 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 <K, V> Map<K, V> copy(Map<K, V> map) { + Map<K, V> result = new LinkedHashMap<K, V>(); + for (Map.Entry<K, V> entry : map.entrySet()) { + result.put(entry.getKey(), (V) copy(entry.getValue())); + } + return result; + } + + /** Returns a deep copy of this map field. */ + public MapFieldLite<K, V> copy() { + return new MapFieldLite<K, V>(copy(mapData)); + } + + /** + * Makes this field immutable. All subsequent modifications will throw an + * {@link UnsupportedOperationException}. + */ + public void makeImmutable() { + isMutable = false; + } + + /** + * Returns whether this field can be modified. + */ + public boolean isMutable() { + return isMutable; + } + + @Override + public void ensureMutable() { + if (!isMutable()) { + throw new UnsupportedOperationException(); + } + } + + /** + * An internal map that checks for mutability before delegating. + */ + static class MutatabilityAwareMap<K, V> implements Map<K, V> { + private final MutabilityOracle mutabilityOracle; + private final Map<K, V> delegate; + + MutatabilityAwareMap(MutabilityOracle mutabilityOracle, Map<K, V> delegate) { + this.mutabilityOracle = mutabilityOracle; + this.delegate = delegate; + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return delegate.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return delegate.containsValue(value); + } + + @Override + public V get(Object key) { + return delegate.get(key); + } + + @Override + public V put(K key, V value) { + mutabilityOracle.ensureMutable(); + return delegate.put(key, value); + } + + @Override + public V remove(Object key) { + mutabilityOracle.ensureMutable(); + return delegate.remove(key); + } + + @Override + public void putAll(Map<? extends K, ? extends V> m) { + mutabilityOracle.ensureMutable(); + delegate.putAll(m); + } + + @Override + public void clear() { + mutabilityOracle.ensureMutable(); + delegate.clear(); + } + + @Override + public Set<K> keySet() { + return new MutatabilityAwareSet<K>(mutabilityOracle, delegate.keySet()); + } + + @Override + public Collection<V> values() { + return new MutatabilityAwareCollection<V>(mutabilityOracle, delegate.values()); + } + + @Override + public Set<java.util.Map.Entry<K, V>> entrySet() { + return new MutatabilityAwareSet<Entry<K, V>>(mutabilityOracle, delegate.entrySet()); + } + + @Override + public boolean equals(Object o) { + return delegate.equals(o); + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } + + @Override + public String toString() { + return delegate.toString(); + } + } + + /** + * An internal collection that checks for mutability before delegating. + */ + private static class MutatabilityAwareCollection<E> implements Collection<E> { + private final MutabilityOracle mutabilityOracle; + private final Collection<E> delegate; + + MutatabilityAwareCollection(MutabilityOracle mutabilityOracle, Collection<E> delegate) { + this.mutabilityOracle = mutabilityOracle; + this.delegate = delegate; + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return delegate.contains(o); + } + + @Override + public Iterator<E> iterator() { + return new MutatabilityAwareIterator<E>(mutabilityOracle, delegate.iterator()); + } + + @Override + public Object[] toArray() { + return delegate.toArray(); + } + + @Override + public <T> T[] toArray(T[] a) { + return delegate.toArray(a); + } + + @Override + public boolean add(E e) { + // Unsupported operation in the delegate. + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(Object o) { + mutabilityOracle.ensureMutable(); + return delegate.remove(o); + } + + @Override + public boolean containsAll(Collection<?> c) { + return delegate.containsAll(c); + } + + @Override + public boolean addAll(Collection<? extends E> c) { + // Unsupported operation in the delegate. + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(Collection<?> c) { + mutabilityOracle.ensureMutable(); + return delegate.removeAll(c); + } + + @Override + public boolean retainAll(Collection<?> c) { + mutabilityOracle.ensureMutable(); + return delegate.retainAll(c); + } + + @Override + public void clear() { + mutabilityOracle.ensureMutable(); + delegate.clear(); + } + + @Override + public boolean equals(Object o) { + return delegate.equals(o); + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } + + @Override + public String toString() { + return delegate.toString(); + } + } + + /** + * An internal set that checks for mutability before delegating. + */ + private static class MutatabilityAwareSet<E> implements Set<E> { + private final MutabilityOracle mutabilityOracle; + private final Set<E> delegate; + + MutatabilityAwareSet(MutabilityOracle mutabilityOracle, Set<E> delegate) { + this.mutabilityOracle = mutabilityOracle; + this.delegate = delegate; + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return delegate.contains(o); + } + + @Override + public Iterator<E> iterator() { + return new MutatabilityAwareIterator<E>(mutabilityOracle, delegate.iterator()); + } + + @Override + public Object[] toArray() { + return delegate.toArray(); + } + + @Override + public <T> T[] toArray(T[] a) { + return delegate.toArray(a); + } + + @Override + public boolean add(E e) { + mutabilityOracle.ensureMutable(); + return delegate.add(e); + } + + @Override + public boolean remove(Object o) { + mutabilityOracle.ensureMutable(); + return delegate.remove(o); + } + + @Override + public boolean containsAll(Collection<?> c) { + return delegate.containsAll(c); + } + + @Override + public boolean addAll(Collection<? extends E> c) { + mutabilityOracle.ensureMutable(); + return delegate.addAll(c); + } + + @Override + public boolean retainAll(Collection<?> c) { + mutabilityOracle.ensureMutable(); + return delegate.retainAll(c); + } + + @Override + public boolean removeAll(Collection<?> c) { + mutabilityOracle.ensureMutable(); + return delegate.removeAll(c); + } + + @Override + public void clear() { + mutabilityOracle.ensureMutable(); + delegate.clear(); + } + + @Override + public boolean equals(Object o) { + return delegate.equals(o); + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } + + @Override + public String toString() { + return delegate.toString(); + } + } + + /** + * An internal iterator that checks for mutability before delegating. + */ + private static class MutatabilityAwareIterator<E> implements Iterator<E> { + private final MutabilityOracle mutabilityOracle; + private final Iterator<E> delegate; + + MutatabilityAwareIterator(MutabilityOracle mutabilityOracle, Iterator<E> delegate) { + this.mutabilityOracle = mutabilityOracle; + this.delegate = delegate; + } + + @Override + public boolean hasNext() { + return delegate.hasNext(); + } + + @Override + public E next() { + return delegate.next(); + } + + @Override + public void remove() { + mutabilityOracle.ensureMutable(); + delegate.remove(); + } + + @Override + public boolean equals(Object obj) { + return delegate.equals(obj); + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } + + @Override + public String toString() { + return delegate.toString(); + } + } +} diff --git a/java/core/src/main/java/com/google/protobuf/Message.java b/java/core/src/main/java/com/google/protobuf/Message.java new file mode 100644 index 00000000..9516d71f --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/Message.java @@ -0,0 +1,266 @@ +// 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. + +// TODO(kenton): Use generics? E.g. Builder<BuilderType extends Builder>, then +// mergeFrom*() could return BuilderType for better type-safety. + +package com.google.protobuf; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; + +/** + * Abstract interface implemented by Protocol Message objects. + * <p> + * See also {@link MessageLite}, which defines most of the methods that typical + * users care about. {@link Message} adds to it methods that are not available + * in the "lite" runtime. The biggest added features are introspection and + * reflection -- i.e., getting descriptors for the message type and accessing + * the field values dynamically. + * + * @author kenton@google.com Kenton Varda + */ +public interface Message extends MessageLite, MessageOrBuilder { + + // (From MessageLite, re-declared here only for return type covariance.) + Parser<? extends Message> getParserForType(); + + + // ----------------------------------------------------------------- + // Comparison and hashing + + /** + * Compares the specified object with this message for equality. Returns + * {@code true} if the given object is a message of the same type (as + * defined by {@code getDescriptorForType()}) and has identical values for + * all of its fields. Subclasses must implement this; inheriting + * {@code Object.equals()} is incorrect. + * + * @param other object to be compared for equality with this message + * @return {@code true} if the specified object is equal to this message + */ + @Override + boolean equals(Object other); + + /** + * Returns the hash code value for this message. The hash code of a message + * should mix the message's type (object identity of the descriptor) with its + * contents (known and unknown field values). Subclasses must implement this; + * inheriting {@code Object.hashCode()} is incorrect. + * + * @return the hash code value for this message + * @see Map#hashCode() + */ + @Override + int hashCode(); + + // ----------------------------------------------------------------- + // Convenience methods. + + /** + * Converts the message to a string in protocol buffer text format. This is + * just a trivial wrapper around {@link + * TextFormat#printToString(MessageOrBuilder)}. + */ + @Override + String toString(); + + // ================================================================= + // Builders + + // (From MessageLite, re-declared here only for return type covariance.) + Builder newBuilderForType(); + Builder toBuilder(); + + /** + * Abstract interface implemented by Protocol Message builders. + */ + interface Builder extends MessageLite.Builder, MessageOrBuilder { + // (From MessageLite.Builder, re-declared here only for return type + // covariance.) + Builder clear(); + + /** + * Merge {@code other} into the message being built. {@code other} must + * have the exact same type as {@code this} (i.e. + * {@code getDescriptorForType() == other.getDescriptorForType()}). + * + * Merging occurs as follows. For each field:<br> + * * For singular primitive fields, if the field is set in {@code other}, + * then {@code other}'s value overwrites the value in this message.<br> + * * For singular message fields, if the field is set in {@code other}, + * it is merged into the corresponding sub-message of this message + * using the same merging rules.<br> + * * For repeated fields, the elements in {@code other} are concatenated + * with the elements in this message. + * * For oneof groups, if the other message has one of the fields set, + * the group of this message is cleared and replaced by the field + * of the other message, so that the oneof constraint is preserved. + * + * This is equivalent to the {@code Message::MergeFrom} method in C++. + */ + Builder mergeFrom(Message other); + + // (From MessageLite.Builder, re-declared here only for return type + // covariance.) + Message build(); + Message buildPartial(); + Builder clone(); + Builder mergeFrom(CodedInputStream input) throws IOException; + Builder mergeFrom(CodedInputStream input, + ExtensionRegistryLite extensionRegistry) + throws IOException; + + /** + * Get the message's type's descriptor. + * See {@link Message#getDescriptorForType()}. + */ + Descriptors.Descriptor getDescriptorForType(); + + /** + * Create a Builder for messages of the appropriate type for the given + * field. Messages built with this can then be passed to setField(), + * setRepeatedField(), or addRepeatedField(). + */ + Builder newBuilderForField(Descriptors.FieldDescriptor field); + + /** + * Get a nested builder instance for the given field. + * <p> + * 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. + * <p> + * 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. + * <p> + * NOTE: implementations that do not support nested builders will throw + * <code>UnsupportedOperationException</code>. + */ + Builder getFieldBuilder(Descriptors.FieldDescriptor field); + + /** + * Get a nested builder instance for the given repeated field instance. + * <p> + * 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. + * <p> + * 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. + * <p> + * NOTE: implementations that do not support nested builders will throw + * <code>UnsupportedOperationException</code>. + */ + 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 + * {@link Message#getField(Descriptors.FieldDescriptor)} would return. + */ + Builder setField(Descriptors.FieldDescriptor field, Object value); + + /** + * Clears the field. This is exactly equivalent to calling the generated + * "clear" accessor method corresponding to the field. + */ + Builder clearField(Descriptors.FieldDescriptor field); + + /** + * Clears the oneof. This is exactly equivalent to calling the generated + * "clear" accessor method corresponding to the oneof. + */ + Builder clearOneof(Descriptors.OneofDescriptor oneof); + + /** + * Sets an element of a repeated field to the given value. The value must + * be of the correct type for this field, i.e. the same type that + * {@link Message#getRepeatedField(Descriptors.FieldDescriptor,int)} would + * return. + * @throws IllegalArgumentException The field is not a repeated field, or + * {@code field.getContainingType() != getDescriptorForType()}. + */ + Builder setRepeatedField(Descriptors.FieldDescriptor field, + int index, Object value); + + /** + * Like {@code setRepeatedField}, but appends the value as a new element. + * @throws IllegalArgumentException The field is not a repeated field, or + * {@code field.getContainingType() != getDescriptorForType()}. + */ + Builder addRepeatedField(Descriptors.FieldDescriptor field, Object value); + + /** Set the {@link UnknownFieldSet} for this message. */ + Builder setUnknownFields(UnknownFieldSet unknownFields); + + /** + * Merge some unknown fields into the {@link UnknownFieldSet} for this + * message. + */ + Builder mergeUnknownFields(UnknownFieldSet unknownFields); + + // --------------------------------------------------------------- + // Convenience methods. + + // (From MessageLite.Builder, re-declared here only for return type + // covariance.) + Builder mergeFrom(ByteString data) throws InvalidProtocolBufferException; + Builder mergeFrom(ByteString data, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException; + Builder mergeFrom(byte[] data) throws InvalidProtocolBufferException; + Builder mergeFrom(byte[] data, int off, int len) + throws InvalidProtocolBufferException; + Builder mergeFrom(byte[] data, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException; + Builder mergeFrom(byte[] data, int off, int len, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException; + Builder mergeFrom(InputStream input) throws IOException; + Builder mergeFrom(InputStream input, + ExtensionRegistryLite extensionRegistry) + throws IOException; + boolean mergeDelimitedFrom(InputStream input) + throws IOException; + boolean mergeDelimitedFrom(InputStream input, + ExtensionRegistryLite extensionRegistry) + throws IOException; + } +} diff --git a/java/core/src/main/java/com/google/protobuf/MessageLite.java b/java/core/src/main/java/com/google/protobuf/MessageLite.java new file mode 100644 index 00000000..798b7943 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/MessageLite.java @@ -0,0 +1,320 @@ +// 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. + +// TODO(kenton): Use generics? E.g. Builder<BuilderType extends Builder>, then +// mergeFrom*() could return BuilderType for better type-safety. + +package com.google.protobuf; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Abstract interface implemented by Protocol Message objects. + * + * <p>This interface is implemented by all protocol message objects. Non-lite + * messages additionally implement the Message interface, which is a subclass + * of MessageLite. Use MessageLite instead when you only need the subset of + * features which it supports -- namely, nothing that uses descriptors or + * reflection. You can instruct the protocol compiler to generate classes + * which implement only MessageLite, not the full Message interface, by adding + * the follow line to the .proto file: + * <pre> + * option optimize_for = LITE_RUNTIME; + * </pre> + * + * <p>This is particularly useful on resource-constrained systems where the + * full protocol buffers runtime library is too big. + * + * <p>Note that on non-constrained systems (e.g. servers) when you need to link + * in lots of protocol definitions, a better way to reduce total code footprint + * is to use {@code optimize_for = CODE_SIZE}. This will make the generated + * code smaller while still supporting all the same features (at the expense of + * speed). {@code optimize_for = LITE_RUNTIME} is best when you only have a + * small number of message types linked into your binary, in which case the + * size of the protocol buffers runtime itself is the biggest problem. + * + * @author kenton@google.com Kenton Varda + */ +public interface MessageLite extends MessageLiteOrBuilder { + + + /** + * Serializes the message and writes it to {@code output}. This does not + * flush or close the stream. + */ + void writeTo(CodedOutputStream output) throws IOException; + + /** + * Get the number of bytes required to encode this message. The result + * is only computed on the first call and memoized after that. + */ + int getSerializedSize(); + + + /** + * Gets the parser for a message of the same type as this message. + */ + Parser<? extends MessageLite> getParserForType(); + + // ----------------------------------------------------------------- + // Convenience methods. + + /** + * Serializes the message to a {@code ByteString} and returns it. This is + * just a trivial wrapper around + * {@link #writeTo(CodedOutputStream)}. + */ + ByteString toByteString(); + + /** + * Serializes the message to a {@code byte} array and returns it. This is + * just a trivial wrapper around + * {@link #writeTo(CodedOutputStream)}. + */ + byte[] toByteArray(); + + /** + * Serializes the message and writes it to {@code output}. This is just a + * trivial wrapper around {@link #writeTo(CodedOutputStream)}. This does + * not flush or close the stream. + * <p> + * NOTE: Protocol Buffers are not self-delimiting. Therefore, if you write + * any more data to the stream after the message, you must somehow ensure + * that the parser on the receiving end does not interpret this as being + * part of the protocol message. This can be done e.g. by writing the size + * of the message before the data, then making sure to limit the input to + * that size on the receiving end (e.g. by wrapping the InputStream in one + * which limits the input). Alternatively, just use + * {@link #writeDelimitedTo(OutputStream)}. + */ + void writeTo(OutputStream output) throws IOException; + + /** + * Like {@link #writeTo(OutputStream)}, but writes the size of the message + * as a varint before writing the data. This allows more data to be written + * to the stream after the message without the need to delimit the message + * data yourself. Use {@link Builder#mergeDelimitedFrom(InputStream)} (or + * the static method {@code YourMessageType.parseDelimitedFrom(InputStream)}) + * to parse messages written by this method. + */ + void writeDelimitedTo(OutputStream output) throws IOException; + + + // ================================================================= + // Builders + + /** + * Constructs a new builder for a message of the same type as this message. + */ + Builder newBuilderForType(); + + /** + * Constructs a builder initialized with the current message. Use this to + * derive a new message from the current one. + */ + Builder toBuilder(); + + /** + * Abstract interface implemented by Protocol Message builders. + */ + interface Builder extends MessageLiteOrBuilder, Cloneable { + /** Resets all fields to their default values. */ + Builder clear(); + + /** + * Constructs the message based on the state of the Builder. Subsequent + * changes to the Builder will not affect the returned message. + * @throws UninitializedMessageException The message is missing one or more + * required fields (i.e. {@link #isInitialized()} returns false). + * Use {@link #buildPartial()} to bypass this check. + */ + MessageLite build(); + + /** + * Like {@link #build()}, but does not throw an exception if the message + * is missing required fields. Instead, a partial message is returned. + * Subsequent changes to the Builder will not affect the returned message. + */ + MessageLite buildPartial(); + + /** + * Clones the Builder. + * @see Object#clone() + */ + Builder clone(); + + /** + * Parses a message of this type from the input and merges it with this + * message. + * + * <p>Warning: This does not verify that all required fields are present in + * the input message. If you call {@link #build()} without setting all + * required fields, it will throw an {@link UninitializedMessageException}, + * which is a {@code RuntimeException} and thus might not be caught. There + * are a few good ways to deal with this: + * <ul> + * <li>Call {@link #isInitialized()} to verify that all required fields + * are set before building. + * <li>Use {@code buildPartial()} to build, which ignores missing + * required fields. + * </ul> + * + * <p>Note: The caller should call + * {@link CodedInputStream#checkLastTagWas(int)} after calling this to + * verify that the last tag seen was the appropriate end-group tag, + * or zero for EOF. + */ + Builder mergeFrom(CodedInputStream input) throws IOException; + + /** + * Like {@link Builder#mergeFrom(CodedInputStream)}, but also + * parses extensions. The extensions that you want to be able to parse + * must be registered in {@code extensionRegistry}. Extensions not in + * the registry will be treated as unknown fields. + */ + Builder mergeFrom(CodedInputStream input, + ExtensionRegistryLite extensionRegistry) + throws IOException; + + // --------------------------------------------------------------- + // Convenience methods. + + /** + * Parse {@code data} as a message of this type and merge it with the + * message being built. This is just a small wrapper around + * {@link #mergeFrom(CodedInputStream)}. + * + * @return this + */ + Builder mergeFrom(ByteString data) throws InvalidProtocolBufferException; + + /** + * Parse {@code data} as a message of this type and merge it with the + * message being built. This is just a small wrapper around + * {@link #mergeFrom(CodedInputStream,ExtensionRegistryLite)}. + * + * @return this + */ + Builder mergeFrom(ByteString data, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException; + + /** + * Parse {@code data} as a message of this type and merge it with the + * message being built. This is just a small wrapper around + * {@link #mergeFrom(CodedInputStream)}. + * + * @return this + */ + Builder mergeFrom(byte[] data) throws InvalidProtocolBufferException; + + /** + * Parse {@code data} as a message of this type and merge it with the + * message being built. This is just a small wrapper around + * {@link #mergeFrom(CodedInputStream)}. + * + * @return this + */ + Builder mergeFrom(byte[] data, int off, int len) + throws InvalidProtocolBufferException; + + /** + * Parse {@code data} as a message of this type and merge it with the + * message being built. This is just a small wrapper around + * {@link #mergeFrom(CodedInputStream,ExtensionRegistryLite)}. + * + * @return this + */ + Builder mergeFrom(byte[] data, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException; + + /** + * Parse {@code data} as a message of this type and merge it with the + * message being built. This is just a small wrapper around + * {@link #mergeFrom(CodedInputStream,ExtensionRegistryLite)}. + * + * @return this + */ + Builder mergeFrom(byte[] data, int off, int len, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException; + + /** + * Parse a message of this type from {@code input} and merge it with the + * message being built. This is just a small wrapper around + * {@link #mergeFrom(CodedInputStream)}. Note that this method always + * reads the <i>entire</i> input (unless it throws an exception). If you + * want it to stop earlier, you will need to wrap your input in some + * wrapper stream that limits reading. Or, use + * {@link MessageLite#writeDelimitedTo(OutputStream)} to write your message + * and {@link #mergeDelimitedFrom(InputStream)} to read it. + * <p> + * Despite usually reading the entire input, this does not close the stream. + * + * @return this + */ + Builder mergeFrom(InputStream input) throws IOException; + + /** + * Parse a message of this type from {@code input} and merge it with the + * message being built. This is just a small wrapper around + * {@link #mergeFrom(CodedInputStream,ExtensionRegistryLite)}. + * + * @return this + */ + Builder mergeFrom(InputStream input, + ExtensionRegistryLite extensionRegistry) + throws IOException; + + /** + * Like {@link #mergeFrom(InputStream)}, but does not read until EOF. + * Instead, the size of the message (encoded as a varint) is read first, + * then the message data. Use + * {@link MessageLite#writeDelimitedTo(OutputStream)} to write messages in + * this format. + * + * @return True if successful, or false if the stream is at EOF when the + * method starts. Any other error (including reaching EOF during + * parsing) will cause an exception to be thrown. + */ + boolean mergeDelimitedFrom(InputStream input) + throws IOException; + + /** + * Like {@link #mergeDelimitedFrom(InputStream)} but supporting extensions. + */ + boolean mergeDelimitedFrom(InputStream input, + ExtensionRegistryLite extensionRegistry) + throws IOException; + } +} diff --git a/java/core/src/main/java/com/google/protobuf/MessageLiteOrBuilder.java b/java/core/src/main/java/com/google/protobuf/MessageLiteOrBuilder.java new file mode 100644 index 00000000..818386ce --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/MessageLiteOrBuilder.java @@ -0,0 +1,60 @@ +// 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; + +/** + * Base interface for methods common to {@link MessageLite} + * and {@link MessageLite.Builder} to provide type equivalency. + * + * @author jonp@google.com (Jon Perlow) + */ +public interface MessageLiteOrBuilder { + /** + * Get an instance of the type with no fields set. Because no fields are set, + * all getters for singular fields will return default values and repeated + * fields will appear empty. + * This may or may not be a singleton. This differs from the + * {@code getDefaultInstance()} method of generated message classes in that + * this method is an abstract method of the {@code MessageLite} interface + * whereas {@code getDefaultInstance()} is a static method of a specific + * class. They return the same thing. + */ + MessageLite getDefaultInstanceForType(); + + /** + * Returns true if all required fields in the message and all embedded + * messages are set, false otherwise. + * + * <p>See also: {@link MessageOrBuilder#getInitializationErrorString()} + */ + boolean isInitialized(); + +} diff --git a/java/core/src/main/java/com/google/protobuf/MessageLiteToString.java b/java/core/src/main/java/com/google/protobuf/MessageLiteToString.java new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/MessageLiteToString.java diff --git a/java/core/src/main/java/com/google/protobuf/MessageOrBuilder.java b/java/core/src/main/java/com/google/protobuf/MessageOrBuilder.java new file mode 100644 index 00000000..f0fc4859 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/MessageOrBuilder.java @@ -0,0 +1,143 @@ +// 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.List; +import java.util.Map; + +/** + * Base interface for methods common to {@link Message} and + * {@link Message.Builder} to provide type equivalency. + * + * @author jonp@google.com (Jon Perlow) + */ +public interface MessageOrBuilder extends MessageLiteOrBuilder { + + // (From MessageLite, re-declared here only for return type covariance.) + //@Override (Java 1.6 override semantics, but we must support 1.5) + Message getDefaultInstanceForType(); + + /** + * Returns a list of field paths (e.g. "foo.bar.baz") of required fields + * which are not set in this message. You should call + * {@link MessageLiteOrBuilder#isInitialized()} first to check if there + * are any missing fields, as that method is likely to be much faster + * than this one even when the message is fully-initialized. + */ + List<String> findInitializationErrors(); + + /** + * Returns a comma-delimited list of required fields which are not set + * in this message object. You should call + * {@link MessageLiteOrBuilder#isInitialized()} first to check if there + * are any missing fields, as that method is likely to be much faster + * than this one even when the message is fully-initialized. + */ + String getInitializationErrorString(); + + /** + * Get the message's type's descriptor. This differs from the + * {@code getDescriptor()} method of generated message classes in that + * this method is an abstract method of the {@code Message} interface + * whereas {@code getDescriptor()} is a static method of a specific class. + * They return the same thing. + */ + Descriptors.Descriptor getDescriptorForType(); + + /** + * Returns a collection of all the fields in this message which are set + * and their corresponding values. A singular ("required" or "optional") + * field is set iff hasField() returns true for that field. A "repeated" + * field is set iff getRepeatedFieldCount() is greater than zero. The + * values are exactly what would be returned by calling + * {@link #getField(Descriptors.FieldDescriptor)} for each field. The map + * is guaranteed to be a sorted map, so iterating over it will return fields + * in order by field number. + * <br> + * If this is for a builder, the returned map may or may not reflect future + * changes to the builder. Either way, the returned map is itself + * unmodifiable. + */ + Map<Descriptors.FieldDescriptor, Object> getAllFields(); + + /** + * Returns true if the given oneof is set. + * @throws IllegalArgumentException if + * {@code oneof.getContainingType() != getDescriptorForType()}. + */ + boolean hasOneof(Descriptors.OneofDescriptor oneof); + + /** + * Obtains the FieldDescriptor if the given oneof is set. Returns null + * if no field is set. + */ + Descriptors.FieldDescriptor getOneofFieldDescriptor( + Descriptors.OneofDescriptor oneof); + + /** + * Returns true if the given field is set. This is exactly equivalent to + * calling the generated "has" accessor method corresponding to the field. + * @throws IllegalArgumentException The field is a repeated field, or + * {@code field.getContainingType() != getDescriptorForType()}. + */ + boolean hasField(Descriptors.FieldDescriptor field); + + /** + * Obtains the value of the given field, or the default value if it is + * not set. For primitive fields, the boxed primitive value is returned. + * For enum fields, the EnumValueDescriptor for the value is returned. For + * embedded message fields, the sub-message is returned. For repeated + * fields, a java.util.List is returned. + */ + Object getField(Descriptors.FieldDescriptor field); + + /** + * Gets the number of elements of a repeated field. This is exactly + * equivalent to calling the generated "Count" accessor method corresponding + * to the field. + * @throws IllegalArgumentException The field is not a repeated field, or + * {@code field.getContainingType() != getDescriptorForType()}. + */ + int getRepeatedFieldCount(Descriptors.FieldDescriptor field); + + /** + * Gets an element of a repeated field. For primitive fields, the boxed + * primitive value is returned. For enum fields, the EnumValueDescriptor + * for the value is returned. For embedded message fields, the sub-message + * is returned. + * @throws IllegalArgumentException The field is not a repeated field, or + * {@code field.getContainingType() != getDescriptorForType()}. + */ + Object getRepeatedField(Descriptors.FieldDescriptor field, int index); + + /** Get the {@link UnknownFieldSet} for this message. */ + UnknownFieldSet getUnknownFields(); +} diff --git a/java/core/src/main/java/com/google/protobuf/MessageReflection.java b/java/core/src/main/java/com/google/protobuf/MessageReflection.java new file mode 100644 index 00000000..de4bfd3e --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/MessageReflection.java @@ -0,0 +1,952 @@ +// 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 java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +/** + * Reflection utility methods shared by both mutable and immutable messages. + * + * @author liujisi@google.com (Pherl Liu) + */ +class MessageReflection { + + static void writeMessageTo( + Message message, + Map<FieldDescriptor, Object> fields, + CodedOutputStream output, + boolean alwaysWriteRequiredFields) + throws IOException { + final boolean isMessageSet = + message.getDescriptorForType().getOptions().getMessageSetWireFormat(); + if (alwaysWriteRequiredFields) { + fields = new TreeMap<FieldDescriptor, Object>(fields); + for (final FieldDescriptor field : + message.getDescriptorForType().getFields()) { + if (field.isRequired() && !fields.containsKey(field)) { + fields.put(field, message.getField(field)); + } + } + } + for (final Map.Entry<Descriptors.FieldDescriptor, Object> entry : + fields.entrySet()) { + final Descriptors.FieldDescriptor field = entry.getKey(); + final Object value = entry.getValue(); + if (isMessageSet && field.isExtension() && + field.getType() == Descriptors.FieldDescriptor.Type.MESSAGE && + !field.isRepeated()) { + output.writeMessageSetExtension(field.getNumber(), (Message) value); + } else { + FieldSet.writeField(field, value, output); + } + } + + final UnknownFieldSet unknownFields = message.getUnknownFields(); + if (isMessageSet) { + unknownFields.writeAsMessageSetTo(output); + } else { + unknownFields.writeTo(output); + } + } + + static int getSerializedSize( + Message message, + Map<FieldDescriptor, Object> fields) { + int size = 0; + final boolean isMessageSet = + message.getDescriptorForType().getOptions().getMessageSetWireFormat(); + + for (final Map.Entry<Descriptors.FieldDescriptor, Object> entry : + fields.entrySet()) { + final Descriptors.FieldDescriptor field = entry.getKey(); + final Object value = entry.getValue(); + if (isMessageSet && field.isExtension() && + field.getType() == Descriptors.FieldDescriptor.Type.MESSAGE && + !field.isRepeated()) { + size += CodedOutputStream.computeMessageSetExtensionSize( + field.getNumber(), (Message) value); + } else { + size += FieldSet.computeFieldSize(field, value); + } + } + + final UnknownFieldSet unknownFields = message.getUnknownFields(); + if (isMessageSet) { + size += unknownFields.getSerializedSizeAsMessageSet(); + } else { + size += unknownFields.getSerializedSize(); + } + return size; + } + + static String delimitWithCommas(List<String> parts) { + StringBuilder result = new StringBuilder(); + for (String part : parts) { + if (result.length() > 0) { + result.append(", "); + } + result.append(part); + } + return result.toString(); + } + + @SuppressWarnings("unchecked") + static boolean isInitialized(MessageOrBuilder message) { + // Check that all required fields are present. + for (final Descriptors.FieldDescriptor field : message + .getDescriptorForType() + .getFields()) { + if (field.isRequired()) { + if (!message.hasField(field)) { + return false; + } + } + } + + // Check that embedded messages are initialized. + for (final Map.Entry<Descriptors.FieldDescriptor, Object> entry : + message.getAllFields().entrySet()) { + final Descriptors.FieldDescriptor field = entry.getKey(); + if (field.getJavaType() == Descriptors.FieldDescriptor.JavaType.MESSAGE) { + if (field.isRepeated()) { + for (final Message element + : (List<Message>) entry.getValue()) { + if (!element.isInitialized()) { + return false; + } + } + } else { + if (!((Message) entry.getValue()).isInitialized()) { + return false; + } + } + } + } + + return true; + } + + private static String subMessagePrefix(final String prefix, + final Descriptors.FieldDescriptor field, + final int index) { + final StringBuilder result = new StringBuilder(prefix); + if (field.isExtension()) { + result.append('(') + .append(field.getFullName()) + .append(')'); + } else { + result.append(field.getName()); + } + if (index != -1) { + result.append('[') + .append(index) + .append(']'); + } + result.append('.'); + return result.toString(); + } + + private static void findMissingFields(final MessageOrBuilder message, + final String prefix, + final List<String> results) { + for (final Descriptors.FieldDescriptor field : + message.getDescriptorForType().getFields()) { + if (field.isRequired() && !message.hasField(field)) { + results.add(prefix + field.getName()); + } + } + + for (final Map.Entry<Descriptors.FieldDescriptor, Object> entry : + message.getAllFields().entrySet()) { + final Descriptors.FieldDescriptor field = entry.getKey(); + final Object value = entry.getValue(); + + if (field.getJavaType() == Descriptors.FieldDescriptor.JavaType.MESSAGE) { + if (field.isRepeated()) { + int i = 0; + for (final Object element : (List) value) { + findMissingFields((MessageOrBuilder) element, + subMessagePrefix(prefix, field, i++), + results); + } + } else { + if (message.hasField(field)) { + findMissingFields((MessageOrBuilder) value, + subMessagePrefix(prefix, field, -1), + results); + } + } + } + } + } + + /** + * Populates {@code this.missingFields} with the full "path" of each missing + * required field in the given message. + */ + static List<String> findMissingFields( + final MessageOrBuilder message) { + final List<String> results = new ArrayList<String>(); + findMissingFields(message, "", results); + return results; + } + + static interface MergeTarget { + enum ContainerType { + MESSAGE, EXTENSION_SET + } + + /** + * Returns the descriptor for the target. + */ + public Descriptors.Descriptor getDescriptorForType(); + + public ContainerType getContainerType(); + + public ExtensionRegistry.ExtensionInfo findExtensionByName( + ExtensionRegistry registry, String name); + + public ExtensionRegistry.ExtensionInfo findExtensionByNumber( + ExtensionRegistry registry, Descriptors.Descriptor containingType, + int fieldNumber); + + /** + * Obtains the value of the given field, or the default value if it is not + * set. For primitive fields, the boxed primitive value is returned. For + * enum fields, the EnumValueDescriptor for the value is returned. For + * embedded message fields, the sub-message is returned. For repeated + * fields, a java.util.List is returned. + */ + public Object getField(Descriptors.FieldDescriptor field); + + /** + * Returns true if the given field is set. This is exactly equivalent to + * calling the generated "has" accessor method corresponding to the field. + * + * @throws IllegalArgumentException The field is a repeated field, or {@code + * field.getContainingType() != getDescriptorForType()}. + */ + boolean hasField(Descriptors.FieldDescriptor field); + + /** + * Sets a field to the given value. The value must be of the correct type + * for this field, i.e. the same type that + * {@link Message#getField(Descriptors.FieldDescriptor)} + * would return. + */ + MergeTarget setField(Descriptors.FieldDescriptor field, Object value); + + /** + * Clears the field. This is exactly equivalent to calling the generated + * "clear" accessor method corresponding to the field. + */ + MergeTarget clearField(Descriptors.FieldDescriptor field); + + /** + * Sets an element of a repeated field to the given value. The value must + * be of the correct type for this field, i.e. the same type that {@link + * Message#getRepeatedField(Descriptors.FieldDescriptor, int)} would return. + * + * @throws IllegalArgumentException The field is not a repeated field, or + * {@code field.getContainingType() != + * getDescriptorForType()}. + */ + MergeTarget setRepeatedField(Descriptors.FieldDescriptor field, + int index, Object value); + + /** + * Like {@code setRepeatedField}, but appends the value as a new element. + * + * @throws IllegalArgumentException The field is not a repeated field, or + * {@code field.getContainingType() != + * getDescriptorForType()}. + */ + MergeTarget addRepeatedField(Descriptors.FieldDescriptor field, + Object value); + + /** + * Returns true if the given oneof is set. + * + * @throws IllegalArgumentException if + * {@code oneof.getContainingType() != getDescriptorForType()}. + */ + boolean hasOneof(Descriptors.OneofDescriptor oneof); + + /** + * Clears the oneof. This is exactly equivalent to calling the generated + * "clear" accessor method corresponding to the oneof. + */ + MergeTarget clearOneof(Descriptors.OneofDescriptor oneof); + + /** + * Obtains the FieldDescriptor if the given oneof is set. Returns null + * if no field is set. + */ + Descriptors.FieldDescriptor getOneofFieldDescriptor(Descriptors.OneofDescriptor oneof); + + /** + * Parse the input stream into a sub field group defined based on either + * FieldDescriptor or the default instance. + */ + Object parseGroup(CodedInputStream input, ExtensionRegistryLite registry, + Descriptors.FieldDescriptor descriptor, Message defaultInstance) + throws IOException; + + /** + * Parse the input stream into a sub field message defined based on either + * FieldDescriptor or the default instance. + */ + Object parseMessage(CodedInputStream input, ExtensionRegistryLite registry, + Descriptors.FieldDescriptor descriptor, Message defaultInstance) + throws IOException; + + /** + * Parse from a ByteString into a sub field message defined based on either + * FieldDescriptor or the default instance. There isn't a varint indicating + * the length of the message at the beginning of the input ByteString. + */ + Object parseMessageFromBytes( + ByteString bytes, ExtensionRegistryLite registry, + Descriptors.FieldDescriptor descriptor, Message defaultInstance) + throws IOException; + + /** + * Returns the UTF8 validation level for the field. + */ + WireFormat.Utf8Validation getUtf8Validation(Descriptors.FieldDescriptor + descriptor); + + /** + * Returns a new merge target for a sub-field. When defaultInstance is + * provided, it indicates the descriptor is for an extension type, and + * implementations should create a new instance from the defaultInstance + * prototype directly. + */ + MergeTarget newMergeTargetForField( + Descriptors.FieldDescriptor descriptor, + Message defaultInstance); + + /** + * Finishes the merge and returns the underlying object. + */ + Object finish(); + } + + static class BuilderAdapter implements MergeTarget { + + private final Message.Builder builder; + + public Descriptors.Descriptor getDescriptorForType() { + return builder.getDescriptorForType(); + } + + public BuilderAdapter(Message.Builder builder) { + this.builder = builder; + } + + public Object getField(Descriptors.FieldDescriptor field) { + return builder.getField(field); + } + + @Override + public boolean hasField(Descriptors.FieldDescriptor field) { + return builder.hasField(field); + } + + public MergeTarget setField(Descriptors.FieldDescriptor field, + Object value) { + builder.setField(field, value); + return this; + } + + public MergeTarget clearField(Descriptors.FieldDescriptor field) { + builder.clearField(field); + return this; + } + + public MergeTarget setRepeatedField( + Descriptors.FieldDescriptor field, int index, Object value) { + builder.setRepeatedField(field, index, value); + return this; + } + + public MergeTarget addRepeatedField( + Descriptors.FieldDescriptor field, Object value) { + builder.addRepeatedField(field, value); + return this; + } + + @Override + public boolean hasOneof(Descriptors.OneofDescriptor oneof) { + return builder.hasOneof(oneof); + } + + @Override + public MergeTarget clearOneof(Descriptors.OneofDescriptor oneof) { + builder.clearOneof(oneof); + return this; + } + + @Override + public Descriptors.FieldDescriptor getOneofFieldDescriptor(Descriptors.OneofDescriptor oneof) { + return builder.getOneofFieldDescriptor(oneof); + } + + public ContainerType getContainerType() { + return ContainerType.MESSAGE; + } + + public ExtensionRegistry.ExtensionInfo findExtensionByName( + ExtensionRegistry registry, String name) { + return registry.findImmutableExtensionByName(name); + } + + public ExtensionRegistry.ExtensionInfo findExtensionByNumber( + ExtensionRegistry registry, Descriptors.Descriptor containingType, + int fieldNumber) { + return registry.findImmutableExtensionByNumber(containingType, + fieldNumber); + } + + public Object parseGroup(CodedInputStream input, + ExtensionRegistryLite extensionRegistry, + Descriptors.FieldDescriptor field, Message defaultInstance) + throws IOException { + Message.Builder subBuilder; + // When default instance is not null. The field is an extension field. + if (defaultInstance != null) { + subBuilder = defaultInstance.newBuilderForType(); + } else { + subBuilder = builder.newBuilderForField(field); + } + if (!field.isRepeated()) { + Message originalMessage = (Message) getField(field); + if (originalMessage != null) { + subBuilder.mergeFrom(originalMessage); + } + } + input.readGroup(field.getNumber(), subBuilder, extensionRegistry); + return subBuilder.buildPartial(); + } + + public Object parseMessage(CodedInputStream input, + ExtensionRegistryLite extensionRegistry, + Descriptors.FieldDescriptor field, Message defaultInstance) + throws IOException { + Message.Builder subBuilder; + // When default instance is not null. The field is an extension field. + if (defaultInstance != null) { + subBuilder = defaultInstance.newBuilderForType(); + } else { + subBuilder = builder.newBuilderForField(field); + } + if (!field.isRepeated()) { + Message originalMessage = (Message) getField(field); + if (originalMessage != null) { + subBuilder.mergeFrom(originalMessage); + } + } + input.readMessage(subBuilder, extensionRegistry); + return subBuilder.buildPartial(); + } + + public Object parseMessageFromBytes(ByteString bytes, + ExtensionRegistryLite extensionRegistry, + Descriptors.FieldDescriptor field, Message defaultInstance) + throws IOException { + Message.Builder subBuilder; + // When default instance is not null. The field is an extension field. + if (defaultInstance != null) { + subBuilder = defaultInstance.newBuilderForType(); + } else { + subBuilder = builder.newBuilderForField(field); + } + if (!field.isRepeated()) { + Message originalMessage = (Message) getField(field); + if (originalMessage != null) { + subBuilder.mergeFrom(originalMessage); + } + } + subBuilder.mergeFrom(bytes, extensionRegistry); + return subBuilder.buildPartial(); + } + + public MergeTarget newMergeTargetForField(Descriptors.FieldDescriptor field, + Message defaultInstance) { + if (defaultInstance != null) { + return new BuilderAdapter( + defaultInstance.newBuilderForType()); + } else { + return new BuilderAdapter(builder.newBuilderForField(field)); + } + } + + public WireFormat.Utf8Validation + getUtf8Validation(Descriptors.FieldDescriptor descriptor) { + if (descriptor.needsUtf8Check()) { + return WireFormat.Utf8Validation.STRICT; + } + // TODO(liujisi): support lazy strings for repeated fields. + if (!descriptor.isRepeated() + && builder instanceof GeneratedMessage.Builder) { + return WireFormat.Utf8Validation.LAZY; + } + return WireFormat.Utf8Validation.LOOSE; + } + + public Object finish() { + return builder.buildPartial(); + } + } + + + static class ExtensionAdapter implements MergeTarget { + + private final FieldSet<Descriptors.FieldDescriptor> extensions; + + ExtensionAdapter(FieldSet<Descriptors.FieldDescriptor> extensions) { + this.extensions = extensions; + } + + public Descriptors.Descriptor getDescriptorForType() { + throw new UnsupportedOperationException( + "getDescriptorForType() called on FieldSet object"); + } + + public Object getField(Descriptors.FieldDescriptor field) { + return extensions.getField(field); + } + + public boolean hasField(Descriptors.FieldDescriptor field) { + return extensions.hasField(field); + } + + public MergeTarget setField(Descriptors.FieldDescriptor field, + Object value) { + extensions.setField(field, value); + return this; + } + + public MergeTarget clearField(Descriptors.FieldDescriptor field) { + extensions.clearField(field); + return this; + } + + public MergeTarget setRepeatedField( + Descriptors.FieldDescriptor field, int index, Object value) { + extensions.setRepeatedField(field, index, value); + return this; + } + + public MergeTarget addRepeatedField( + Descriptors.FieldDescriptor field, Object value) { + extensions.addRepeatedField(field, value); + return this; + } + + @Override + public boolean hasOneof(Descriptors.OneofDescriptor oneof) { + return false; + } + + @Override + public MergeTarget clearOneof(Descriptors.OneofDescriptor oneof) { + // Nothing to clear. + return this; + } + + @Override + public Descriptors.FieldDescriptor getOneofFieldDescriptor(Descriptors.OneofDescriptor oneof) { + return null; + } + + public ContainerType getContainerType() { + return ContainerType.EXTENSION_SET; + } + + public ExtensionRegistry.ExtensionInfo findExtensionByName( + ExtensionRegistry registry, String name) { + return registry.findImmutableExtensionByName(name); + } + + public ExtensionRegistry.ExtensionInfo findExtensionByNumber( + ExtensionRegistry registry, Descriptors.Descriptor containingType, + int fieldNumber) { + return registry.findImmutableExtensionByNumber(containingType, + fieldNumber); + } + + public Object parseGroup(CodedInputStream input, + ExtensionRegistryLite registry, Descriptors.FieldDescriptor field, + Message defaultInstance) throws IOException { + Message.Builder subBuilder = + defaultInstance.newBuilderForType(); + if (!field.isRepeated()) { + Message originalMessage = (Message) getField(field); + if (originalMessage != null) { + subBuilder.mergeFrom(originalMessage); + } + } + input.readGroup(field.getNumber(), subBuilder, registry); + return subBuilder.buildPartial(); + } + + public Object parseMessage(CodedInputStream input, + ExtensionRegistryLite registry, Descriptors.FieldDescriptor field, + Message defaultInstance) throws IOException { + Message.Builder subBuilder = + defaultInstance.newBuilderForType(); + if (!field.isRepeated()) { + Message originalMessage = (Message) getField(field); + if (originalMessage != null) { + subBuilder.mergeFrom(originalMessage); + } + } + input.readMessage(subBuilder, registry); + return subBuilder.buildPartial(); + } + + public Object parseMessageFromBytes(ByteString bytes, + ExtensionRegistryLite registry, Descriptors.FieldDescriptor field, + Message defaultInstance) throws IOException { + Message.Builder subBuilder = defaultInstance.newBuilderForType(); + if (!field.isRepeated()) { + Message originalMessage = (Message) getField(field); + if (originalMessage != null) { + subBuilder.mergeFrom(originalMessage); + } + } + subBuilder.mergeFrom(bytes, registry); + return subBuilder.buildPartial(); + } + + public MergeTarget newMergeTargetForField( + Descriptors.FieldDescriptor descriptor, Message defaultInstance) { + throw new UnsupportedOperationException( + "newMergeTargetForField() called on FieldSet object"); + } + + public WireFormat.Utf8Validation + getUtf8Validation(Descriptors.FieldDescriptor descriptor) { + if (descriptor.needsUtf8Check()) { + return WireFormat.Utf8Validation.STRICT; + } + // TODO(liujisi): support lazy strings for ExtesnsionSet. + return WireFormat.Utf8Validation.LOOSE; + } + + public Object finish() { + throw new UnsupportedOperationException( + "finish() called on FieldSet object"); + } + } + + /** + * Parses a single field into MergeTarget. The target can be Message.Builder, + * FieldSet or MutableMessage. + * + * Package-private because it is used by GeneratedMessage.ExtendableMessage. + * + * @param tag The tag, which should have already been read. + * @return {@code true} unless the tag is an end-group tag. + */ + static boolean mergeFieldFrom( + CodedInputStream input, + UnknownFieldSet.Builder unknownFields, + ExtensionRegistryLite extensionRegistry, + Descriptors.Descriptor type, + MergeTarget target, + int tag) throws IOException { + if (type.getOptions().getMessageSetWireFormat() && + tag == WireFormat.MESSAGE_SET_ITEM_TAG) { + mergeMessageSetExtensionFromCodedStream( + input, unknownFields, extensionRegistry, type, target); + return true; + } + + final int wireType = WireFormat.getTagWireType(tag); + final int fieldNumber = WireFormat.getTagFieldNumber(tag); + + final Descriptors.FieldDescriptor field; + Message defaultInstance = null; + + if (type.isExtensionNumber(fieldNumber)) { + // extensionRegistry may be either ExtensionRegistry or + // ExtensionRegistryLite. Since the type we are parsing is a full + // message, only a full ExtensionRegistry could possibly contain + // extensions of it. Otherwise we will treat the registry as if it + // were empty. + if (extensionRegistry instanceof ExtensionRegistry) { + final ExtensionRegistry.ExtensionInfo extension = + target.findExtensionByNumber((ExtensionRegistry) extensionRegistry, + type, fieldNumber); + if (extension == null) { + field = null; + } else { + field = extension.descriptor; + defaultInstance = extension.defaultInstance; + if (defaultInstance == null && + field.getJavaType() + == Descriptors.FieldDescriptor.JavaType.MESSAGE) { + throw new IllegalStateException( + "Message-typed extension lacked default instance: " + + field.getFullName()); + } + } + } else { + field = null; + } + } else if (target.getContainerType() == MergeTarget.ContainerType.MESSAGE) { + field = type.findFieldByNumber(fieldNumber); + } else { + field = null; + } + + boolean unknown = false; + boolean packed = false; + if (field == null) { + unknown = true; // Unknown field. + } else if (wireType == FieldSet.getWireFormatForFieldType( + field.getLiteType(), + false /* isPacked */)) { + packed = false; + } else if (field.isPackable() && + wireType == FieldSet.getWireFormatForFieldType( + field.getLiteType(), + true /* isPacked */)) { + packed = true; + } else { + unknown = true; // Unknown wire type. + } + + if (unknown) { // Unknown field or wrong wire type. Skip. + return unknownFields.mergeFieldFrom(tag, input); + } + + if (packed) { + final int length = input.readRawVarint32(); + final int limit = input.pushLimit(length); + if (field.getLiteType() == WireFormat.FieldType.ENUM) { + while (input.getBytesUntilLimit() > 0) { + final int rawValue = input.readEnum(); + 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); + } + } + } else { + while (input.getBytesUntilLimit() > 0) { + final Object value = WireFormat.readPrimitiveField( + input, field.getLiteType(), target.getUtf8Validation(field)); + target.addRepeatedField(field, value); + } + } + input.popLimit(limit); + } else { + final Object value; + switch (field.getType()) { + case GROUP: { + value = target + .parseGroup(input, extensionRegistry, field, defaultInstance); + break; + } + case MESSAGE: { + value = target + .parseMessage(input, extensionRegistry, field, defaultInstance); + break; + } + case ENUM: + final int rawValue = input.readEnum(); + 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: + value = WireFormat.readPrimitiveField( + input, field.getLiteType(), target.getUtf8Validation(field)); + break; + } + + if (field.isRepeated()) { + target.addRepeatedField(field, value); + } else { + target.setField(field, value); + } + } + + return true; + } + + /** + * Called by {@code #mergeFieldFrom()} to parse a MessageSet extension into + * MergeTarget. + */ + private static void mergeMessageSetExtensionFromCodedStream( + CodedInputStream input, + UnknownFieldSet.Builder unknownFields, + ExtensionRegistryLite extensionRegistry, + Descriptors.Descriptor type, + MergeTarget target) throws IOException { + + // The wire format for MessageSet is: + // message MessageSet { + // repeated group Item = 1 { + // required int32 typeId = 2; + // required bytes message = 3; + // } + // } + // "typeId" is the extension's field number. The extension can only be + // a message type, where "message" contains the encoded bytes of that + // message. + // + // In practice, we will probably never see a MessageSet item in which + // the message appears before the type ID, or where either field does not + // appear exactly once. However, in theory such cases are valid, so we + // should be prepared to accept them. + + int typeId = 0; + ByteString rawBytes = null; // If we encounter "message" before "typeId" + ExtensionRegistry.ExtensionInfo extension = null; + + // Read bytes from input, if we get it's type first then parse it eagerly, + // otherwise we store the raw bytes in a local variable. + while (true) { + final int tag = input.readTag(); + if (tag == 0) { + break; + } + + if (tag == WireFormat.MESSAGE_SET_TYPE_ID_TAG) { + typeId = input.readUInt32(); + if (typeId != 0) { + // extensionRegistry may be either ExtensionRegistry or + // ExtensionRegistryLite. Since the type we are parsing is a full + // message, only a full ExtensionRegistry could possibly contain + // extensions of it. Otherwise we will treat the registry as if it + // were empty. + if (extensionRegistry instanceof ExtensionRegistry) { + extension = target.findExtensionByNumber( + (ExtensionRegistry) extensionRegistry, type, typeId); + } + } + + } else if (tag == WireFormat.MESSAGE_SET_MESSAGE_TAG) { + if (typeId != 0) { + if (extension != null && + ExtensionRegistryLite.isEagerlyParseMessageSets()) { + // We already know the type, so we can parse directly from the + // input with no copying. Hooray! + eagerlyMergeMessageSetExtension( + input, extension, extensionRegistry, target); + rawBytes = null; + continue; + } + } + // We haven't seen a type ID yet or we want parse message lazily. + rawBytes = input.readBytes(); + + } else { // Unknown tag. Skip it. + if (!input.skipField(tag)) { + break; // End of group + } + } + } + input.checkLastTagWas(WireFormat.MESSAGE_SET_ITEM_END_TAG); + + // Process the raw bytes. + if (rawBytes != null && typeId != 0) { // Zero is not a valid type ID. + if (extension != null) { // We known the type + mergeMessageSetExtensionFromBytes( + rawBytes, extension, extensionRegistry, target); + } else { // We don't know how to parse this. Ignore it. + if (rawBytes != null) { + unknownFields.mergeField(typeId, UnknownFieldSet.Field.newBuilder() + .addLengthDelimited(rawBytes).build()); + } + } + } + } + + private static void mergeMessageSetExtensionFromBytes( + ByteString rawBytes, + ExtensionRegistry.ExtensionInfo extension, + ExtensionRegistryLite extensionRegistry, + MergeTarget target) throws IOException { + + Descriptors.FieldDescriptor field = extension.descriptor; + boolean hasOriginalValue = target.hasField(field); + + if (hasOriginalValue || ExtensionRegistryLite.isEagerlyParseMessageSets()) { + // If the field already exists, we just parse the field. + Object value = target.parseMessageFromBytes( + rawBytes, extensionRegistry,field, extension.defaultInstance); + target.setField(field, value); + } else { + // Use LazyField to load MessageSet lazily. + LazyField lazyField = new LazyField( + extension.defaultInstance, extensionRegistry, rawBytes); + target.setField(field, lazyField); + } + } + + private static void eagerlyMergeMessageSetExtension( + CodedInputStream input, + ExtensionRegistry.ExtensionInfo extension, + ExtensionRegistryLite extensionRegistry, + MergeTarget target) throws IOException { + Descriptors.FieldDescriptor field = extension.descriptor; + Object value = target.parseMessage(input, extensionRegistry, field, + extension.defaultInstance); + target.setField(field, value); + } +} diff --git a/java/core/src/main/java/com/google/protobuf/MutabilityOracle.java b/java/core/src/main/java/com/google/protobuf/MutabilityOracle.java new file mode 100644 index 00000000..82b723c9 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/MutabilityOracle.java @@ -0,0 +1,48 @@ +// 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; + +/** + * Verifies that an object is mutable, throwing if not. + */ +interface MutabilityOracle { + static final MutabilityOracle IMMUTABLE = new MutabilityOracle() { + @Override + public void ensureMutable() { + throw new UnsupportedOperationException(); + } + }; + + /** + * Throws an {@link UnsupportedOperationException} if not mutable. + */ + void ensureMutable(); +} diff --git a/java/core/src/main/java/com/google/protobuf/NioByteString.java b/java/core/src/main/java/com/google/protobuf/NioByteString.java new file mode 100644 index 00000000..f71e41b2 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/NioByteString.java @@ -0,0 +1,309 @@ +// 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.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.InvalidMarkException; +import java.nio.channels.Channels; +import java.nio.charset.Charset; +import java.util.Collections; +import java.util.List; + +/** + * A {@link ByteString} that wraps around a {@link ByteBuffer}. + */ +final class NioByteString extends ByteString.LeafByteString { + private final ByteBuffer buffer; + + NioByteString(ByteBuffer buffer) { + if (buffer == null) { + throw new NullPointerException("buffer"); + } + + this.buffer = buffer.slice(); + } + + // ================================================================= + // Serializable + + /** + * Magic method that lets us override serialization behavior. + */ + private Object writeReplace() { + return ByteString.copyFrom(buffer.slice()); + } + + /** + * Magic method that lets us override deserialization behavior. + */ + private void readObject(@SuppressWarnings("unused") ObjectInputStream in) throws IOException { + throw new InvalidObjectException("NioByteString instances are not to be serialized directly"); + } + + // ================================================================= + + @Override + public byte byteAt(int index) { + try { + return buffer.get(index); + } catch (ArrayIndexOutOfBoundsException e) { + throw e; + } catch (IndexOutOfBoundsException e) { + throw new ArrayIndexOutOfBoundsException(e.getMessage()); + } + } + + @Override + public int size() { + return buffer.remaining(); + } + + @Override + public ByteString substring(int beginIndex, int endIndex) { + try { + ByteBuffer slice = slice(beginIndex, endIndex); + return new NioByteString(slice); + } catch (ArrayIndexOutOfBoundsException e) { + throw e; + } catch (IndexOutOfBoundsException e) { + throw new ArrayIndexOutOfBoundsException(e.getMessage()); + } + } + + @Override + protected void copyToInternal( + byte[] target, int sourceOffset, int targetOffset, int numberToCopy) { + ByteBuffer slice = buffer.slice(); + slice.position(sourceOffset); + slice.get(target, targetOffset, numberToCopy); + } + + @Override + public void copyTo(ByteBuffer target) { + target.put(buffer.slice()); + } + + @Override + public void writeTo(OutputStream out) throws IOException { + writeToInternal(out, buffer.position(), buffer.remaining()); + } + + @Override + boolean equalsRange(ByteString other, int offset, int length) { + return substring(0, length).equals(other.substring(offset, offset + length)); + } + + @Override + void writeToInternal(OutputStream out, int sourceOffset, int numberToWrite) throws IOException { + if (buffer.hasArray()) { + // Optimized write for array-backed buffers. + // Note that we're taking the risk that a malicious OutputStream could modify the array. + int bufferOffset = buffer.arrayOffset() + buffer.position() + sourceOffset; + out.write(buffer.array(), bufferOffset, numberToWrite); + return; + } + + // Slow path + if (out instanceof FileOutputStream || numberToWrite >= 8192) { + // Use a channel to write out the ByteBuffer. + Channels.newChannel(out).write(slice(sourceOffset, sourceOffset + numberToWrite)); + } else { + // Just copy the data to an array and write it. + out.write(toByteArray()); + } + } + + @Override + public ByteBuffer asReadOnlyByteBuffer() { + return buffer.asReadOnlyBuffer(); + } + + @Override + public List<ByteBuffer> asReadOnlyByteBufferList() { + return Collections.singletonList(asReadOnlyByteBuffer()); + } + + @Override + protected String toStringInternal(Charset charset) { + byte[] bytes; + int offset; + if (buffer.hasArray()) { + bytes = buffer.array(); + offset = buffer.arrayOffset() + buffer.position(); + } else { + bytes = toByteArray(); + offset = 0; + } + return new String(bytes, offset, size(), charset); + } + + @Override + public boolean isValidUtf8() { + // TODO(nathanmittler): add a ByteBuffer fork for Utf8.isValidUtf8 to avoid the copy + byte[] bytes; + int startIndex; + if (buffer.hasArray()) { + bytes = buffer.array(); + startIndex = buffer.arrayOffset() + buffer.position(); + } else { + bytes = toByteArray(); + startIndex = 0; + } + return Utf8.isValidUtf8(bytes, startIndex, startIndex + size()); + } + + @Override + protected int partialIsValidUtf8(int state, int offset, int length) { + // TODO(nathanmittler): TODO add a ByteBuffer fork for Utf8.partialIsValidUtf8 to avoid the copy + byte[] bytes; + int startIndex; + if (buffer.hasArray()) { + bytes = buffer.array(); + startIndex = buffer.arrayOffset() + buffer.position(); + } else { + bytes = toByteArray(); + startIndex = 0; + } + return Utf8.partialIsValidUtf8(state, bytes, startIndex, startIndex + size()); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (!(other instanceof ByteString)) { + return false; + } + ByteString otherString = ((ByteString) other); + if (size() != otherString.size()) { + return false; + } + if (size() == 0) { + return true; + } + if (other instanceof NioByteString) { + return buffer.equals(((NioByteString) other).buffer); + } + if (other instanceof RopeByteString) { + return other.equals(this); + } + return buffer.equals(otherString.asReadOnlyByteBuffer()); + } + + @Override + protected int partialHash(int h, int offset, int length) { + for (int i = offset; i < offset + length; i++) { + h = h * 31 + buffer.get(i); + } + return h; + } + + @Override + public InputStream newInput() { + return new InputStream() { + private final ByteBuffer buf = buffer.slice(); + + @Override + public void mark(int readlimit) { + buf.mark(); + } + + @Override + public boolean markSupported() { + return true; + } + + @Override + public void reset() throws IOException { + try { + buf.reset(); + } catch (InvalidMarkException e) { + throw new IOException(e); + } + } + + @Override + public int available() throws IOException { + return buf.remaining(); + } + + @Override + public int read() throws IOException { + if (!buf.hasRemaining()) { + return -1; + } + return buf.get() & 0xFF; + } + + @Override + public int read(byte[] bytes, int off, int len) throws IOException { + if (!buf.hasRemaining()) { + return -1; + } + + len = Math.min(len, buf.remaining()); + buf.get(bytes, off, len); + return len; + } + }; + } + + @Override + public CodedInputStream newCodedInput() { + return CodedInputStream.newInstance(buffer); + } + + /** + * Creates a slice of a range of this buffer. + * + * @param beginIndex the beginning index of the slice (inclusive). + * @param endIndex the end index of the slice (exclusive). + * @return the requested slice. + */ + private ByteBuffer slice(int beginIndex, int endIndex) { + if (beginIndex < buffer.position() || endIndex > buffer.limit() || beginIndex > endIndex) { + throw new IllegalArgumentException( + String.format("Invalid indices [%d, %d]", beginIndex, endIndex)); + } + + ByteBuffer slice = buffer.slice(); + slice.position(beginIndex - buffer.position()); + slice.limit(endIndex - buffer.position()); + return slice; + } +} diff --git a/java/core/src/main/java/com/google/protobuf/Parser.java b/java/core/src/main/java/com/google/protobuf/Parser.java new file mode 100644 index 00000000..227c02b7 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/Parser.java @@ -0,0 +1,261 @@ +// 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.InputStream; + +/** + * Abstract interface for parsing Protocol Messages. + * + * The implementation should be stateless and thread-safe. + * + * @author liujisi@google.com (Pherl Liu) + */ +public interface Parser<MessageType> { + /** + * Parses a message of {@code MessageType} from the input. + * + * <p>Note: The caller should call + * {@link CodedInputStream#checkLastTagWas(int)} after calling this to + * verify that the last tag seen was the appropriate end-group tag, + * or zero for EOF. + */ + public MessageType parseFrom(CodedInputStream input) + throws InvalidProtocolBufferException; + + /** + * Like {@link #parseFrom(CodedInputStream)}, but also parses extensions. + * The extensions that you want to be able to parse must be registered in + * {@code extensionRegistry}. Extensions not in the registry will be treated + * as unknown fields. + */ + public MessageType parseFrom(CodedInputStream input, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException; + + /** + * Like {@link #parseFrom(CodedInputStream)}, but does not throw an + * exception if the message is missing required fields. Instead, a partial + * message is returned. + */ + public MessageType parsePartialFrom(CodedInputStream input) + throws InvalidProtocolBufferException; + + /** + * Like {@link #parseFrom(CodedInputStream input, ExtensionRegistryLite)}, + * but does not throw an exception if the message is missing required fields. + * Instead, a partial message is returned. + */ + public MessageType parsePartialFrom(CodedInputStream input, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException; + + // --------------------------------------------------------------- + // Convenience methods. + + /** + * Parses {@code data} as a message of {@code MessageType}. + * This is just a small wrapper around {@link #parseFrom(CodedInputStream)}. + */ + public MessageType parseFrom(ByteString data) + throws InvalidProtocolBufferException; + + /** + * Parses {@code data} as a message of {@code MessageType}. + * This is just a small wrapper around + * {@link #parseFrom(CodedInputStream, ExtensionRegistryLite)}. + */ + public MessageType parseFrom(ByteString data, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException; + + /** + * Like {@link #parseFrom(ByteString)}, but does not throw an + * exception if the message is missing required fields. Instead, a partial + * message is returned. + */ + public MessageType parsePartialFrom(ByteString data) + throws InvalidProtocolBufferException; + + /** + * Like {@link #parseFrom(ByteString, ExtensionRegistryLite)}, + * but does not throw an exception if the message is missing required fields. + * Instead, a partial message is returned. + */ + public MessageType parsePartialFrom(ByteString data, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException; + + /** + * Parses {@code data} as a message of {@code MessageType}. + * This is just a small wrapper around {@link #parseFrom(CodedInputStream)}. + */ + public MessageType parseFrom(byte[] data, int off, int len) + throws InvalidProtocolBufferException; + + /** + * Parses {@code data} as a message of {@code MessageType}. + * This is just a small wrapper around + * {@link #parseFrom(CodedInputStream, ExtensionRegistryLite)}. + */ + public MessageType parseFrom(byte[] data, int off, int len, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException; + + /** + * Parses {@code data} as a message of {@code MessageType}. + * This is just a small wrapper around {@link #parseFrom(CodedInputStream)}. + */ + public MessageType parseFrom(byte[] data) + throws InvalidProtocolBufferException; + + /** + * Parses {@code data} as a message of {@code MessageType}. + * This is just a small wrapper around + * {@link #parseFrom(CodedInputStream, ExtensionRegistryLite)}. + */ + public MessageType parseFrom(byte[] data, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException; + + /** + * Like {@link #parseFrom(byte[], int, int)}, but does not throw an + * exception if the message is missing required fields. Instead, a partial + * message is returned. + */ + public MessageType parsePartialFrom(byte[] data, int off, int len) + throws InvalidProtocolBufferException; + + /** + * Like {@link #parseFrom(ByteString, ExtensionRegistryLite)}, + * but does not throw an exception if the message is missing required fields. + * Instead, a partial message is returned. + */ + public MessageType parsePartialFrom(byte[] data, int off, int len, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException; + + /** + * Like {@link #parseFrom(byte[])}, but does not throw an + * exception if the message is missing required fields. Instead, a partial + * message is returned. + */ + public MessageType parsePartialFrom(byte[] data) + throws InvalidProtocolBufferException; + + /** + * Like {@link #parseFrom(byte[], ExtensionRegistryLite)}, + * but does not throw an exception if the message is missing required fields. + * Instead, a partial message is returned. + */ + public MessageType parsePartialFrom(byte[] data, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException; + + /** + * Parse a message of {@code MessageType} from {@code input}. + * This is just a small wrapper around {@link #parseFrom(CodedInputStream)}. + * Note that this method always reads the <i>entire</i> input (unless it + * throws an exception). If you want it to stop earlier, you will need to + * wrap your input in some wrapper stream that limits reading. Or, use + * {@link MessageLite#writeDelimitedTo(java.io.OutputStream)} to write your + * message and {@link #parseDelimitedFrom(InputStream)} to read it. + * <p> + * Despite usually reading the entire input, this does not close the stream. + */ + public MessageType parseFrom(InputStream input) + throws InvalidProtocolBufferException; + + /** + * Parses a message of {@code MessageType} from {@code input}. + * This is just a small wrapper around + * {@link #parseFrom(CodedInputStream, ExtensionRegistryLite)}. + */ + public MessageType parseFrom(InputStream input, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException; + + /** + * Like {@link #parseFrom(InputStream)}, but does not throw an + * exception if the message is missing required fields. Instead, a partial + * message is returned. + */ + public MessageType parsePartialFrom(InputStream input) + throws InvalidProtocolBufferException; + + /** + * Like {@link #parseFrom(InputStream, ExtensionRegistryLite)}, + * but does not throw an exception if the message is missing required fields. + * Instead, a partial message is returned. + */ + public MessageType parsePartialFrom(InputStream input, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException; + + /** + * Like {@link #parseFrom(InputStream)}, but does not read util EOF. + * Instead, the size of message (encoded as a varint) is read first, + * then the message data. Use + * {@link MessageLite#writeDelimitedTo(java.io.OutputStream)} to write + * messages in this format. + * + * @return Parsed message if successful, or null if the stream is at EOF when + * the method starts. Any other error (including reaching EOF during + * parsing) will cause an exception to be thrown. + */ + public MessageType parseDelimitedFrom(InputStream input) + throws InvalidProtocolBufferException; + + /** + * Like {@link #parseDelimitedFrom(InputStream)} but supporting extensions. + */ + public MessageType parseDelimitedFrom(InputStream input, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException; + + /** + * Like {@link #parseDelimitedFrom(InputStream)}, but does not throw an + * exception if the message is missing required fields. Instead, a partial + * message is returned. + */ + public MessageType parsePartialDelimitedFrom(InputStream input) + throws InvalidProtocolBufferException; + + /** + * Like {@link #parseDelimitedFrom(InputStream, ExtensionRegistryLite)}, + * but does not throw an exception if the message is missing required fields. + * Instead, a partial message is returned. + */ + public MessageType parsePartialDelimitedFrom( + InputStream input, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException; +} diff --git a/java/core/src/main/java/com/google/protobuf/ProtobufArrayList.java b/java/core/src/main/java/com/google/protobuf/ProtobufArrayList.java new file mode 100644 index 00000000..d2f82ac5 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/ProtobufArrayList.java @@ -0,0 +1,99 @@ +// 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.Internal.ProtobufList; + +import java.util.ArrayList; +import java.util.List; + +/** + * Implements {@link ProtobufList} for non-primitive and {@link String} types. + */ +class ProtobufArrayList<E> extends AbstractProtobufList<E> { + + private static final ProtobufArrayList<Object> EMPTY_LIST = new ProtobufArrayList<Object>(); + static { + EMPTY_LIST.makeImmutable(); + } + + @SuppressWarnings("unchecked") // Guaranteed safe by runtime. + public static <E> ProtobufArrayList<E> emptyList() { + return (ProtobufArrayList<E>) EMPTY_LIST; + } + + private final List<E> list; + + ProtobufArrayList() { + list = new ArrayList<E>(); + } + + ProtobufArrayList(List<E> toCopy) { + list = new ArrayList<E>(toCopy); + } + + ProtobufArrayList(int capacity) { + list = new ArrayList<E>(capacity); + } + + @Override + public void add(int index, E element) { + ensureIsMutable(); + list.add(index, element); + modCount++; + } + + @Override + public E get(int index) { + return list.get(index); + } + + @Override + public E remove(int index) { + ensureIsMutable(); + E toReturn = list.remove(index); + modCount++; + return toReturn; + } + + @Override + public E set(int index, E element) { + ensureIsMutable(); + E toReturn = list.set(index, element); + modCount++; + return toReturn; + } + + @Override + public int size() { + return list.size(); + } +} diff --git a/java/core/src/main/java/com/google/protobuf/ProtocolMessageEnum.java b/java/core/src/main/java/com/google/protobuf/ProtocolMessageEnum.java new file mode 100644 index 00000000..0c8df989 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/ProtocolMessageEnum.java @@ -0,0 +1,58 @@ +// 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.EnumDescriptor; +import com.google.protobuf.Descriptors.EnumValueDescriptor; + +/** + * Interface of useful methods added to all enums generated by the protocol + * compiler. + */ +public interface ProtocolMessageEnum extends Internal.EnumLite { + + /** + * Return the value's numeric value as defined in the .proto file. + */ + int getNumber(); + + /** + * Return the value's descriptor, which contains information such as + * value name, number, and type. + */ + EnumValueDescriptor getValueDescriptor(); + + /** + * Return the enum type's descriptor, which contains information + * about each defined value, etc. + */ + EnumDescriptor getDescriptorForType(); +} diff --git a/java/core/src/main/java/com/google/protobuf/ProtocolStringList.java b/java/core/src/main/java/com/google/protobuf/ProtocolStringList.java new file mode 100644 index 00000000..d553b41e --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/ProtocolStringList.java @@ -0,0 +1,48 @@ +// 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.List; + +/** + * An interface extending {@code List<String>} used for repeated string fields + * to provide optional access to the data as a list of ByteStrings. The + * underlying implementation stores values as either ByteStrings or Strings + * (see {@link LazyStringArrayList}) depending on how the value was initialized + * or last read, and it is often more efficient to deal with lists of + * ByteStrings when handling protos that have been deserialized from bytes. + */ +public interface ProtocolStringList extends List<String> { + + /** Returns a view of the data as a list of ByteStrings. */ + List<ByteString> asByteStringList(); + +} diff --git a/java/core/src/main/java/com/google/protobuf/RepeatedFieldBuilder.java b/java/core/src/main/java/com/google/protobuf/RepeatedFieldBuilder.java new file mode 100644 index 00000000..f91cdbce --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/RepeatedFieldBuilder.java @@ -0,0 +1,702 @@ +// 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.AbstractList; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * {@code RepeatedFieldBuilder} implements a structure that a protocol + * message uses to hold a repeated field of other protocol messages. It supports + * the classical use case of adding immutable {@link Message}'s to the + * repeated field and is highly optimized around this (no extra memory + * allocations and sharing of immutable arrays). + * <br> + * It also supports the additional use case of adding a {@link Message.Builder} + * to the repeated field and deferring conversion of that {@code Builder} + * to an immutable {@code Message}. In this way, it's possible to maintain + * a tree of {@code Builder}'s that acts as a fully read/write data + * structure. + * <br> + * Logically, one can think of a tree of builders as converting the entire tree + * to messages when build is called on the root or when any method is called + * that desires a Message instead of a Builder. In terms of the implementation, + * the {@code SingleFieldBuilder} and {@code RepeatedFieldBuilder} + * classes cache messages that were created so that messages only need to be + * created when some change occurred in its builder or a builder for one of its + * descendants. + * + * @param <MType> the type of message for the field + * @param <BType> the type of builder for the field + * @param <IType> the common interface for the message and the builder + * + * @author jonp@google.com (Jon Perlow) + */ +public class RepeatedFieldBuilder + <MType extends GeneratedMessage, + BType extends GeneratedMessage.Builder, + IType extends MessageOrBuilder> + implements GeneratedMessage.BuilderParent { + + // Parent to send changes to. + private GeneratedMessage.BuilderParent parent; + + // List of messages. Never null. It may be immutable, in which case + // isMessagesListMutable will be false. See note below. + private List<MType> messages; + + // Whether messages is an mutable array that can be modified. + private boolean isMessagesListMutable; + + // List of builders. May be null, in which case, no nested builders were + // created. If not null, entries represent the builder for that index. + private List<SingleFieldBuilder<MType, BType, IType>> builders; + + // Here are the invariants for messages and builders: + // 1. messages is never null and its count corresponds to the number of items + // in the repeated field. + // 2. If builders is non-null, messages and builders MUST always + // contain the same number of items. + // 3. Entries in either array can be null, but for any index, there MUST be + // either a Message in messages or a builder in builders. + // 4. If the builder at an index is non-null, the builder is + // authoritative. This is the case where a Builder was set on the index. + // Any message in the messages array MUST be ignored. + // t. If the builder at an index is null, the message in the messages + // list is authoritative. This is the case where a Message (not a Builder) + // was set directly for an index. + + // Indicates that we've built a message and so we are now obligated + // to dispatch dirty invalidations. See GeneratedMessage.BuilderListener. + private boolean isClean; + + // A view of this builder that exposes a List interface of messages. This is + // initialized on demand. This is fully backed by this object and all changes + // are reflected in it. Access to any item converts it to a message if it + // was a builder. + private MessageExternalList<MType, BType, IType> externalMessageList; + + // A view of this builder that exposes a List interface of builders. This is + // initialized on demand. This is fully backed by this object and all changes + // are reflected in it. Access to any item converts it to a builder if it + // was a message. + private BuilderExternalList<MType, BType, IType> externalBuilderList; + + // A view of this builder that exposes a List interface of the interface + // implemented by messages and builders. This is initialized on demand. This + // is fully backed by this object and all changes are reflected in it. + // Access to any item returns either a builder or message depending on + // what is most efficient. + private MessageOrBuilderExternalList<MType, BType, IType> + externalMessageOrBuilderList; + + /** + * Constructs a new builder with an empty list of messages. + * + * @param messages the current list of messages + * @param isMessagesListMutable Whether the messages list is mutable + * @param parent a listener to notify of changes + * @param isClean whether the builder is initially marked clean + */ + public RepeatedFieldBuilder( + List<MType> messages, + boolean isMessagesListMutable, + GeneratedMessage.BuilderParent parent, + boolean isClean) { + this.messages = messages; + this.isMessagesListMutable = isMessagesListMutable; + this.parent = parent; + this.isClean = isClean; + } + + public void dispose() { + // Null out parent so we stop sending it invalidations. + parent = null; + } + + /** + * Ensures that the list of messages is mutable so it can be updated. If it's + * immutable, a copy is made. + */ + private void ensureMutableMessageList() { + if (!isMessagesListMutable) { + messages = new ArrayList<MType>(messages); + isMessagesListMutable = true; + } + } + + /** + * Ensures that the list of builders is not null. If it's null, the list is + * created and initialized to be the same size as the messages list with + * null entries. + */ + private void ensureBuilders() { + if (this.builders == null) { + this.builders = + new ArrayList<SingleFieldBuilder<MType, BType, IType>>( + messages.size()); + for (int i = 0; i < messages.size(); i++) { + builders.add(null); + } + } + } + + /** + * Gets the count of items in the list. + * + * @return the count of items in the list. + */ + public int getCount() { + return messages.size(); + } + + /** + * Gets whether the list is empty. + * + * @return whether the list is empty + */ + public boolean isEmpty() { + return messages.isEmpty(); + } + + /** + * Get the message at the specified index. If the message is currently stored + * as a {@code Builder}, it is converted to a {@code Message} by + * calling {@link Message.Builder#buildPartial} on it. + * + * @param index the index of the message to get + * @return the message for the specified index + */ + public MType getMessage(int index) { + return getMessage(index, false); + } + + /** + * Get the message at the specified index. If the message is currently stored + * as a {@code Builder}, it is converted to a {@code Message} by + * calling {@link Message.Builder#buildPartial} on it. + * + * @param index the index of the message to get + * @param forBuild this is being called for build so we want to make sure + * we SingleFieldBuilder.build to send dirty invalidations + * @return the message for the specified index + */ + private MType getMessage(int index, boolean forBuild) { + if (this.builders == null) { + // We don't have any builders -- return the current Message. + // This is the case where no builder was created, so we MUST have a + // Message. + return messages.get(index); + } + + SingleFieldBuilder<MType, BType, IType> builder = builders.get(index); + if (builder == null) { + // We don't have a builder -- return the current message. + // This is the case where no builder was created for the entry at index, + // so we MUST have a message. + return messages.get(index); + + } else { + return forBuild ? builder.build() : builder.getMessage(); + } + } + + /** + * Gets a builder for the specified index. If no builder has been created for + * that index, a builder is created on demand by calling + * {@link Message#toBuilder}. + * + * @param index the index of the message to get + * @return The builder for that index + */ + public BType getBuilder(int index) { + ensureBuilders(); + SingleFieldBuilder<MType, BType, IType> builder = builders.get(index); + if (builder == null) { + MType message = messages.get(index); + builder = new SingleFieldBuilder<MType, BType, IType>( + message, this, isClean); + builders.set(index, builder); + } + return builder.getBuilder(); + } + + /** + * Gets the base class interface for the specified index. This may either be + * a builder or a message. It will return whatever is more efficient. + * + * @param index the index of the message to get + * @return the message or builder for the index as the base class interface + */ + @SuppressWarnings("unchecked") + public IType getMessageOrBuilder(int index) { + if (this.builders == null) { + // We don't have any builders -- return the current Message. + // This is the case where no builder was created, so we MUST have a + // Message. + return (IType) messages.get(index); + } + + SingleFieldBuilder<MType, BType, IType> builder = builders.get(index); + if (builder == null) { + // We don't have a builder -- return the current message. + // This is the case where no builder was created for the entry at index, + // so we MUST have a message. + return (IType) messages.get(index); + + } else { + return builder.getMessageOrBuilder(); + } + } + + /** + * Sets a message at the specified index replacing the existing item at + * that index. + * + * @param index the index to set. + * @param message the message to set + * @return the builder + */ + public RepeatedFieldBuilder<MType, BType, IType> setMessage( + int index, MType message) { + if (message == null) { + throw new NullPointerException(); + } + ensureMutableMessageList(); + messages.set(index, message); + if (builders != null) { + SingleFieldBuilder<MType, BType, IType> entry = + builders.set(index, null); + if (entry != null) { + entry.dispose(); + } + } + onChanged(); + incrementModCounts(); + return this; + } + + /** + * Appends the specified element to the end of this list. + * + * @param message the message to add + * @return the builder + */ + public RepeatedFieldBuilder<MType, BType, IType> addMessage( + MType message) { + if (message == null) { + throw new NullPointerException(); + } + ensureMutableMessageList(); + messages.add(message); + if (builders != null) { + builders.add(null); + } + onChanged(); + incrementModCounts(); + return this; + } + + /** + * Inserts the specified message at the specified position in this list. + * Shifts the element currently at that position (if any) and any subsequent + * elements to the right (adds one to their indices). + * + * @param index the index at which to insert the message + * @param message the message to add + * @return the builder + */ + public RepeatedFieldBuilder<MType, BType, IType> addMessage( + int index, MType message) { + if (message == null) { + throw new NullPointerException(); + } + ensureMutableMessageList(); + messages.add(index, message); + if (builders != null) { + builders.add(index, null); + } + onChanged(); + incrementModCounts(); + return this; + } + + /** + * Appends all of the messages in the specified collection to the end of + * this list, in the order that they are returned by the specified + * collection's iterator. + * + * @param values the messages to add + * @return the builder + */ + public RepeatedFieldBuilder<MType, BType, IType> addAllMessages( + Iterable<? extends MType> values) { + for (final MType value : values) { + if (value == null) { + 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<MType> collection = (Collection<MType>) values; + if (collection.size() == 0) { + return this; + } + size = collection.size(); + } + ensureMutableMessageList(); + + if (size >= 0 && messages instanceof ArrayList) { + ((ArrayList<MType>) messages) + .ensureCapacity(messages.size() + size); + } + + for (MType value : values) { + addMessage(value); + } + + onChanged(); + incrementModCounts(); + return this; + } + + /** + * Appends a new builder to the end of this list and returns the builder. + * + * @param message the message to add which is the basis of the builder + * @return the new builder + */ + public BType addBuilder(MType message) { + ensureMutableMessageList(); + ensureBuilders(); + SingleFieldBuilder<MType, BType, IType> builder = + new SingleFieldBuilder<MType, BType, IType>( + message, this, isClean); + messages.add(null); + builders.add(builder); + onChanged(); + incrementModCounts(); + return builder.getBuilder(); + } + + /** + * Inserts a new builder at the specified position in this list. + * Shifts the element currently at that position (if any) and any subsequent + * elements to the right (adds one to their indices). + * + * @param index the index at which to insert the builder + * @param message the message to add which is the basis of the builder + * @return the builder + */ + public BType addBuilder(int index, MType message) { + ensureMutableMessageList(); + ensureBuilders(); + SingleFieldBuilder<MType, BType, IType> builder = + new SingleFieldBuilder<MType, BType, IType>( + message, this, isClean); + messages.add(index, null); + builders.add(index, builder); + onChanged(); + incrementModCounts(); + return builder.getBuilder(); + } + + /** + * Removes the element at the specified position in this list. Shifts any + * subsequent elements to the left (subtracts one from their indices). + * Returns the element that was removed from the list. + * + * @param index the index at which to remove the message + */ + public void remove(int index) { + ensureMutableMessageList(); + messages.remove(index); + if (builders != null) { + SingleFieldBuilder<MType, BType, IType> entry = + builders.remove(index); + if (entry != null) { + entry.dispose(); + } + } + onChanged(); + incrementModCounts(); + } + + /** + * Removes all of the elements from this list. + * The list will be empty after this call returns. + */ + public void clear() { + messages = Collections.emptyList(); + isMessagesListMutable = false; + if (builders != null) { + for (SingleFieldBuilder<MType, BType, IType> entry : + builders) { + if (entry != null) { + entry.dispose(); + } + } + builders = null; + } + onChanged(); + incrementModCounts(); + } + + /** + * Builds the list of messages from the builder and returns them. + * + * @return an immutable list of messages + */ + public List<MType> build() { + // Now that build has been called, we are required to dispatch + // invalidations. + isClean = true; + + if (!isMessagesListMutable && builders == null) { + // We still have an immutable list and we never created a builder. + return messages; + } + + boolean allMessagesInSync = true; + if (!isMessagesListMutable) { + // We still have an immutable list. Let's see if any of them are out + // of sync with their builders. + for (int i = 0; i < messages.size(); i++) { + Message message = messages.get(i); + SingleFieldBuilder<MType, BType, IType> builder = builders.get(i); + if (builder != null) { + if (builder.build() != message) { + allMessagesInSync = false; + break; + } + } + } + if (allMessagesInSync) { + // Immutable list is still in sync. + return messages; + } + } + + // Need to make sure messages is up to date + ensureMutableMessageList(); + for (int i = 0; i < messages.size(); i++) { + messages.set(i, getMessage(i, true)); + } + + // We're going to return our list as immutable so we mark that we can + // no longer update it. + messages = Collections.unmodifiableList(messages); + isMessagesListMutable = false; + return messages; + } + + /** + * Gets a view of the builder as a list of messages. The returned list is live + * and will reflect any changes to the underlying builder. + * + * @return the messages in the list + */ + public List<MType> getMessageList() { + if (externalMessageList == null) { + externalMessageList = + new MessageExternalList<MType, BType, IType>(this); + } + return externalMessageList; + } + + /** + * Gets a view of the builder as a list of builders. This returned list is + * live and will reflect any changes to the underlying builder. + * + * @return the builders in the list + */ + public List<BType> getBuilderList() { + if (externalBuilderList == null) { + externalBuilderList = + new BuilderExternalList<MType, BType, IType>(this); + } + return externalBuilderList; + } + + /** + * Gets a view of the builder as a list of MessageOrBuilders. This returned + * list is live and will reflect any changes to the underlying builder. + * + * @return the builders in the list + */ + public List<IType> getMessageOrBuilderList() { + if (externalMessageOrBuilderList == null) { + externalMessageOrBuilderList = + new MessageOrBuilderExternalList<MType, BType, IType>(this); + } + return externalMessageOrBuilderList; + } + + /** + * Called when a the builder or one of its nested children has changed + * and any parent should be notified of its invalidation. + */ + private void onChanged() { + if (isClean && parent != null) { + parent.markDirty(); + + // Don't keep dispatching invalidations until build is called again. + isClean = false; + } + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public void markDirty() { + onChanged(); + } + + /** + * Increments the mod counts so that an ConcurrentModificationException can + * be thrown if calling code tries to modify the builder while its iterating + * the list. + */ + private void incrementModCounts() { + if (externalMessageList != null) { + externalMessageList.incrementModCount(); + } + if (externalBuilderList != null) { + externalBuilderList.incrementModCount(); + } + if (externalMessageOrBuilderList != null) { + externalMessageOrBuilderList.incrementModCount(); + } + } + + /** + * Provides a live view of the builder as a list of messages. + * + * @param <MType> the type of message for the field + * @param <BType> the type of builder for the field + * @param <IType> the common interface for the message and the builder + */ + private static class MessageExternalList< + MType extends GeneratedMessage, + BType extends GeneratedMessage.Builder, + IType extends MessageOrBuilder> + extends AbstractList<MType> implements List<MType> { + + RepeatedFieldBuilder<MType, BType, IType> builder; + + MessageExternalList( + RepeatedFieldBuilder<MType, BType, IType> builder) { + this.builder = builder; + } + + public int size() { + return this.builder.getCount(); + } + + public MType get(int index) { + return builder.getMessage(index); + } + + void incrementModCount() { + modCount++; + } + } + + /** + * Provides a live view of the builder as a list of builders. + * + * @param <MType> the type of message for the field + * @param <BType> the type of builder for the field + * @param <IType> the common interface for the message and the builder + */ + private static class BuilderExternalList< + MType extends GeneratedMessage, + BType extends GeneratedMessage.Builder, + IType extends MessageOrBuilder> + extends AbstractList<BType> implements List<BType> { + + RepeatedFieldBuilder<MType, BType, IType> builder; + + BuilderExternalList( + RepeatedFieldBuilder<MType, BType, IType> builder) { + this.builder = builder; + } + + public int size() { + return this.builder.getCount(); + } + + public BType get(int index) { + return builder.getBuilder(index); + } + + void incrementModCount() { + modCount++; + } + } + + /** + * Provides a live view of the builder as a list of builders. + * + * @param <MType> the type of message for the field + * @param <BType> the type of builder for the field + * @param <IType> the common interface for the message and the builder + */ + private static class MessageOrBuilderExternalList< + MType extends GeneratedMessage, + BType extends GeneratedMessage.Builder, + IType extends MessageOrBuilder> + extends AbstractList<IType> implements List<IType> { + + RepeatedFieldBuilder<MType, BType, IType> builder; + + MessageOrBuilderExternalList( + RepeatedFieldBuilder<MType, BType, IType> builder) { + this.builder = builder; + } + + public int size() { + return this.builder.getCount(); + } + + public IType get(int index) { + return builder.getMessageOrBuilder(index); + } + + void incrementModCount() { + modCount++; + } + } +} diff --git a/java/core/src/main/java/com/google/protobuf/RopeByteString.java b/java/core/src/main/java/com/google/protobuf/RopeByteString.java new file mode 100644 index 00000000..6e8eb724 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/RopeByteString.java @@ -0,0 +1,888 @@ +// 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.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Stack; + +/** + * Class to represent {@code ByteStrings} formed by concatenation of other + * ByteStrings, without copying the data in the pieces. The concatenation is + * represented as a tree whose leaf nodes are each a {@link LiteralByteString}. + * + * <p>Most of the operation here is inspired by the now-famous paper <a + * href="http://www.cs.ubc.ca/local/reading/proceedings/spe91-95/spe/vol25/issue12/spe986.pdf"> + * BAP95 </a> Ropes: an Alternative to Strings hans-j. boehm, russ atkinson and + * michael plass + * + * <p>The algorithms described in the paper have been implemented for character + * strings in {@code com.google.common.string.Rope} and in the c++ class {@code + * cord.cc}. + * + * <p>Fundamentally the Rope algorithm represents the collection of pieces as a + * binary tree. BAP95 uses a Fibonacci bound relating depth to a minimum + * sequence length, sequences that are too short relative to their depth cause a + * tree rebalance. More precisely, a tree of depth d is "balanced" in the + * terminology of BAP95 if its length is at least F(d+2), where F(n) is the + * n-the Fibonacci number. Thus for depths 0, 1, 2, 3, 4, 5,... we have minimum + * lengths 1, 2, 3, 5, 8, 13,... + * + * @author carlanton@google.com (Carl Haverl) + */ +final class RopeByteString extends ByteString { + + /** + * BAP95. Let Fn be the nth Fibonacci number. A {@link RopeByteString} of + * depth n is "balanced", i.e flat enough, if its length is at least Fn+2, + * e.g. a "balanced" {@link RopeByteString} of depth 1 must have length at + * least 2, of depth 4 must have length >= 8, etc. + * + * <p>There's nothing special about using the Fibonacci numbers for this, but + * they are a reasonable sequence for encapsulating the idea that we are OK + * with longer strings being encoded in deeper binary trees. + * + * <p>For 32-bit integers, this array has length 46. + */ + private static final int[] minLengthByDepth; + + static { + // Dynamically generate the list of Fibonacci numbers the first time this + // class is accessed. + List<Integer> numbers = new ArrayList<Integer>(); + + // we skip the first Fibonacci number (1). So instead of: 1 1 2 3 5 8 ... + // we have: 1 2 3 5 8 ... + int f1 = 1; + int f2 = 1; + + // get all the values until we roll over. + while (f2 > 0) { + numbers.add(f2); + int temp = f1 + f2; + f1 = f2; + f2 = temp; + } + + // we include this here so that we can index this array to [x + 1] in the + // loops below. + numbers.add(Integer.MAX_VALUE); + minLengthByDepth = new int[numbers.size()]; + for (int i = 0; i < minLengthByDepth.length; i++) { + // unbox all the values + minLengthByDepth[i] = numbers.get(i); + } + } + + private final int totalLength; + private final ByteString left; + private final ByteString right; + private final int leftLength; + private final int treeDepth; + + /** + * Create a new RopeByteString, which can be thought of as a new tree node, by + * recording references to the two given strings. + * + * @param left string on the left of this node, should have {@code size() > + * 0} + * @param right string on the right of this node, should have {@code size() > + * 0} + */ + private RopeByteString(ByteString left, ByteString right) { + this.left = left; + this.right = right; + leftLength = left.size(); + totalLength = leftLength + right.size(); + treeDepth = Math.max(left.getTreeDepth(), right.getTreeDepth()) + 1; + } + + /** + * Concatenate the given strings while performing various optimizations to + * slow the growth rate of tree depth and tree node count. The result is + * either a {@link LiteralByteString} or a {@link RopeByteString} + * depending on which optimizations, if any, were applied. + * + * <p>Small pieces of length less than {@link + * ByteString#CONCATENATE_BY_COPY_SIZE} may be copied by value here, as in + * BAP95. Large pieces are referenced without copy. + * + * @param left string on the left + * @param right string on the right + * @return concatenation representing the same sequence as the given strings + */ + static ByteString concatenate(ByteString left, ByteString right) { + if (right.size() == 0) { + return left; + } + + if (left.size() == 0) { + return right; + } + + final int newLength = left.size() + right.size(); + if (newLength < ByteString.CONCATENATE_BY_COPY_SIZE) { + // Optimization from BAP95: For short (leaves in paper, but just short + // here) total length, do a copy of data to a new leaf. + return concatenateBytes(left, right); + } + + if (left instanceof RopeByteString) { + final RopeByteString leftRope = (RopeByteString) left; + if (leftRope.right.size() + right.size() < CONCATENATE_BY_COPY_SIZE) { + // Optimization from BAP95: As an optimization of the case where the + // ByteString is constructed by repeated concatenate, recognize the case + // where a short string is concatenated to a left-hand node whose + // right-hand branch is short. In the paper this applies to leaves, but + // we just look at the length here. This has the advantage of shedding + // references to unneeded data when substrings have been taken. + // + // When we recognize this case, we do a copy of the data and create a + // new parent node so that the depth of the result is the same as the + // given left tree. + ByteString newRight = concatenateBytes(leftRope.right, right); + return new RopeByteString(leftRope.left, newRight); + } + + if (leftRope.left.getTreeDepth() > leftRope.right.getTreeDepth() + && leftRope.getTreeDepth() > right.getTreeDepth()) { + // Typically for concatenate-built strings the left-side is deeper than + // the right. This is our final attempt to concatenate without + // increasing the tree depth. We'll redo the the node on the RHS. This + // is yet another optimization for building the string by repeatedly + // concatenating on the right. + ByteString newRight = new RopeByteString(leftRope.right, right); + return new RopeByteString(leftRope.left, newRight); + } + } + + // Fine, we'll add a node and increase the tree depth--unless we rebalance ;^) + int newDepth = Math.max(left.getTreeDepth(), right.getTreeDepth()) + 1; + if (newLength >= minLengthByDepth[newDepth]) { + // The tree is shallow enough, so don't rebalance + return new RopeByteString(left, right); + } + + return new Balancer().balance(left, right); + } + + /** + * Concatenates two strings by copying data values. This is called in a few + * cases in order to reduce the growth of the number of tree nodes. + * + * @param left string on the left + * @param right string on the right + * @return string formed by copying data bytes + */ + private static LiteralByteString concatenateBytes(ByteString left, + ByteString right) { + int leftSize = left.size(); + int rightSize = right.size(); + byte[] bytes = new byte[leftSize + rightSize]; + left.copyTo(bytes, 0, 0, leftSize); + right.copyTo(bytes, 0, leftSize, rightSize); + return new LiteralByteString(bytes); // Constructor wraps bytes + } + + /** + * Create a new RopeByteString for testing only while bypassing all the + * defenses of {@link #concatenate(ByteString, ByteString)}. This allows + * testing trees of specific structure. We are also able to insert empty + * leaves, though these are dis-allowed, so that we can make sure the + * implementation can withstand their presence. + * + * @param left string on the left of this node + * @param right string on the right of this node + * @return an unsafe instance for testing only + */ + static RopeByteString newInstanceForTest(ByteString left, ByteString right) { + return new RopeByteString(left, right); + } + + /** + * Gets the byte at the given index. + * Throws {@link ArrayIndexOutOfBoundsException} for backwards-compatibility + * reasons although it would more properly be {@link + * IndexOutOfBoundsException}. + * + * @param index index of byte + * @return the value + * @throws ArrayIndexOutOfBoundsException {@code index} is < 0 or >= size + */ + @Override + public byte byteAt(int index) { + checkIndex(index, totalLength); + + // Find the relevant piece by recursive descent + if (index < leftLength) { + return left.byteAt(index); + } + + return right.byteAt(index - leftLength); + } + + @Override + public int size() { + return totalLength; + } + + // ================================================================= + // Pieces + + @Override + protected int getTreeDepth() { + return treeDepth; + } + + /** + * Determines if the tree is balanced according to BAP95, which means the tree + * is flat-enough with respect to the bounds. Note that this definition of + * balanced is one where sub-trees of balanced trees are not necessarily + * balanced. + * + * @return true if the tree is balanced + */ + @Override + protected boolean isBalanced() { + return totalLength >= minLengthByDepth[treeDepth]; + } + + /** + * Takes a substring of this one. This involves recursive descent along the + * left and right edges of the substring, and referencing any wholly contained + * segments in between. Any leaf nodes entirely uninvolved in the substring + * will not be referenced by the substring. + * + * <p>Substrings of {@code length < 2} should result in at most a single + * recursive call chain, terminating at a leaf node. Thus the result will be a + * {@link LiteralByteString}. {@link #RopeByteString(ByteString, + * ByteString)}. + * + * @param beginIndex start at this index + * @param endIndex the last character is the one before this index + * @return substring leaf node or tree + */ + @Override + public ByteString substring(int beginIndex, int endIndex) { + final int length = checkRange(beginIndex, endIndex, totalLength); + + if (length == 0) { + // Empty substring + return ByteString.EMPTY; + } + + if (length == totalLength) { + // The whole string + return this; + } + + // Proper substring + if (endIndex <= leftLength) { + // Substring on the left + return left.substring(beginIndex, endIndex); + } + + if (beginIndex >= leftLength) { + // Substring on the right + return right.substring(beginIndex - leftLength, endIndex - leftLength); + } + + // Split substring + ByteString leftSub = left.substring(beginIndex); + ByteString rightSub = right.substring(0, endIndex - leftLength); + // Intentionally not rebalancing, since in many cases these two + // substrings will already be less deep than the top-level + // RopeByteString we're taking a substring of. + return new RopeByteString(leftSub, rightSub); + } + + // ================================================================= + // ByteString -> byte[] + + @Override + protected void copyToInternal(byte[] target, int sourceOffset, + int targetOffset, int numberToCopy) { + if (sourceOffset + numberToCopy <= leftLength) { + left.copyToInternal(target, sourceOffset, targetOffset, numberToCopy); + } else if (sourceOffset >= leftLength) { + right.copyToInternal(target, sourceOffset - leftLength, targetOffset, + numberToCopy); + } else { + int leftLength = this.leftLength - sourceOffset; + left.copyToInternal(target, sourceOffset, targetOffset, leftLength); + right.copyToInternal(target, 0, targetOffset + leftLength, + numberToCopy - leftLength); + } + } + + @Override + public void copyTo(ByteBuffer target) { + left.copyTo(target); + right.copyTo(target); + } + + @Override + public ByteBuffer asReadOnlyByteBuffer() { + ByteBuffer byteBuffer = ByteBuffer.wrap(toByteArray()); + return byteBuffer.asReadOnlyBuffer(); + } + + @Override + public List<ByteBuffer> asReadOnlyByteBufferList() { + // Walk through the list of LiteralByteString's that make up this + // rope, and add each one as a read-only ByteBuffer. + List<ByteBuffer> result = new ArrayList<ByteBuffer>(); + PieceIterator pieces = new PieceIterator(this); + while (pieces.hasNext()) { + LeafByteString byteString = pieces.next(); + result.add(byteString.asReadOnlyByteBuffer()); + } + return result; + } + + @Override + public void writeTo(OutputStream outputStream) throws IOException { + left.writeTo(outputStream); + right.writeTo(outputStream); + } + + @Override + void writeToInternal(OutputStream out, int sourceOffset, + int numberToWrite) throws IOException { + if (sourceOffset + numberToWrite <= leftLength) { + left.writeToInternal(out, sourceOffset, numberToWrite); + } else if (sourceOffset >= leftLength) { + right.writeToInternal(out, sourceOffset - leftLength, numberToWrite); + } else { + int numberToWriteInLeft = leftLength - sourceOffset; + left.writeToInternal(out, sourceOffset, numberToWriteInLeft); + right.writeToInternal(out, 0, numberToWrite - numberToWriteInLeft); + } + } + + @Override + protected String toStringInternal(Charset charset) { + return new String(toByteArray(), charset); + } + + // ================================================================= + // UTF-8 decoding + + @Override + public boolean isValidUtf8() { + int leftPartial = left.partialIsValidUtf8(Utf8.COMPLETE, 0, leftLength); + int state = right.partialIsValidUtf8(leftPartial, 0, right.size()); + return state == Utf8.COMPLETE; + } + + @Override + protected int partialIsValidUtf8(int state, int offset, int length) { + int toIndex = offset + length; + if (toIndex <= leftLength) { + return left.partialIsValidUtf8(state, offset, length); + } else if (offset >= leftLength) { + return right.partialIsValidUtf8(state, offset - leftLength, length); + } else { + int leftLength = this.leftLength - offset; + int leftPartial = left.partialIsValidUtf8(state, offset, leftLength); + return right.partialIsValidUtf8(leftPartial, 0, length - leftLength); + } + } + + // ================================================================= + // equals() and hashCode() + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (!(other instanceof ByteString)) { + return false; + } + + ByteString otherByteString = (ByteString) other; + if (totalLength != otherByteString.size()) { + return false; + } + if (totalLength == 0) { + return true; + } + + // You don't really want to be calling equals on long strings, but since + // we cache the hashCode, we effectively cache inequality. We use the cached + // hashCode if it's already computed. It's arguable we should compute the + // hashCode here, and if we're going to be testing a bunch of byteStrings, + // it might even make sense. + int thisHash = peekCachedHashCode(); + int thatHash = otherByteString.peekCachedHashCode(); + if (thisHash != 0 && thatHash != 0 && thisHash != thatHash) { + return false; + } + + return equalsFragments(otherByteString); + } + + /** + * Determines if this string is equal to another of the same length by + * iterating over the leaf nodes. On each step of the iteration, the + * overlapping segments of the leaves are compared. + * + * @param other string of the same length as this one + * @return true if the values of this string equals the value of the given + * one + */ + private boolean equalsFragments(ByteString other) { + int thisOffset = 0; + Iterator<LeafByteString> thisIter = new PieceIterator(this); + LeafByteString thisString = thisIter.next(); + + int thatOffset = 0; + Iterator<LeafByteString> thatIter = new PieceIterator(other); + LeafByteString thatString = thatIter.next(); + + int pos = 0; + while (true) { + int thisRemaining = thisString.size() - thisOffset; + int thatRemaining = thatString.size() - thatOffset; + int bytesToCompare = Math.min(thisRemaining, thatRemaining); + + // At least one of the offsets will be zero + boolean stillEqual = (thisOffset == 0) + ? thisString.equalsRange(thatString, thatOffset, bytesToCompare) + : thatString.equalsRange(thisString, thisOffset, bytesToCompare); + if (!stillEqual) { + return false; + } + + pos += bytesToCompare; + if (pos >= totalLength) { + if (pos == totalLength) { + return true; + } + throw new IllegalStateException(); + } + // We always get to the end of at least one of the pieces + if (bytesToCompare == thisRemaining) { // If reached end of this + thisOffset = 0; + thisString = thisIter.next(); + } else { + thisOffset += bytesToCompare; + } + if (bytesToCompare == thatRemaining) { // If reached end of that + thatOffset = 0; + thatString = thatIter.next(); + } else { + thatOffset += bytesToCompare; + } + } + } + + @Override + protected int partialHash(int h, int offset, int length) { + int toIndex = offset + length; + if (toIndex <= leftLength) { + return left.partialHash(h, offset, length); + } else if (offset >= leftLength) { + return right.partialHash(h, offset - leftLength, length); + } else { + int leftLength = this.leftLength - offset; + int leftPartial = left.partialHash(h, offset, leftLength); + return right.partialHash(leftPartial, 0, length - leftLength); + } + } + + // ================================================================= + // Input stream + + @Override + public CodedInputStream newCodedInput() { + return CodedInputStream.newInstance(new RopeInputStream()); + } + + @Override + public InputStream newInput() { + return new RopeInputStream(); + } + + /** + * This class implements the balancing algorithm of BAP95. In the paper the + * authors use an array to keep track of pieces, while here we use a stack. + * The tree is balanced by traversing subtrees in left to right order, and the + * stack always contains the part of the string we've traversed so far. + * + * <p>One surprising aspect of the algorithm is the result of balancing is not + * necessarily balanced, though it is nearly balanced. For details, see + * BAP95. + */ + private static class Balancer { + // Stack containing the part of the string, starting from the left, that + // we've already traversed. The final string should be the equivalent of + // concatenating the strings on the stack from bottom to top. + private final Stack<ByteString> prefixesStack = new Stack<ByteString>(); + + private ByteString balance(ByteString left, ByteString right) { + doBalance(left); + doBalance(right); + + // Sweep stack to gather the result + ByteString partialString = prefixesStack.pop(); + while (!prefixesStack.isEmpty()) { + ByteString newLeft = prefixesStack.pop(); + partialString = new RopeByteString(newLeft, partialString); + } + // We should end up with a RopeByteString since at a minimum we will + // create one from concatenating left and right + return partialString; + } + + private void doBalance(ByteString root) { + // BAP95: Insert balanced subtrees whole. This means the result might not + // be balanced, leading to repeated rebalancings on concatenate. However, + // these rebalancings are shallow due to ignoring balanced subtrees, and + // relatively few calls to insert() result. + if (root.isBalanced()) { + insert(root); + } else if (root instanceof RopeByteString) { + RopeByteString rbs = (RopeByteString) root; + doBalance(rbs.left); + doBalance(rbs.right); + } else { + throw new IllegalArgumentException( + "Has a new type of ByteString been created? Found " + + root.getClass()); + } + } + + /** + * Push a string on the balance stack (BAP95). BAP95 uses an array and + * calls the elements in the array 'bins'. We instead use a stack, so the + * 'bins' of lengths are represented by differences between the elements of + * minLengthByDepth. + * + * <p>If the length bin for our string, and all shorter length bins, are + * empty, we just push it on the stack. Otherwise, we need to start + * concatenating, putting the given string in the "middle" and continuing + * until we land in an empty length bin that matches the length of our + * concatenation. + * + * @param byteString string to place on the balance stack + */ + private void insert(ByteString byteString) { + int depthBin = getDepthBinForLength(byteString.size()); + int binEnd = minLengthByDepth[depthBin + 1]; + + // BAP95: Concatenate all trees occupying bins representing the length of + // our new piece or of shorter pieces, to the extent that is possible. + // The goal is to clear the bin which our piece belongs in, but that may + // not be entirely possible if there aren't enough longer bins occupied. + if (prefixesStack.isEmpty() || prefixesStack.peek().size() >= binEnd) { + prefixesStack.push(byteString); + } else { + int binStart = minLengthByDepth[depthBin]; + + // Concatenate the subtrees of shorter length + ByteString newTree = prefixesStack.pop(); + while (!prefixesStack.isEmpty() + && prefixesStack.peek().size() < binStart) { + ByteString left = prefixesStack.pop(); + newTree = new RopeByteString(left, newTree); + } + + // Concatenate the given string + newTree = new RopeByteString(newTree, byteString); + + // Continue concatenating until we land in an empty bin + while (!prefixesStack.isEmpty()) { + depthBin = getDepthBinForLength(newTree.size()); + binEnd = minLengthByDepth[depthBin + 1]; + if (prefixesStack.peek().size() < binEnd) { + ByteString left = prefixesStack.pop(); + newTree = new RopeByteString(left, newTree); + } else { + break; + } + } + prefixesStack.push(newTree); + } + } + + private int getDepthBinForLength(int length) { + int depth = Arrays.binarySearch(minLengthByDepth, length); + if (depth < 0) { + // It wasn't an exact match, so convert to the index of the containing + // fragment, which is one less even than the insertion point. + int insertionPoint = -(depth + 1); + depth = insertionPoint - 1; + } + + return depth; + } + } + + /** + * This class is a continuable tree traversal, which keeps the state + * information which would exist on the stack in a recursive traversal instead + * on a stack of "Bread Crumbs". The maximum depth of the stack in this + * iterator is the same as the depth of the tree being traversed. + * + * <p>This iterator is used to implement + * {@link RopeByteString#equalsFragments(ByteString)}. + */ + private static class PieceIterator implements Iterator<LeafByteString> { + + private final Stack<RopeByteString> breadCrumbs = + new Stack<RopeByteString>(); + private LeafByteString next; + + private PieceIterator(ByteString root) { + next = getLeafByLeft(root); + } + + private LeafByteString getLeafByLeft(ByteString root) { + ByteString pos = root; + while (pos instanceof RopeByteString) { + RopeByteString rbs = (RopeByteString) pos; + breadCrumbs.push(rbs); + pos = rbs.left; + } + return (LeafByteString) pos; + } + + private LeafByteString getNextNonEmptyLeaf() { + while (true) { + // Almost always, we go through this loop exactly once. However, if + // we discover an empty string in the rope, we toss it and try again. + if (breadCrumbs.isEmpty()) { + return null; + } else { + LeafByteString result = getLeafByLeft(breadCrumbs.pop().right); + if (!result.isEmpty()) { + return result; + } + } + } + } + + @Override + public boolean hasNext() { + return next != null; + } + + /** + * Returns the next item and advances one {@code LiteralByteString}. + * + * @return next non-empty LiteralByteString or {@code null} + */ + @Override + public LeafByteString next() { + if (next == null) { + throw new NoSuchElementException(); + } + LeafByteString result = next; + next = getNextNonEmptyLeaf(); + return result; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } + + // ================================================================= + // Serializable + + private static final long serialVersionUID = 1L; + + Object writeReplace() { + return new LiteralByteString(toByteArray()); + } + + private void readObject(@SuppressWarnings("unused") ObjectInputStream in) throws IOException { + throw new InvalidObjectException( + "RopeByteStream instances are not to be serialized directly"); + } + + /** + * This class is the {@link RopeByteString} equivalent for + * {@link ByteArrayInputStream}. + */ + private class RopeInputStream extends InputStream { + // Iterates through the pieces of the rope + private PieceIterator pieceIterator; + // The current piece + private LeafByteString currentPiece; + // The size of the current piece + private int currentPieceSize; + // The index of the next byte to read in the current piece + private int currentPieceIndex; + // The offset of the start of the current piece in the rope byte string + private int currentPieceOffsetInRope; + // Offset in the buffer at which user called mark(); + private int mark; + + public RopeInputStream() { + initialize(); + } + + @Override + public int read(byte b[], int offset, int length) { + if (b == null) { + throw new NullPointerException(); + } else if (offset < 0 || length < 0 || length > b.length - offset) { + throw new IndexOutOfBoundsException(); + } + return readSkipInternal(b, offset, length); + } + + @Override + public long skip(long length) { + if (length < 0) { + throw new IndexOutOfBoundsException(); + } else if (length > Integer.MAX_VALUE) { + length = Integer.MAX_VALUE; + } + return readSkipInternal(null, 0, (int) length); + } + + /** + * Internal implementation of read and skip. If b != null, then read the + * next {@code length} bytes into the buffer {@code b} at + * offset {@code offset}. If b == null, then skip the next {@code length} + * bytes. + * <p> + * This method assumes that all error checking has already happened. + * <p> + * Returns the actual number of bytes read or skipped. + */ + private int readSkipInternal(byte b[], int offset, int length) { + int bytesRemaining = length; + while (bytesRemaining > 0) { + advanceIfCurrentPieceFullyRead(); + if (currentPiece == null) { + if (bytesRemaining == length) { + // We didn't manage to read anything + return -1; + } + break; + } else { + // Copy the bytes from this piece. + int currentPieceRemaining = currentPieceSize - currentPieceIndex; + int count = Math.min(currentPieceRemaining, bytesRemaining); + if (b != null) { + currentPiece.copyTo(b, currentPieceIndex, offset, count); + offset += count; + } + currentPieceIndex += count; + bytesRemaining -= count; + } + } + // Return the number of bytes read. + return length - bytesRemaining; + } + + @Override + public int read() throws IOException { + advanceIfCurrentPieceFullyRead(); + if (currentPiece == null) { + return -1; + } else { + return currentPiece.byteAt(currentPieceIndex++) & 0xFF; + } + } + + @Override + public int available() throws IOException { + int bytesRead = currentPieceOffsetInRope + currentPieceIndex; + return RopeByteString.this.size() - bytesRead; + } + + @Override + public boolean markSupported() { + return true; + } + + @Override + public void mark(int readAheadLimit) { + // Set the mark to our position in the byte string + mark = currentPieceOffsetInRope + currentPieceIndex; + } + + @Override + public synchronized void reset() { + // Just reinitialize and skip the specified number of bytes. + initialize(); + readSkipInternal(null, 0, mark); + } + + /** Common initialization code used by both the constructor and reset() */ + private void initialize() { + pieceIterator = new PieceIterator(RopeByteString.this); + currentPiece = pieceIterator.next(); + currentPieceSize = currentPiece.size(); + currentPieceIndex = 0; + currentPieceOffsetInRope = 0; + } + + /** + * Skips to the next piece if we have read all the data in the current + * piece. Sets currentPiece to null if we have reached the end of the + * input. + */ + private void advanceIfCurrentPieceFullyRead() { + if (currentPiece != null && currentPieceIndex == currentPieceSize) { + // Generally, we can only go through this loop at most once, since + // empty strings can't end up in a rope. But better to test. + currentPieceOffsetInRope += currentPieceSize; + currentPieceIndex = 0; + if (pieceIterator.hasNext()) { + currentPiece = pieceIterator.next(); + currentPieceSize = currentPiece.size(); + } else { + currentPiece = null; + currentPieceSize = 0; + } + } + } + } +} diff --git a/java/core/src/main/java/com/google/protobuf/RpcCallback.java b/java/core/src/main/java/com/google/protobuf/RpcCallback.java new file mode 100644 index 00000000..10752968 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/RpcCallback.java @@ -0,0 +1,47 @@ +// 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; + +/** + * Interface for an RPC callback, normally called when an RPC completes. + * {@code ParameterType} is normally the method's response message type. + * + * <p>Starting with version 2.3.0, RPC implementations should not try to build + * on this, but should instead provide code generator plugins which generate + * code specific to the particular RPC implementation. This way the generated + * code can be more appropriate for the implementation in use and can avoid + * unnecessary layers of indirection. + * + * @author kenton@google.com Kenton Varda + */ +public interface RpcCallback<ParameterType> { + void run(ParameterType parameter); +} diff --git a/java/core/src/main/java/com/google/protobuf/RpcChannel.java b/java/core/src/main/java/com/google/protobuf/RpcChannel.java new file mode 100644 index 00000000..f272f4ad --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/RpcChannel.java @@ -0,0 +1,71 @@ +// 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; + +/** + * <p>Abstract interface for an RPC channel. An {@code RpcChannel} represents a + * communication line to a {@link Service} which can be used to call that + * {@link Service}'s methods. The {@link Service} may be running on another + * machine. Normally, you should not call an {@code RpcChannel} directly, but + * instead construct a stub {@link Service} wrapping it. Example: + * + * <pre> + * RpcChannel channel = rpcImpl.newChannel("remotehost.example.com:1234"); + * RpcController controller = rpcImpl.newController(); + * MyService service = MyService.newStub(channel); + * service.myMethod(controller, request, callback); + * </pre> + * + * <p>Starting with version 2.3.0, RPC implementations should not try to build + * on this, but should instead provide code generator plugins which generate + * code specific to the particular RPC implementation. This way the generated + * code can be more appropriate for the implementation in use and can avoid + * unnecessary layers of indirection. + * + * @author kenton@google.com Kenton Varda + */ +public interface RpcChannel { + /** + * Call the given method of the remote service. This method is similar to + * {@code Service.callMethod()} with one important difference: the caller + * decides the types of the {@code Message} objects, not the callee. The + * request may be of any type as long as + * {@code request.getDescriptor() == method.getInputType()}. + * The response passed to the callback will be of the same type as + * {@code responsePrototype} (which must have + * {@code getDescriptor() == method.getOutputType()}). + */ + void callMethod(Descriptors.MethodDescriptor method, + RpcController controller, + Message request, + Message responsePrototype, + RpcCallback<Message> done); +} diff --git a/java/core/src/main/java/com/google/protobuf/RpcController.java b/java/core/src/main/java/com/google/protobuf/RpcController.java new file mode 100644 index 00000000..a92dd7be --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/RpcController.java @@ -0,0 +1,118 @@ +// 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; + +/** + * <p>An {@code RpcController} mediates a single method call. The primary + * purpose of the controller is to provide a way to manipulate settings + * specific to the RPC implementation and to find out about RPC-level errors. + * + * <p>Starting with version 2.3.0, RPC implementations should not try to build + * on this, but should instead provide code generator plugins which generate + * code specific to the particular RPC implementation. This way the generated + * code can be more appropriate for the implementation in use and can avoid + * unnecessary layers of indirection. + * + * <p>The methods provided by the {@code RpcController} interface are intended + * to be a "least common denominator" set of features which we expect all + * implementations to support. Specific implementations may provide more + * advanced features (e.g. deadline propagation). + * + * @author kenton@google.com Kenton Varda + */ +public interface RpcController { + // ----------------------------------------------------------------- + // These calls may be made from the client side only. Their results + // are undefined on the server side (may throw RuntimeExceptions). + + /** + * Resets the RpcController to its initial state so that it may be reused in + * a new call. This can be called from the client side only. It must not + * be called while an RPC is in progress. + */ + void reset(); + + /** + * After a call has finished, returns true if the call failed. The possible + * reasons for failure depend on the RPC implementation. {@code failed()} + * most only be called on the client side, and must not be called before a + * call has finished. + */ + boolean failed(); + + /** + * If {@code failed()} is {@code true}, returns a human-readable description + * of the error. + */ + String errorText(); + + /** + * Advises the RPC system that the caller desires that the RPC call be + * canceled. The RPC system may cancel it immediately, may wait awhile and + * then cancel it, or may not even cancel the call at all. If the call is + * canceled, the "done" callback will still be called and the RpcController + * will indicate that the call failed at that time. + */ + void startCancel(); + + // ----------------------------------------------------------------- + // These calls may be made from the server side only. Their results + // are undefined on the client side (may throw RuntimeExceptions). + + /** + * Causes {@code failed()} to return true on the client side. {@code reason} + * will be incorporated into the message returned by {@code errorText()}. + * If you find you need to return machine-readable information about + * failures, you should incorporate it into your response protocol buffer + * and should NOT call {@code setFailed()}. + */ + void setFailed(String reason); + + /** + * If {@code true}, indicates that the client canceled the RPC, so the server + * may as well give up on replying to it. This method must be called on the + * server side only. The server should still call the final "done" callback. + */ + boolean isCanceled(); + + /** + * Asks that the given callback be called when the RPC is canceled. The + * parameter passed to the callback will always be {@code null}. The + * callback will always be called exactly once. If the RPC completes without + * being canceled, the callback will be called after completion. If the RPC + * has already been canceled when NotifyOnCancel() is called, the callback + * will be called immediately. + * + * <p>{@code notifyOnCancel()} must be called no more than once per request. + * It must be called on the server side only. + */ + void notifyOnCancel(RpcCallback<Object> callback); +} diff --git a/java/core/src/main/java/com/google/protobuf/RpcUtil.java b/java/core/src/main/java/com/google/protobuf/RpcUtil.java new file mode 100644 index 00000000..694b8d13 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/RpcUtil.java @@ -0,0 +1,134 @@ +// 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; + +/** + * Grab-bag of utility functions useful when dealing with RPCs. + * + * @author kenton@google.com Kenton Varda + */ +public final class RpcUtil { + private RpcUtil() {} + + /** + * Take an {@code RpcCallback<Message>} and convert it to an + * {@code RpcCallback} accepting a specific message type. This is always + * type-safe (parameter type contravariance). + */ + @SuppressWarnings("unchecked") + public static <Type extends Message> RpcCallback<Type> + specializeCallback(final RpcCallback<Message> originalCallback) { + return (RpcCallback<Type>)originalCallback; + // The above cast works, but only due to technical details of the Java + // implementation. A more theoretically correct -- but less efficient -- + // implementation would be as follows: + // return new RpcCallback<Type>() { + // public void run(Type parameter) { + // originalCallback.run(parameter); + // } + // }; + } + + /** + * Take an {@code RpcCallback} accepting a specific message type and convert + * it to an {@code RpcCallback<Message>}. The generalized callback will + * accept any message object which has the same descriptor, and will convert + * it to the correct class before calling the original callback. However, + * if the generalized callback is given a message with a different descriptor, + * an exception will be thrown. + */ + public static <Type extends Message> + RpcCallback<Message> generalizeCallback( + final RpcCallback<Type> originalCallback, + final Class<Type> originalClass, + final Type defaultInstance) { + return new RpcCallback<Message>() { + public void run(final Message parameter) { + Type typedParameter; + try { + typedParameter = originalClass.cast(parameter); + } catch (ClassCastException ignored) { + typedParameter = copyAsType(defaultInstance, parameter); + } + originalCallback.run(typedParameter); + } + }; + } + + /** + * Creates a new message of type "Type" which is a copy of "source". "source" + * must have the same descriptor but may be a different class (e.g. + * DynamicMessage). + */ + @SuppressWarnings("unchecked") + private static <Type extends Message> Type copyAsType( + final Type typeDefaultInstance, final Message source) { + return (Type) typeDefaultInstance + .newBuilderForType().mergeFrom(source).build(); + } + + /** + * Creates a callback which can only be called once. This may be useful for + * security, when passing a callback to untrusted code: most callbacks do + * not expect to be called more than once, so doing so may expose bugs if it + * is not prevented. + */ + public static <ParameterType> + RpcCallback<ParameterType> newOneTimeCallback( + final RpcCallback<ParameterType> originalCallback) { + return new RpcCallback<ParameterType>() { + private boolean alreadyCalled = false; + + public void run(final ParameterType parameter) { + synchronized(this) { + if (alreadyCalled) { + throw new AlreadyCalledException(); + } + alreadyCalled = true; + } + + originalCallback.run(parameter); + } + }; + } + + /** + * Exception thrown when a one-time callback is called more than once. + */ + public static final class AlreadyCalledException extends RuntimeException { + private static final long serialVersionUID = 5469741279507848266L; + + public AlreadyCalledException() { + super("This RpcCallback was already called and cannot be called " + + "multiple times."); + } + } +} diff --git a/java/core/src/main/java/com/google/protobuf/Service.java b/java/core/src/main/java/com/google/protobuf/Service.java new file mode 100644 index 00000000..ba7b033e --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/Service.java @@ -0,0 +1,117 @@ +// 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; + +/** + * Abstract base interface for protocol-buffer-based RPC services. Services + * themselves are abstract classes (implemented either by servers or as + * stubs), but they subclass this base interface. The methods of this + * interface can be used to call the methods of the service without knowing + * its exact type at compile time (analogous to the Message interface). + * + * <p>Starting with version 2.3.0, RPC implementations should not try to build + * on this, but should instead provide code generator plugins which generate + * code specific to the particular RPC implementation. This way the generated + * code can be more appropriate for the implementation in use and can avoid + * unnecessary layers of indirection. + * + * @author kenton@google.com Kenton Varda + */ +public interface Service { + /** + * Get the {@code ServiceDescriptor} describing this service and its methods. + */ + Descriptors.ServiceDescriptor getDescriptorForType(); + + /** + * <p>Call a method of the service specified by MethodDescriptor. This is + * normally implemented as a simple {@code switch()} that calls the standard + * definitions of the service's methods. + * + * <p>Preconditions: + * <ul> + * <li>{@code method.getService() == getDescriptorForType()} + * <li>{@code request} is of the exact same class as the object returned by + * {@code getRequestPrototype(method)}. + * <li>{@code controller} is of the correct type for the RPC implementation + * being used by this Service. For stubs, the "correct type" depends + * on the RpcChannel which the stub is using. Server-side Service + * implementations are expected to accept whatever type of + * {@code RpcController} the server-side RPC implementation uses. + * </ul> + * + * <p>Postconditions: + * <ul> + * <li>{@code done} will be called when the method is complete. This may be + * before {@code callMethod()} returns or it may be at some point in + * the future. + * <li>The parameter to {@code done} is the response. It must be of the + * exact same type as would be returned by + * {@code getResponsePrototype(method)}. + * <li>If the RPC failed, the parameter to {@code done} will be + * {@code null}. Further details about the failure can be found by + * querying {@code controller}. + * </ul> + */ + void callMethod(Descriptors.MethodDescriptor method, + RpcController controller, + Message request, + RpcCallback<Message> done); + + /** + * <p>{@code callMethod()} requires that the request passed in is of a + * particular subclass of {@code Message}. {@code getRequestPrototype()} + * gets the default instances of this type for a given method. You can then + * call {@code Message.newBuilderForType()} on this instance to + * construct a builder to build an object which you can then pass to + * {@code callMethod()}. + * + * <p>Example: + * <pre> + * MethodDescriptor method = + * service.getDescriptorForType().findMethodByName("Foo"); + * Message request = + * stub.getRequestPrototype(method).newBuilderForType() + * .mergeFrom(input).build(); + * service.callMethod(method, request, callback); + * </pre> + */ + Message getRequestPrototype(Descriptors.MethodDescriptor method); + + /** + * Like {@code getRequestPrototype()}, but gets a prototype of the response + * message. {@code getResponsePrototype()} is generally not needed because + * the {@code Service} implementation constructs the response message itself, + * but it may be useful in some cases to know ahead of time what type of + * object will be returned. + */ + Message getResponsePrototype(Descriptors.MethodDescriptor method); +} diff --git a/java/core/src/main/java/com/google/protobuf/ServiceException.java b/java/core/src/main/java/com/google/protobuf/ServiceException.java new file mode 100644 index 00000000..00d57075 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/ServiceException.java @@ -0,0 +1,52 @@ +// 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; + +/** + * Thrown by blocking RPC methods when a failure occurs. + * + * @author cpovirk@google.com (Chris Povirk) + */ +public class ServiceException extends Exception { + private static final long serialVersionUID = -1219262335729891920L; + + public ServiceException(final String message) { + super(message); + } + + public ServiceException(final Throwable cause) { + super(cause); + } + + public ServiceException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/java/core/src/main/java/com/google/protobuf/SingleFieldBuilder.java b/java/core/src/main/java/com/google/protobuf/SingleFieldBuilder.java new file mode 100644 index 00000000..aba65e32 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/SingleFieldBuilder.java @@ -0,0 +1,241 @@ +// 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; + +/** + * {@code SingleFieldBuilder} implements a structure that a protocol + * message uses to hold a single field of another protocol message. It supports + * the classical use case of setting an immutable {@link Message} as the value + * of the field and is highly optimized around this. + * <br> + * It also supports the additional use case of setting a {@link Message.Builder} + * as the field and deferring conversion of that {@code Builder} + * to an immutable {@code Message}. In this way, it's possible to maintain + * a tree of {@code Builder}'s that acts as a fully read/write data + * structure. + * <br> + * Logically, one can think of a tree of builders as converting the entire tree + * to messages when build is called on the root or when any method is called + * that desires a Message instead of a Builder. In terms of the implementation, + * the {@code SingleFieldBuilder} and {@code RepeatedFieldBuilder} + * classes cache messages that were created so that messages only need to be + * created when some change occurred in its builder or a builder for one of its + * descendants. + * + * @param <MType> the type of message for the field + * @param <BType> the type of builder for the field + * @param <IType> the common interface for the message and the builder + * + * @author jonp@google.com (Jon Perlow) + */ +public class SingleFieldBuilder + <MType extends GeneratedMessage, + BType extends GeneratedMessage.Builder, + IType extends MessageOrBuilder> + implements GeneratedMessage.BuilderParent { + + // Parent to send changes to. + private GeneratedMessage.BuilderParent parent; + + // Invariant: one of builder or message fields must be non-null. + + // If set, this is the case where we are backed by a builder. In this case, + // message field represents a cached message for the builder (or null if + // there is no cached message). + private BType builder; + + // If builder is non-null, this represents a cached message from the builder. + // If builder is null, this is the authoritative message for the field. + private MType message; + + // Indicates that we've built a message and so we are now obligated + // to dispatch dirty invalidations. See GeneratedMessage.BuilderListener. + private boolean isClean; + + public SingleFieldBuilder( + MType message, + GeneratedMessage.BuilderParent parent, + boolean isClean) { + if (message == null) { + throw new NullPointerException(); + } + this.message = message; + this.parent = parent; + this.isClean = isClean; + } + + public void dispose() { + // Null out parent so we stop sending it invalidations. + parent = null; + } + + /** + * Get the message for the field. If the message is currently stored + * as a {@code Builder}, it is converted to a {@code Message} by + * calling {@link Message.Builder#buildPartial} on it. If no message has + * been set, returns the default instance of the message. + * + * @return the message for the field + */ + @SuppressWarnings("unchecked") + public MType getMessage() { + if (message == null) { + // If message is null, the invariant is that we must be have a builder. + message = (MType) builder.buildPartial(); + } + return message; + } + + /** + * Builds the message and returns it. + * + * @return the message + */ + public MType build() { + // Now that build has been called, we are required to dispatch + // invalidations. + isClean = true; + return getMessage(); + } + + /** + * Gets a builder for the field. If no builder has been created yet, a + * builder is created on demand by calling {@link Message#toBuilder}. + * + * @return The builder for the field + */ + @SuppressWarnings("unchecked") + public BType getBuilder() { + if (builder == null) { + // builder.mergeFrom() on a fresh builder + // does not create any sub-objects with independent clean/dirty states, + // therefore setting the builder itself to clean without actually calling + // build() cannot break any invariants. + builder = (BType) message.newBuilderForType(this); + builder.mergeFrom(message); // no-op if message is the default message + builder.markClean(); + } + return builder; + } + + /** + * Gets the base class interface for the field. This may either be a builder + * or a message. It will return whatever is more efficient. + * + * @return the message or builder for the field as the base class interface + */ + @SuppressWarnings("unchecked") + public IType getMessageOrBuilder() { + if (builder != null) { + return (IType) builder; + } else { + return (IType) message; + } + } + + /** + * Sets a message for the field replacing any existing value. + * + * @param message the message to set + * @return the builder + */ + public SingleFieldBuilder<MType, BType, IType> setMessage( + MType message) { + if (message == null) { + throw new NullPointerException(); + } + this.message = message; + if (builder != null) { + builder.dispose(); + builder = null; + } + onChanged(); + return this; + } + + /** + * Merges the field from another field. + * + * @param value the value to merge from + * @return the builder + */ + public SingleFieldBuilder<MType, BType, IType> mergeFrom( + MType value) { + if (builder == null && message == message.getDefaultInstanceForType()) { + message = value; + } else { + getBuilder().mergeFrom(value); + } + onChanged(); + return this; + } + + /** + * Clears the value of the field. + * + * @return the builder + */ + @SuppressWarnings("unchecked") + public SingleFieldBuilder<MType, BType, IType> clear() { + message = (MType) (message != null ? + message.getDefaultInstanceForType() : + builder.getDefaultInstanceForType()); + if (builder != null) { + builder.dispose(); + builder = null; + } + onChanged(); + return this; + } + + /** + * Called when a the builder or one of its nested children has changed + * and any parent should be notified of its invalidation. + */ + private void onChanged() { + // If builder is null, this is the case where onChanged is being called + // from setMessage or clear. + if (builder != null) { + message = null; + } + if (isClean && parent != null) { + parent.markDirty(); + + // Don't keep dispatching invalidations until build is called again. + isClean = false; + } + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public void markDirty() { + onChanged(); + } +} diff --git a/java/core/src/main/java/com/google/protobuf/SmallSortedMap.java b/java/core/src/main/java/com/google/protobuf/SmallSortedMap.java new file mode 100644 index 00000000..0674d2e2 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/SmallSortedMap.java @@ -0,0 +1,618 @@ +// 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.AbstractMap; +import java.util.AbstractSet; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.TreeMap; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.SortedMap; + +/** + * A custom map implementation from FieldDescriptor to Object optimized to + * minimize the number of memory allocations for instances with a small number + * of mappings. The implementation stores the first {@code k} mappings in an + * array for a configurable value of {@code k}, allowing direct access to the + * corresponding {@code Entry}s without the need to create an Iterator. The + * remaining entries are stored in an overflow map. Iteration over the entries + * in the map should be done as follows: + * + * <pre> {@code + * for (int i = 0; i < fieldMap.getNumArrayEntries(); i++) { + * process(fieldMap.getArrayEntryAt(i)); + * } + * for (Map.Entry<K, V> entry : fieldMap.getOverflowEntries()) { + * process(entry); + * } + * }</pre> + * + * The resulting iteration is in order of ascending field tag number. The + * object returned by {@link #entrySet()} adheres to the same contract but is + * less efficient as it necessarily involves creating an object for iteration. + * <p> + * The tradeoff for this memory efficiency is that the worst case running time + * of the {@code put()} operation is {@code O(k + lg n)}, which happens when + * entries are added in descending order. {@code k} should be chosen such that + * it covers enough common cases without adversely affecting larger maps. In + * practice, the worst case scenario does not happen for extensions because + * extension fields are serialized and deserialized in order of ascending tag + * number, but the worst case scenario can happen for DynamicMessages. + * <p> + * The running time for all other operations is similar to that of + * {@code TreeMap}. + * <p> + * Instances are not thread-safe until {@link #makeImmutable()} is called, + * after which any modifying operation will result in an + * {@link UnsupportedOperationException}. + * + * @author darick@google.com Darick Tong + */ +// This class is final for all intents and purposes because the constructor is +// private. However, the FieldDescriptor-specific logic is encapsulated in +// a subclass to aid testability of the core logic. +class SmallSortedMap<K extends Comparable<K>, V> extends AbstractMap<K, V> { + + /** + * Creates a new instance for mapping FieldDescriptors to their values. + * The {@link #makeImmutable()} implementation will convert the List values + * of any repeated fields to unmodifiable lists. + * + * @param arraySize The size of the entry array containing the + * lexicographically smallest mappings. + */ + static <FieldDescriptorType extends + FieldSet.FieldDescriptorLite<FieldDescriptorType>> + SmallSortedMap<FieldDescriptorType, Object> newFieldMap(int arraySize) { + return new SmallSortedMap<FieldDescriptorType, Object>(arraySize) { + @Override + @SuppressWarnings("unchecked") + public void makeImmutable() { + if (!isImmutable()) { + for (int i = 0; i < getNumArrayEntries(); i++) { + final Map.Entry<FieldDescriptorType, Object> entry = + getArrayEntryAt(i); + if (entry.getKey().isRepeated()) { + final List value = (List) entry.getValue(); + entry.setValue(Collections.unmodifiableList(value)); + } + } + for (Map.Entry<FieldDescriptorType, Object> entry : + getOverflowEntries()) { + if (entry.getKey().isRepeated()) { + final List value = (List) entry.getValue(); + entry.setValue(Collections.unmodifiableList(value)); + } + } + } + super.makeImmutable(); + } + }; + } + + /** + * Creates a new instance for testing. + * + * @param arraySize The size of the entry array containing the + * lexicographically smallest mappings. + */ + static <K extends Comparable<K>, V> SmallSortedMap<K, V> newInstanceForTest( + int arraySize) { + return new SmallSortedMap<K, V>(arraySize); + } + + private final int maxArraySize; + // The "entry array" is actually a List because generic arrays are not + // allowed. ArrayList also nicely handles the entry shifting on inserts and + // removes. + private List<Entry> entryList; + private Map<K, V> overflowEntries; + private boolean isImmutable; + // The EntrySet is a stateless view of the Map. It's initialized the first + // time it is requested and reused henceforth. + private volatile EntrySet lazyEntrySet; + + /** + * @code arraySize Size of the array in which the lexicographically smallest + * mappings are stored. (i.e. the {@code k} referred to in the class + * documentation). + */ + private SmallSortedMap(int arraySize) { + this.maxArraySize = arraySize; + this.entryList = Collections.emptyList(); + this.overflowEntries = Collections.emptyMap(); + } + + /** Make this map immutable from this point forward. */ + public void makeImmutable() { + if (!isImmutable) { + // Note: There's no need to wrap the entryList in an unmodifiableList + // because none of the list's accessors are exposed. The iterator() of + // overflowEntries, on the other hand, is exposed so it must be made + // unmodifiable. + overflowEntries = overflowEntries.isEmpty() ? + Collections.<K, V>emptyMap() : + Collections.unmodifiableMap(overflowEntries); + isImmutable = true; + } + } + + /** @return Whether {@link #makeImmutable()} has been called. */ + public boolean isImmutable() { + return isImmutable; + } + + /** @return The number of entries in the entry array. */ + public int getNumArrayEntries() { + return entryList.size(); + } + + /** @return The array entry at the given {@code index}. */ + public Map.Entry<K, V> getArrayEntryAt(int index) { + return entryList.get(index); + } + + /** @return There number of overflow entries. */ + public int getNumOverflowEntries() { + return overflowEntries.size(); + } + + /** @return An iterable over the overflow entries. */ + public Iterable<Map.Entry<K, V>> getOverflowEntries() { + return overflowEntries.isEmpty() ? + EmptySet.<Map.Entry<K, V>>iterable() : + overflowEntries.entrySet(); + } + + @Override + public int size() { + return entryList.size() + overflowEntries.size(); + } + + /** + * The implementation throws a {@code ClassCastException} if o is not an + * object of type {@code K}. + * + * {@inheritDoc} + */ + @Override + public boolean containsKey(Object o) { + @SuppressWarnings("unchecked") + final K key = (K) o; + return binarySearchInArray(key) >= 0 || overflowEntries.containsKey(key); + } + + /** + * The implementation throws a {@code ClassCastException} if o is not an + * object of type {@code K}. + * + * {@inheritDoc} + */ + @Override + public V get(Object o) { + @SuppressWarnings("unchecked") + final K key = (K) o; + final int index = binarySearchInArray(key); + if (index >= 0) { + return entryList.get(index).getValue(); + } + return overflowEntries.get(key); + } + + @Override + public V put(K key, V value) { + checkMutable(); + final int index = binarySearchInArray(key); + if (index >= 0) { + // Replace existing array entry. + return entryList.get(index).setValue(value); + } + ensureEntryArrayMutable(); + final int insertionPoint = -(index + 1); + if (insertionPoint >= maxArraySize) { + // Put directly in overflow. + return getOverflowEntriesMutable().put(key, value); + } + // Insert new Entry in array. + if (entryList.size() == maxArraySize) { + // Shift the last array entry into overflow. + final Entry lastEntryInArray = entryList.remove(maxArraySize - 1); + getOverflowEntriesMutable().put(lastEntryInArray.getKey(), + lastEntryInArray.getValue()); + } + entryList.add(insertionPoint, new Entry(key, value)); + return null; + } + + @Override + public void clear() { + checkMutable(); + if (!entryList.isEmpty()) { + entryList.clear(); + } + if (!overflowEntries.isEmpty()) { + overflowEntries.clear(); + } + } + + /** + * The implementation throws a {@code ClassCastException} if o is not an + * object of type {@code K}. + * + * {@inheritDoc} + */ + @Override + public V remove(Object o) { + checkMutable(); + @SuppressWarnings("unchecked") + final K key = (K) o; + final int index = binarySearchInArray(key); + if (index >= 0) { + return removeArrayEntryAt(index); + } + // overflowEntries might be Collections.unmodifiableMap(), so only + // call remove() if it is non-empty. + if (overflowEntries.isEmpty()) { + return null; + } else { + return overflowEntries.remove(key); + } + } + + private V removeArrayEntryAt(int index) { + checkMutable(); + final V removed = entryList.remove(index).getValue(); + if (!overflowEntries.isEmpty()) { + // Shift the first entry in the overflow to be the last entry in the + // array. + final Iterator<Map.Entry<K, V>> iterator = + getOverflowEntriesMutable().entrySet().iterator(); + entryList.add(new Entry(iterator.next())); + iterator.remove(); + } + return removed; + } + + /** + * @param key The key to find in the entry array. + * @return The returned integer position follows the same semantics as the + * value returned by {@link java.util.Arrays#binarySearch()}. + */ + private int binarySearchInArray(K key) { + int left = 0; + int right = entryList.size() - 1; + + // Optimization: For the common case in which entries are added in + // ascending tag order, check the largest element in the array before + // doing a full binary search. + if (right >= 0) { + int cmp = key.compareTo(entryList.get(right).getKey()); + if (cmp > 0) { + return -(right + 2); // Insert point is after "right". + } else if (cmp == 0) { + return right; + } + } + + while (left <= right) { + int mid = (left + right) / 2; + int cmp = key.compareTo(entryList.get(mid).getKey()); + if (cmp < 0) { + right = mid - 1; + } else if (cmp > 0) { + left = mid + 1; + } else { + return mid; + } + } + return -(left + 1); + } + + /** + * Similar to the AbstractMap implementation of {@code keySet()} and + * {@code values()}, the entry set is created the first time this method is + * called, and returned in response to all subsequent calls. + * + * {@inheritDoc} + */ + @Override + public Set<Map.Entry<K, V>> entrySet() { + if (lazyEntrySet == null) { + lazyEntrySet = new EntrySet(); + } + return lazyEntrySet; + } + + /** + * @throws UnsupportedOperationException if {@link #makeImmutable()} has + * has been called. + */ + private void checkMutable() { + if (isImmutable) { + throw new UnsupportedOperationException(); + } + } + + /** + * @return a {@link SortedMap} to which overflow entries mappings can be + * added or removed. + * @throws UnsupportedOperationException if {@link #makeImmutable()} has been + * called. + */ + @SuppressWarnings("unchecked") + private SortedMap<K, V> getOverflowEntriesMutable() { + checkMutable(); + if (overflowEntries.isEmpty() && !(overflowEntries instanceof TreeMap)) { + overflowEntries = new TreeMap<K, V>(); + } + return (SortedMap<K, V>) overflowEntries; + } + + /** + * Lazily creates the entry list. Any code that adds to the list must first + * call this method. + */ + private void ensureEntryArrayMutable() { + checkMutable(); + if (entryList.isEmpty() && !(entryList instanceof ArrayList)) { + entryList = new ArrayList<Entry>(maxArraySize); + } + } + + /** + * Entry implementation that implements Comparable in order to support + * binary search within the entry array. Also checks mutability in + * {@link #setValue()}. + */ + private class Entry implements Map.Entry<K, V>, Comparable<Entry> { + + private final K key; + private V value; + + Entry(Map.Entry<K, V> copy) { + this(copy.getKey(), copy.getValue()); + } + + Entry(K key, V value) { + this.key = key; + this.value = value; + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public K getKey() { + return key; + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public V getValue() { + return value; + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public int compareTo(Entry other) { + return getKey().compareTo(other.getKey()); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public V setValue(V newValue) { + checkMutable(); + final V oldValue = this.value; + this.value = newValue; + return oldValue; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof Map.Entry)) { + return false; + } + @SuppressWarnings("unchecked") + Map.Entry<?, ?> other = (Map.Entry<?, ?>) o; + return equals(key, other.getKey()) && equals(value, other.getValue()); + } + + @Override + public int hashCode() { + return (key == null ? 0 : key.hashCode()) ^ + (value == null ? 0 : value.hashCode()); + } + + @Override + public String toString() { + return key + "=" + value; + } + + /** equals() that handles null values. */ + private boolean equals(Object o1, Object o2) { + return o1 == null ? o2 == null : o1.equals(o2); + } + } + + /** + * Stateless view of the entries in the field map. + */ + private class EntrySet extends AbstractSet<Map.Entry<K, V>> { + + @Override + public Iterator<Map.Entry<K, V>> iterator() { + return new EntryIterator(); + } + + @Override + public int size() { + return SmallSortedMap.this.size(); + } + + /** + * Throws a {@link ClassCastException} if o is not of the expected type. + * + * {@inheritDoc} + */ + @Override + public boolean contains(Object o) { + @SuppressWarnings("unchecked") + final Map.Entry<K, V> entry = (Map.Entry<K, V>) o; + final V existing = get(entry.getKey()); + final V value = entry.getValue(); + return existing == value || + (existing != null && existing.equals(value)); + } + + @Override + public boolean add(Map.Entry<K, V> entry) { + if (!contains(entry)) { + put(entry.getKey(), entry.getValue()); + return true; + } + return false; + } + + /** + * Throws a {@link ClassCastException} if o is not of the expected type. + * + * {@inheritDoc} + */ + @Override + public boolean remove(Object o) { + @SuppressWarnings("unchecked") + final Map.Entry<K, V> entry = (Map.Entry<K, V>) o; + if (contains(entry)) { + SmallSortedMap.this.remove(entry.getKey()); + return true; + } + return false; + } + + @Override + public void clear() { + SmallSortedMap.this.clear(); + } + } + + /** + * Iterator implementation that switches from the entry array to the overflow + * entries appropriately. + */ + private class EntryIterator implements Iterator<Map.Entry<K, V>> { + + private int pos = -1; + private boolean nextCalledBeforeRemove; + private Iterator<Map.Entry<K, V>> lazyOverflowIterator; + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public boolean hasNext() { + return (pos + 1) < entryList.size() || + getOverflowIterator().hasNext(); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public Map.Entry<K, V> next() { + nextCalledBeforeRemove = true; + // Always increment pos so that we know whether the last returned value + // was from the array or from overflow. + if (++pos < entryList.size()) { + return entryList.get(pos); + } + return getOverflowIterator().next(); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public void remove() { + if (!nextCalledBeforeRemove) { + throw new IllegalStateException("remove() was called before next()"); + } + nextCalledBeforeRemove = false; + checkMutable(); + + if (pos < entryList.size()) { + removeArrayEntryAt(pos--); + } else { + getOverflowIterator().remove(); + } + } + + /** + * It is important to create the overflow iterator only after the array + * entries have been iterated over because the overflow entry set changes + * when the client calls remove() on the array entries, which invalidates + * any existing iterators. + */ + private Iterator<Map.Entry<K, V>> getOverflowIterator() { + if (lazyOverflowIterator == null) { + lazyOverflowIterator = overflowEntries.entrySet().iterator(); + } + return lazyOverflowIterator; + } + } + + /** + * Helper class that holds immutable instances of an Iterable/Iterator that + * we return when the overflow entries is empty. This eliminates the creation + * of an Iterator object when there is nothing to iterate over. + */ + private static class EmptySet { + + private static final Iterator<Object> ITERATOR = new Iterator<Object>() { + //@Override (Java 1.6 override semantics, but we must support 1.5) + public boolean hasNext() { + return false; + } + //@Override (Java 1.6 override semantics, but we must support 1.5) + public Object next() { + throw new NoSuchElementException(); + } + //@Override (Java 1.6 override semantics, but we must support 1.5) + public void remove() { + throw new UnsupportedOperationException(); + } + }; + + private static final Iterable<Object> ITERABLE = new Iterable<Object>() { + //@Override (Java 1.6 override semantics, but we must support 1.5) + public Iterator<Object> iterator() { + return ITERATOR; + } + }; + + @SuppressWarnings("unchecked") + static <T> Iterable<T> iterable() { + return (Iterable<T>) ITERABLE; + } + } +} diff --git a/java/core/src/main/java/com/google/protobuf/TextFormat.java b/java/core/src/main/java/com/google/protobuf/TextFormat.java new file mode 100644 index 00000000..c99b5285 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/TextFormat.java @@ -0,0 +1,2065 @@ +// 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 java.io.IOException; +import java.math.BigInteger; +import java.nio.CharBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Provide text parsing and formatting support for proto2 instances. + * The implementation largely follows google/protobuf/text_format.cc. + * + * @author wenboz@google.com Wenbo Zhu + * @author kenton@google.com Kenton Varda + */ +public final class TextFormat { + private TextFormat() {} + + private static final Logger logger = + Logger.getLogger(TextFormat.class.getName()); + + private static final Printer DEFAULT_PRINTER = new Printer(); + private static final Printer SINGLE_LINE_PRINTER = + (new Printer()).setSingleLineMode(true); + private static final Printer UNICODE_PRINTER = + (new Printer()).setEscapeNonAscii(false); + + /** + * Outputs a textual representation of the Protocol Message supplied into + * the parameter output. (This representation is the new version of the + * classic "ProtocolPrinter" output from the original Protocol Buffer system) + */ + public static void print( + final MessageOrBuilder message, final Appendable output) + throws IOException { + DEFAULT_PRINTER.print(message, new TextGenerator(output)); + } + + /** Outputs a textual representation of {@code fields} to {@code output}. */ + public static void print(final UnknownFieldSet fields, + final Appendable output) + throws IOException { + DEFAULT_PRINTER.printUnknownFields(fields, new TextGenerator(output)); + } + + /** + * Same as {@code print()}, except that non-ASCII characters are not + * escaped. + */ + public static void printUnicode( + final MessageOrBuilder message, final Appendable output) + throws IOException { + UNICODE_PRINTER.print(message, new TextGenerator(output)); + } + + /** + * Same as {@code print()}, except that non-ASCII characters are not + * escaped. + */ + public static void printUnicode(final UnknownFieldSet fields, + final Appendable output) + throws IOException { + UNICODE_PRINTER.printUnknownFields(fields, new TextGenerator(output)); + } + + /** + * Generates a human readable form of this message, useful for debugging and + * other purposes, with no newline characters. + */ + public static String shortDebugString(final MessageOrBuilder message) { + try { + final StringBuilder sb = new StringBuilder(); + SINGLE_LINE_PRINTER.print(message, new TextGenerator(sb)); + // Single line mode currently might have an extra space at the end. + return sb.toString().trim(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + /** + * Generates a human readable form of the field, useful for debugging + * and other purposes, with no newline characters. + */ + public static String shortDebugString(final FieldDescriptor field, + final Object value) { + try { + final StringBuilder sb = new StringBuilder(); + SINGLE_LINE_PRINTER.printField(field, value, new TextGenerator(sb)); + return sb.toString().trim(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + /** + * Generates a human readable form of the unknown fields, useful for debugging + * and other purposes, with no newline characters. + */ + public static String shortDebugString(final UnknownFieldSet fields) { + try { + final StringBuilder sb = new StringBuilder(); + SINGLE_LINE_PRINTER.printUnknownFields(fields, new TextGenerator(sb)); + // Single line mode currently might have an extra space at the end. + return sb.toString().trim(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + /** + * Like {@code print()}, but writes directly to a {@code String} and + * returns it. + */ + public static String printToString(final MessageOrBuilder message) { + try { + final StringBuilder text = new StringBuilder(); + print(message, text); + return text.toString(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + /** + * Like {@code print()}, but writes directly to a {@code String} and + * returns it. + */ + public static String printToString(final UnknownFieldSet fields) { + try { + final StringBuilder text = new StringBuilder(); + print(fields, text); + return text.toString(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + /** + * Same as {@code printToString()}, except that non-ASCII characters + * in string type fields are not escaped in backslash+octals. + */ + public static String printToUnicodeString(final MessageOrBuilder message) { + try { + final StringBuilder text = new StringBuilder(); + UNICODE_PRINTER.print(message, new TextGenerator(text)); + return text.toString(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + /** + * Same as {@code printToString()}, except that non-ASCII characters + * in string type fields are not escaped in backslash+octals. + */ + public static String printToUnicodeString(final UnknownFieldSet fields) { + try { + final StringBuilder text = new StringBuilder(); + UNICODE_PRINTER.printUnknownFields(fields, new TextGenerator(text)); + return text.toString(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + public static void printField(final FieldDescriptor field, + final Object value, + final Appendable output) + throws IOException { + DEFAULT_PRINTER.printField(field, value, new TextGenerator(output)); + } + + public static String printFieldToString(final FieldDescriptor field, + final Object value) { + try { + final StringBuilder text = new StringBuilder(); + printField(field, value, text); + return text.toString(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + /** + * Outputs a textual representation of the value of given field value. + * + * @param field the descriptor of the field + * @param value the value of the field + * @param output the output to which to append the formatted value + * @throws ClassCastException if the value is not appropriate for the + * given field descriptor + * @throws IOException if there is an exception writing to the output + */ + public static void printFieldValue(final FieldDescriptor field, + final Object value, + final Appendable output) + throws IOException { + DEFAULT_PRINTER.printFieldValue(field, value, new TextGenerator(output)); + } + + /** + * Outputs a textual representation of the value of an unknown field. + * + * @param tag the field's tag number + * @param value the value of the field + * @param output the output to which to append the formatted value + * @throws ClassCastException if the value is not appropriate for the + * given field descriptor + * @throws IOException if there is an exception writing to the output + */ + public static void printUnknownFieldValue(final int tag, + final Object value, + final Appendable output) + throws IOException { + printUnknownFieldValue(tag, value, new TextGenerator(output)); + } + + private static void printUnknownFieldValue(final int tag, + final Object value, + final TextGenerator generator) + throws IOException { + switch (WireFormat.getTagWireType(tag)) { + case WireFormat.WIRETYPE_VARINT: + generator.print(unsignedToString((Long) value)); + break; + case WireFormat.WIRETYPE_FIXED32: + generator.print( + String.format((Locale) null, "0x%08x", (Integer) value)); + break; + case WireFormat.WIRETYPE_FIXED64: + generator.print(String.format((Locale) null, "0x%016x", (Long) value)); + break; + case WireFormat.WIRETYPE_LENGTH_DELIMITED: + generator.print("\""); + generator.print(escapeBytes((ByteString) value)); + generator.print("\""); + break; + case WireFormat.WIRETYPE_START_GROUP: + DEFAULT_PRINTER.printUnknownFields((UnknownFieldSet) value, generator); + break; + default: + throw new IllegalArgumentException("Bad tag: " + tag); + } + } + + /** Helper class for converting protobufs to text. */ + private static final class Printer { + /** Whether to omit newlines from the output. */ + boolean singleLineMode = false; + + /** Whether to escape non ASCII characters with backslash and octal. */ + boolean escapeNonAscii = true; + + private Printer() {} + + /** Setter of singleLineMode */ + private Printer setSingleLineMode(boolean singleLineMode) { + this.singleLineMode = singleLineMode; + return this; + } + + /** Setter of escapeNonAscii */ + private Printer setEscapeNonAscii(boolean escapeNonAscii) { + this.escapeNonAscii = escapeNonAscii; + return this; + } + + private void print( + final MessageOrBuilder message, final TextGenerator generator) + throws IOException { + for (Map.Entry<FieldDescriptor, Object> field + : message.getAllFields().entrySet()) { + printField(field.getKey(), field.getValue(), generator); + } + printUnknownFields(message.getUnknownFields(), generator); + } + + private void printField(final FieldDescriptor field, final Object value, + final TextGenerator generator) throws IOException { + if (field.isRepeated()) { + // Repeated field. Print each element. + for (Object element : (List<?>) value) { + printSingleField(field, element, generator); + } + } else { + printSingleField(field, value, generator); + } + } + + private void printSingleField(final FieldDescriptor field, + final Object value, + final TextGenerator generator) + throws IOException { + if (field.isExtension()) { + generator.print("["); + // We special-case MessageSet elements for compatibility with proto1. + if (field.getContainingType().getOptions().getMessageSetWireFormat() + && (field.getType() == FieldDescriptor.Type.MESSAGE) + && (field.isOptional()) + // object equality + && (field.getExtensionScope() == field.getMessageType())) { + generator.print(field.getMessageType().getFullName()); + } else { + generator.print(field.getFullName()); + } + generator.print("]"); + } else { + if (field.getType() == FieldDescriptor.Type.GROUP) { + // Groups must be serialized with their original capitalization. + generator.print(field.getMessageType().getName()); + } else { + generator.print(field.getName()); + } + } + + if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + if (singleLineMode) { + generator.print(" { "); + } else { + generator.print(" {\n"); + generator.indent(); + } + } else { + generator.print(": "); + } + + printFieldValue(field, value, generator); + + if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + if (singleLineMode) { + generator.print("} "); + } else { + generator.outdent(); + generator.print("}\n"); + } + } else { + if (singleLineMode) { + generator.print(" "); + } else { + generator.print("\n"); + } + } + } + + private void printFieldValue(final FieldDescriptor field, + final Object value, + final TextGenerator generator) + throws IOException { + switch (field.getType()) { + case INT32: + case SINT32: + case SFIXED32: + generator.print(((Integer) value).toString()); + break; + + case INT64: + case SINT64: + case SFIXED64: + generator.print(((Long) value).toString()); + break; + + case BOOL: + generator.print(((Boolean) value).toString()); + break; + + case FLOAT: + generator.print(((Float) value).toString()); + break; + + case DOUBLE: + generator.print(((Double) value).toString()); + break; + + case UINT32: + case FIXED32: + generator.print(unsignedToString((Integer) value)); + break; + + case UINT64: + case FIXED64: + generator.print(unsignedToString((Long) value)); + break; + + case STRING: + generator.print("\""); + generator.print(escapeNonAscii + ? escapeText((String) value) + : escapeDoubleQuotesAndBackslashes((String) value) + .replace("\n", "\\n")); + generator.print("\""); + break; + + case BYTES: + generator.print("\""); + if (value instanceof ByteString) { + generator.print(escapeBytes((ByteString) value)); + } else { + generator.print(escapeBytes((byte[]) value)); + } + generator.print("\""); + break; + + case ENUM: + generator.print(((EnumValueDescriptor) value).getName()); + break; + + case MESSAGE: + case GROUP: + print((Message) value, generator); + break; + } + } + + private void printUnknownFields(final UnknownFieldSet unknownFields, + final TextGenerator generator) + throws IOException { + for (Map.Entry<Integer, UnknownFieldSet.Field> entry : + unknownFields.asMap().entrySet()) { + final int number = entry.getKey(); + final UnknownFieldSet.Field field = entry.getValue(); + printUnknownField(number, WireFormat.WIRETYPE_VARINT, + field.getVarintList(), generator); + printUnknownField(number, WireFormat.WIRETYPE_FIXED32, + field.getFixed32List(), generator); + printUnknownField(number, WireFormat.WIRETYPE_FIXED64, + field.getFixed64List(), generator); + printUnknownField(number, WireFormat.WIRETYPE_LENGTH_DELIMITED, + field.getLengthDelimitedList(), generator); + for (final UnknownFieldSet value : field.getGroupList()) { + generator.print(entry.getKey().toString()); + if (singleLineMode) { + generator.print(" { "); + } else { + generator.print(" {\n"); + generator.indent(); + } + printUnknownFields(value, generator); + if (singleLineMode) { + generator.print("} "); + } else { + generator.outdent(); + generator.print("}\n"); + } + } + } + } + + private void printUnknownField(final int number, + final int wireType, + final List<?> values, + final TextGenerator generator) + throws IOException { + for (final Object value : values) { + generator.print(String.valueOf(number)); + generator.print(": "); + printUnknownFieldValue(wireType, value, generator); + generator.print(singleLineMode ? " " : "\n"); + } + } + } + + /** Convert an unsigned 32-bit integer to a string. */ + public static String unsignedToString(final int value) { + if (value >= 0) { + return Integer.toString(value); + } else { + return Long.toString(value & 0x00000000FFFFFFFFL); + } + } + + /** Convert an unsigned 64-bit integer to a string. */ + public static String unsignedToString(final long value) { + if (value >= 0) { + return Long.toString(value); + } else { + // Pull off the most-significant bit so that BigInteger doesn't think + // the number is negative, then set it again using setBit(). + return BigInteger.valueOf(value & 0x7FFFFFFFFFFFFFFFL) + .setBit(63).toString(); + } + } + + /** + * An inner class for writing text to the output stream. + */ + private static final class TextGenerator { + private final Appendable output; + private final StringBuilder indent = new StringBuilder(); + private boolean atStartOfLine = true; + + private TextGenerator(final Appendable output) { + this.output = output; + } + + /** + * Indent text by two spaces. After calling Indent(), two spaces will be + * inserted at the beginning of each line of text. Indent() may be called + * multiple times to produce deeper indents. + */ + public void indent() { + indent.append(" "); + } + + /** + * Reduces the current indent level by two spaces, or crashes if the indent + * level is zero. + */ + public void outdent() { + final int length = indent.length(); + if (length == 0) { + throw new IllegalArgumentException( + " Outdent() without matching Indent()."); + } + indent.delete(length - 2, length); + } + + /** + * Print text to the output stream. + */ + public void print(final CharSequence text) throws IOException { + final int size = text.length(); + int pos = 0; + + for (int i = 0; i < size; i++) { + if (text.charAt(i) == '\n') { + write(text.subSequence(pos, i + 1)); + pos = i + 1; + atStartOfLine = true; + } + } + write(text.subSequence(pos, size)); + } + + private void write(final CharSequence data) throws IOException { + if (data.length() == 0) { + return; + } + if (atStartOfLine) { + atStartOfLine = false; + output.append(indent); + } + output.append(data); + } + } + + // ================================================================= + // Parsing + + /** + * Represents a stream of tokens parsed from a {@code String}. + * + * <p>The Java standard library provides many classes that you might think + * would be useful for implementing this, but aren't. For example: + * + * <ul> + * <li>{@code java.io.StreamTokenizer}: This almost does what we want -- or, + * at least, something that would get us close to what we want -- except + * for one fatal flaw: It automatically un-escapes strings using Java + * escape sequences, which do not include all the escape sequences we + * need to support (e.g. '\x'). + * <li>{@code java.util.Scanner}: This seems like a great way at least to + * parse regular expressions out of a stream (so we wouldn't have to load + * the entire input into a single string before parsing). Sadly, + * {@code Scanner} requires that tokens be delimited with some delimiter. + * Thus, although the text "foo:" should parse to two tokens ("foo" and + * ":"), {@code Scanner} would recognize it only as a single token. + * Furthermore, {@code Scanner} provides no way to inspect the contents + * of delimiters, making it impossible to keep track of line and column + * numbers. + * </ul> + * + * <p>Luckily, Java's regular expression support does manage to be useful to + * us. (Barely: We need {@code Matcher.usePattern()}, which is new in + * Java 1.5.) So, we can use that, at least. Unfortunately, this implies + * that we need to have the entire input in one contiguous string. + */ + private static final class Tokenizer { + private final CharSequence text; + private final Matcher matcher; + private String currentToken; + + // The character index within this.text at which the current token begins. + private int pos = 0; + + // The line and column numbers of the current token. + private int line = 0; + private int column = 0; + + // The line and column numbers of the previous token (allows throwing + // errors *after* consuming). + private int previousLine = 0; + private int previousColumn = 0; + + // We use possessive quantifiers (*+ and ++) because otherwise the Java + // regex matcher has stack overflows on large inputs. + private static final Pattern WHITESPACE = + Pattern.compile("(\\s|(#.*$))++", Pattern.MULTILINE); + private static final Pattern TOKEN = Pattern.compile( + "[a-zA-Z_][0-9a-zA-Z_+-]*+|" + // an identifier + "[.]?[0-9+-][0-9a-zA-Z_.+-]*+|" + // a number + "\"([^\"\n\\\\]|\\\\.)*+(\"|\\\\?$)|" + // a double-quoted string + "\'([^\'\n\\\\]|\\\\.)*+(\'|\\\\?$)", // a single-quoted string + Pattern.MULTILINE); + + private static final Pattern DOUBLE_INFINITY = Pattern.compile( + "-?inf(inity)?", + Pattern.CASE_INSENSITIVE); + private static final Pattern FLOAT_INFINITY = Pattern.compile( + "-?inf(inity)?f?", + Pattern.CASE_INSENSITIVE); + private static final Pattern FLOAT_NAN = Pattern.compile( + "nanf?", + Pattern.CASE_INSENSITIVE); + + /** Construct a tokenizer that parses tokens from the given text. */ + private Tokenizer(final CharSequence text) { + this.text = text; + this.matcher = WHITESPACE.matcher(text); + skipWhitespace(); + nextToken(); + } + + /** Are we at the end of the input? */ + public boolean atEnd() { + return currentToken.length() == 0; + } + + /** Advance to the next token. */ + public void nextToken() { + previousLine = line; + previousColumn = column; + + // Advance the line counter to the current position. + while (pos < matcher.regionStart()) { + if (text.charAt(pos) == '\n') { + ++line; + column = 0; + } else { + ++column; + } + ++pos; + } + + // Match the next token. + if (matcher.regionStart() == matcher.regionEnd()) { + // EOF + currentToken = ""; + } else { + matcher.usePattern(TOKEN); + if (matcher.lookingAt()) { + currentToken = matcher.group(); + matcher.region(matcher.end(), matcher.regionEnd()); + } else { + // Take one character. + currentToken = String.valueOf(text.charAt(pos)); + matcher.region(pos + 1, matcher.regionEnd()); + } + + skipWhitespace(); + } + } + + /** + * Skip over any whitespace so that the matcher region starts at the next + * token. + */ + private void skipWhitespace() { + matcher.usePattern(WHITESPACE); + if (matcher.lookingAt()) { + matcher.region(matcher.end(), matcher.regionEnd()); + } + } + + /** + * If the next token exactly matches {@code token}, consume it and return + * {@code true}. Otherwise, return {@code false} without doing anything. + */ + public boolean tryConsume(final String token) { + if (currentToken.equals(token)) { + nextToken(); + return true; + } else { + return false; + } + } + + /** + * If the next token exactly matches {@code token}, consume it. Otherwise, + * throw a {@link ParseException}. + */ + public void consume(final String token) throws ParseException { + if (!tryConsume(token)) { + throw parseException("Expected \"" + token + "\"."); + } + } + + /** + * Returns {@code true} if the next token is an integer, but does + * not consume it. + */ + public boolean lookingAtInteger() { + if (currentToken.length() == 0) { + return false; + } + + final char c = currentToken.charAt(0); + return ('0' <= c && c <= '9') + || c == '-' || c == '+'; + } + + /** + * Returns {@code true} if the current token's text is equal to that + * specified. + */ + public boolean lookingAt(String text) { + return currentToken.equals(text); + } + + /** + * If the next token is an identifier, consume it and return its value. + * Otherwise, throw a {@link ParseException}. + */ + public String consumeIdentifier() throws ParseException { + for (int i = 0; i < currentToken.length(); i++) { + final char c = currentToken.charAt(i); + if (('a' <= c && c <= 'z') + || ('A' <= c && c <= 'Z') + || ('0' <= c && c <= '9') + || (c == '_') || (c == '.')) { + // OK + } else { + throw parseException( + "Expected identifier. Found '" + currentToken + "'"); + } + } + + final String result = currentToken; + nextToken(); + return result; + } + + /** + * If the next token is an identifier, consume it and return {@code true}. + * Otherwise, return {@code false} without doing anything. + */ + public boolean tryConsumeIdentifier() { + try { + consumeIdentifier(); + return true; + } catch (ParseException e) { + return false; + } + } + + /** + * If the next token is a 32-bit signed integer, consume it and return its + * value. Otherwise, throw a {@link ParseException}. + */ + public int consumeInt32() throws ParseException { + try { + final int result = parseInt32(currentToken); + nextToken(); + return result; + } catch (NumberFormatException e) { + throw integerParseException(e); + } + } + + /** + * If the next token is a 32-bit unsigned integer, consume it and return its + * value. Otherwise, throw a {@link ParseException}. + */ + public int consumeUInt32() throws ParseException { + try { + final int result = parseUInt32(currentToken); + nextToken(); + return result; + } catch (NumberFormatException e) { + throw integerParseException(e); + } + } + + /** + * If the next token is a 64-bit signed integer, consume it and return its + * value. Otherwise, throw a {@link ParseException}. + */ + public long consumeInt64() throws ParseException { + try { + final long result = parseInt64(currentToken); + nextToken(); + return result; + } catch (NumberFormatException e) { + throw integerParseException(e); + } + } + + /** + * If the next token is a 64-bit signed integer, consume it and return + * {@code true}. Otherwise, return {@code false} without doing anything. + */ + public boolean tryConsumeInt64() { + try { + consumeInt64(); + return true; + } catch (ParseException e) { + return false; + } + } + + /** + * If the next token is a 64-bit unsigned integer, consume it and return its + * value. Otherwise, throw a {@link ParseException}. + */ + public long consumeUInt64() throws ParseException { + try { + final long result = parseUInt64(currentToken); + nextToken(); + return result; + } catch (NumberFormatException e) { + throw integerParseException(e); + } + } + + /** + * If the next token is a 64-bit unsigned integer, consume it and return + * {@code true}. Otherwise, return {@code false} without doing anything. + */ + public boolean tryConsumeUInt64() { + try { + consumeUInt64(); + return true; + } catch (ParseException e) { + return false; + } + } + + /** + * If the next token is a double, consume it and return its value. + * Otherwise, throw a {@link ParseException}. + */ + public double consumeDouble() throws ParseException { + // We need to parse infinity and nan separately because + // Double.parseDouble() does not accept "inf", "infinity", or "nan". + if (DOUBLE_INFINITY.matcher(currentToken).matches()) { + final boolean negative = currentToken.startsWith("-"); + nextToken(); + return negative ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY; + } + if (currentToken.equalsIgnoreCase("nan")) { + nextToken(); + return Double.NaN; + } + try { + final double result = Double.parseDouble(currentToken); + nextToken(); + return result; + } catch (NumberFormatException e) { + throw floatParseException(e); + } + } + + /** + * If the next token is a double, consume it and return {@code true}. + * Otherwise, return {@code false} without doing anything. + */ + public boolean tryConsumeDouble() { + try { + consumeDouble(); + return true; + } catch (ParseException e) { + return false; + } + } + + /** + * If the next token is a float, consume it and return its value. + * Otherwise, throw a {@link ParseException}. + */ + public float consumeFloat() throws ParseException { + // We need to parse infinity and nan separately because + // Float.parseFloat() does not accept "inf", "infinity", or "nan". + if (FLOAT_INFINITY.matcher(currentToken).matches()) { + final boolean negative = currentToken.startsWith("-"); + nextToken(); + return negative ? Float.NEGATIVE_INFINITY : Float.POSITIVE_INFINITY; + } + if (FLOAT_NAN.matcher(currentToken).matches()) { + nextToken(); + return Float.NaN; + } + try { + final float result = Float.parseFloat(currentToken); + nextToken(); + return result; + } catch (NumberFormatException e) { + throw floatParseException(e); + } + } + + /** + * If the next token is a float, consume it and return {@code true}. + * Otherwise, return {@code false} without doing anything. + */ + public boolean tryConsumeFloat() { + try { + consumeFloat(); + return true; + } catch (ParseException e) { + return false; + } + } + + /** + * If the next token is a boolean, consume it and return its value. + * Otherwise, throw a {@link ParseException}. + */ + public boolean consumeBoolean() throws ParseException { + if (currentToken.equals("true") + || currentToken.equals("t") + || currentToken.equals("1")) { + nextToken(); + return true; + } else if (currentToken.equals("false") + || currentToken.equals("f") + || currentToken.equals("0")) { + nextToken(); + return false; + } else { + throw parseException("Expected \"true\" or \"false\"."); + } + } + + /** + * If the next token is a string, consume it and return its (unescaped) + * value. Otherwise, throw a {@link ParseException}. + */ + public String consumeString() throws ParseException { + return consumeByteString().toStringUtf8(); + } + + /** + * If the next token is a string, consume it and return true. Otherwise, + * return false. + */ + public boolean tryConsumeString() { + try { + consumeString(); + return true; + } catch (ParseException e) { + return false; + } + } + + /** + * If the next token is a string, consume it, unescape it as a + * {@link ByteString}, and return it. Otherwise, throw a + * {@link ParseException}. + */ + public ByteString consumeByteString() throws ParseException { + List<ByteString> list = new ArrayList<ByteString>(); + consumeByteString(list); + while (currentToken.startsWith("'") || currentToken.startsWith("\"")) { + consumeByteString(list); + } + return ByteString.copyFrom(list); + } + + /** + * Like {@link #consumeByteString()} but adds each token of the string to + * the given list. String literals (whether bytes or text) may come in + * multiple adjacent tokens which are automatically concatenated, like in + * C or Python. + */ + private void consumeByteString(List<ByteString> list) + throws ParseException { + final char quote = currentToken.length() > 0 + ? currentToken.charAt(0) + : '\0'; + if (quote != '\"' && quote != '\'') { + throw parseException("Expected string."); + } + + if (currentToken.length() < 2 + || currentToken.charAt(currentToken.length() - 1) != quote) { + throw parseException("String missing ending quote."); + } + + try { + final String escaped = + currentToken.substring(1, currentToken.length() - 1); + final ByteString result = unescapeBytes(escaped); + nextToken(); + list.add(result); + } catch (InvalidEscapeSequenceException e) { + throw parseException(e.getMessage()); + } + } + + /** + * Returns a {@link ParseException} with the current line and column + * numbers in the description, suitable for throwing. + */ + public ParseException parseException(final String description) { + // Note: People generally prefer one-based line and column numbers. + return new ParseException( + line + 1, column + 1, description); + } + + /** + * Returns a {@link ParseException} with the line and column numbers of + * the previous token in the description, suitable for throwing. + */ + public ParseException parseExceptionPreviousToken( + final String description) { + // Note: People generally prefer one-based line and column numbers. + return new ParseException( + previousLine + 1, previousColumn + 1, description); + } + + /** + * Constructs an appropriate {@link ParseException} for the given + * {@code NumberFormatException} when trying to parse an integer. + */ + private ParseException integerParseException( + final NumberFormatException e) { + return parseException("Couldn't parse integer: " + e.getMessage()); + } + + /** + * Constructs an appropriate {@link ParseException} for the given + * {@code NumberFormatException} when trying to parse a float or double. + */ + private ParseException floatParseException(final NumberFormatException e) { + return parseException("Couldn't parse number: " + e.getMessage()); + } + + /** + * Returns a {@link UnknownFieldParseException} with the line and column + * numbers of the previous token in the description, and the unknown field + * name, suitable for throwing. + */ + public UnknownFieldParseException unknownFieldParseExceptionPreviousToken( + final String unknownField, final String description) { + // Note: People generally prefer one-based line and column numbers. + return new UnknownFieldParseException( + previousLine + 1, previousColumn + 1, unknownField, description); + } + } + + /** Thrown when parsing an invalid text format message. */ + public static class ParseException extends IOException { + private static final long serialVersionUID = 3196188060225107702L; + + private final int line; + private final int column; + + /** Create a new instance, with -1 as the line and column numbers. */ + public ParseException(final String message) { + this(-1, -1, message); + } + + /** + * Create a new instance + * + * @param line the line number where the parse error occurred, + * using 1-offset. + * @param column the column number where the parser error occurred, + * using 1-offset. + */ + public ParseException(final int line, final int column, + final String message) { + super(Integer.toString(line) + ":" + column + ": " + message); + this.line = line; + this.column = column; + } + + /** + * Return the line where the parse exception occurred, or -1 when + * none is provided. The value is specified as 1-offset, so the first + * line is line 1. + */ + public int getLine() { + return line; + } + + /** + * Return the column where the parse exception occurred, or -1 when + * none is provided. The value is specified as 1-offset, so the first + * line is line 1. + */ + public int getColumn() { + return column; + } + } + + /** + * Thrown when encountering an unknown field while parsing + * a text format message. + */ + public static class UnknownFieldParseException extends ParseException { + private final String unknownField; + + /** + * Create a new instance, with -1 as the line and column numbers, and an + * empty unknown field name. + */ + public UnknownFieldParseException(final String message) { + this(-1, -1, "", message); + } + + /** + * Create a new instance + * + * @param line the line number where the parse error occurred, + * using 1-offset. + * @param column the column number where the parser error occurred, + * using 1-offset. + * @param unknownField the name of the unknown field found while parsing. + */ + public UnknownFieldParseException(final int line, final int column, + final String unknownField, final String message) { + super(line, column, message); + this.unknownField = unknownField; + } + + /** + * Return the name of the unknown field encountered while parsing the + * protocol buffer string. + */ + public String getUnknownField() { + return unknownField; + } + } + + private static final Parser PARSER = Parser.newBuilder().build(); + + /** + * Return a {@link Parser} instance which can parse text-format + * messages. The returned instance is thread-safe. + */ + public static Parser getParser() { + return PARSER; + } + + /** + * Parse a text-format message from {@code input} and merge the contents + * into {@code builder}. + */ + public static void merge(final Readable input, + final Message.Builder builder) + throws IOException { + PARSER.merge(input, builder); + } + + /** + * Parse a text-format message from {@code input} and merge the contents + * into {@code builder}. + */ + public static void merge(final CharSequence input, + final Message.Builder builder) + throws ParseException { + PARSER.merge(input, builder); + } + + /** + * Parse a text-format message from {@code input} and merge the contents + * into {@code builder}. Extensions will be recognized if they are + * registered in {@code extensionRegistry}. + */ + public static void merge(final Readable input, + final ExtensionRegistry extensionRegistry, + final Message.Builder builder) + throws IOException { + PARSER.merge(input, extensionRegistry, builder); + } + + + /** + * Parse a text-format message from {@code input} and merge the contents + * into {@code builder}. Extensions will be recognized if they are + * registered in {@code extensionRegistry}. + */ + public static void merge(final CharSequence input, + final ExtensionRegistry extensionRegistry, + final Message.Builder builder) + throws ParseException { + PARSER.merge(input, extensionRegistry, builder); + } + + + /** + * Parser for text-format proto2 instances. This class is thread-safe. + * The implementation largely follows google/protobuf/text_format.cc. + * + * <p>Use {@link TextFormat#getParser()} to obtain the default parser, or + * {@link Builder} to control the parser behavior. + */ + public static class Parser { + /** + * Determines if repeated values for non-repeated fields and + * oneofs are permitted. For example, given required/optional field "foo" + * and a oneof containing "baz" and "qux": + * <ul> + * <li>"foo: 1 foo: 2" + * <li>"baz: 1 qux: 2" + * <li>merging "foo: 2" into a proto in which foo is already set, or + * <li>merging "qux: 2" into a proto in which baz is already set. + * </ul> + */ + public enum SingularOverwritePolicy { + /** The last value is retained. */ + ALLOW_SINGULAR_OVERWRITES, + /** An error is issued. */ + FORBID_SINGULAR_OVERWRITES + } + + private final boolean allowUnknownFields; + private final SingularOverwritePolicy singularOverwritePolicy; + + private Parser(boolean allowUnknownFields, + SingularOverwritePolicy singularOverwritePolicy) { + this.allowUnknownFields = allowUnknownFields; + this.singularOverwritePolicy = singularOverwritePolicy; + } + + /** + * Returns a new instance of {@link Builder}. + */ + public static Builder newBuilder() { + return new Builder(); + } + + /** + * Builder that can be used to obtain new instances of {@link Parser}. + */ + public static class Builder { + private boolean allowUnknownFields = false; + private SingularOverwritePolicy singularOverwritePolicy = + SingularOverwritePolicy.ALLOW_SINGULAR_OVERWRITES; + + + /** + * Sets parser behavior when a non-repeated field appears more than once. + */ + public Builder setSingularOverwritePolicy(SingularOverwritePolicy p) { + this.singularOverwritePolicy = p; + return this; + } + + public Parser build() { + return new Parser(allowUnknownFields, singularOverwritePolicy); + } + } + + /** + * Parse a text-format message from {@code input} and merge the contents + * into {@code builder}. + */ + public void merge(final Readable input, + final Message.Builder builder) + throws IOException { + merge(input, ExtensionRegistry.getEmptyRegistry(), builder); + } + + /** + * Parse a text-format message from {@code input} and merge the contents + * into {@code builder}. + */ + public void merge(final CharSequence input, + final Message.Builder builder) + throws ParseException { + merge(input, ExtensionRegistry.getEmptyRegistry(), builder); + } + + /** + * Parse a text-format message from {@code input} and merge the contents + * into {@code builder}. Extensions will be recognized if they are + * registered in {@code extensionRegistry}. + */ + public void merge(final Readable input, + final ExtensionRegistry extensionRegistry, + final Message.Builder builder) + throws IOException { + // Read the entire input to a String then parse that. + + // If StreamTokenizer were not quite so crippled, or if there were a kind + // of Reader that could read in chunks that match some particular regex, + // or if we wanted to write a custom Reader to tokenize our stream, then + // we would not have to read to one big String. Alas, none of these is + // the case. Oh well. + + merge(toStringBuilder(input), extensionRegistry, builder); + } + + + private static final int BUFFER_SIZE = 4096; + + // TODO(chrisn): See if working around java.io.Reader#read(CharBuffer) + // overhead is worthwhile + private static StringBuilder toStringBuilder(final Readable input) + throws IOException { + final StringBuilder text = new StringBuilder(); + final CharBuffer buffer = CharBuffer.allocate(BUFFER_SIZE); + while (true) { + final int n = input.read(buffer); + if (n == -1) { + break; + } + buffer.flip(); + text.append(buffer, 0, n); + } + return text; + } + + /** + * Parse a text-format message from {@code input} and merge the contents + * into {@code builder}. Extensions will be recognized if they are + * registered in {@code extensionRegistry}. + */ + public void merge(final CharSequence input, + final ExtensionRegistry extensionRegistry, + final Message.Builder builder) + throws ParseException { + final Tokenizer tokenizer = new Tokenizer(input); + MessageReflection.BuilderAdapter target = + new MessageReflection.BuilderAdapter(builder); + + while (!tokenizer.atEnd()) { + mergeField(tokenizer, extensionRegistry, target); + } + } + + + /** + * Parse a single field from {@code tokenizer} and merge it into + * {@code builder}. + */ + private void mergeField(final Tokenizer tokenizer, + final ExtensionRegistry extensionRegistry, + final MessageReflection.MergeTarget target) + throws ParseException { + FieldDescriptor field = null; + final Descriptor type = target.getDescriptorForType(); + ExtensionRegistry.ExtensionInfo extension = null; + + if (tokenizer.tryConsume("[")) { + // An extension. + final StringBuilder name = + new StringBuilder(tokenizer.consumeIdentifier()); + while (tokenizer.tryConsume(".")) { + name.append('.'); + name.append(tokenizer.consumeIdentifier()); + } + + extension = target.findExtensionByName( + extensionRegistry, name.toString()); + + if (extension == null) { + if (!allowUnknownFields) { + throw tokenizer.parseExceptionPreviousToken( + "Extension \"" + name + "\" not found in the ExtensionRegistry."); + } else { + logger.warning( + "Extension \"" + name + "\" not found in the ExtensionRegistry."); + } + } else { + if (extension.descriptor.getContainingType() != type) { + throw tokenizer.parseExceptionPreviousToken( + "Extension \"" + name + "\" does not extend message type \"" + + type.getFullName() + "\"."); + } + field = extension.descriptor; + } + + tokenizer.consume("]"); + } else { + final String name = tokenizer.consumeIdentifier(); + field = type.findFieldByName(name); + + // Group names are expected to be capitalized as they appear in the + // .proto file, which actually matches their type names, not their field + // names. + if (field == null) { + // Explicitly specify US locale so that this code does not break when + // executing in Turkey. + final String lowerName = name.toLowerCase(Locale.US); + field = type.findFieldByName(lowerName); + // If the case-insensitive match worked but the field is NOT a group, + if (field != null && field.getType() != FieldDescriptor.Type.GROUP) { + field = null; + } + } + // Again, special-case group names as described above. + if (field != null && field.getType() == FieldDescriptor.Type.GROUP + && !field.getMessageType().getName().equals(name)) { + field = null; + } + + if (field == null) { + if (!allowUnknownFields) { + throw tokenizer.unknownFieldParseExceptionPreviousToken( + name, + "Message type \"" + type.getFullName() + + "\" has no field named \"" + name + "\"."); + } else { + logger.warning( + "Message type \"" + type.getFullName() + + "\" has no field named \"" + name + "\"."); + } + } + } + + // Skips unknown fields. + if (field == null) { + // Try to guess the type of this field. + // If this field is not a message, there should be a ":" between the + // field name and the field value and also the field value should not + // start with "{" or "<" which indicates the beginning of a message body. + // If there is no ":" or there is a "{" or "<" after ":", this field has + // to be a message or the input is ill-formed. + if (tokenizer.tryConsume(":") + && !tokenizer.lookingAt("{") + && !tokenizer.lookingAt("<")) { + skipFieldValue(tokenizer); + } else { + skipFieldMessage(tokenizer); + } + return; + } + + // Handle potential ':'. + if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + tokenizer.tryConsume(":"); // optional + } else { + tokenizer.consume(":"); // required + } + // Support specifying repeated field values as a comma-separated list. + // Ex."foo: [1, 2, 3]" + if (field.isRepeated() && tokenizer.tryConsume("[")) { + while (true) { + consumeFieldValue(tokenizer, extensionRegistry, target, field, extension); + if (tokenizer.tryConsume("]")) { + // End of list. + break; + } + tokenizer.consume(","); + } + } else { + consumeFieldValue(tokenizer, extensionRegistry, target, field, extension); + } + + // For historical reasons, fields may optionally be separated by commas or + // semicolons. + if (!tokenizer.tryConsume(";")) { + tokenizer.tryConsume(","); + } + } + + /** + * Parse a single field value from {@code tokenizer} and merge it into + * {@code builder}. + */ + private void consumeFieldValue( + final Tokenizer tokenizer, + final ExtensionRegistry extensionRegistry, + final MessageReflection.MergeTarget target, + final FieldDescriptor field, + final ExtensionRegistry.ExtensionInfo extension) + throws ParseException { + Object value = null; + + if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + final String endToken; + if (tokenizer.tryConsume("<")) { + endToken = ">"; + } else { + tokenizer.consume("{"); + endToken = "}"; + } + + final MessageReflection.MergeTarget subField; + subField = target.newMergeTargetForField(field, + (extension == null) ? null : extension.defaultInstance); + + while (!tokenizer.tryConsume(endToken)) { + if (tokenizer.atEnd()) { + throw tokenizer.parseException( + "Expected \"" + endToken + "\"."); + } + mergeField(tokenizer, extensionRegistry, subField); + } + + value = subField.finish(); + + } else { + switch (field.getType()) { + case INT32: + case SINT32: + case SFIXED32: + value = tokenizer.consumeInt32(); + break; + + case INT64: + case SINT64: + case SFIXED64: + value = tokenizer.consumeInt64(); + break; + + case UINT32: + case FIXED32: + value = tokenizer.consumeUInt32(); + break; + + case UINT64: + case FIXED64: + value = tokenizer.consumeUInt64(); + break; + + case FLOAT: + value = tokenizer.consumeFloat(); + break; + + case DOUBLE: + value = tokenizer.consumeDouble(); + break; + + case BOOL: + value = tokenizer.consumeBoolean(); + break; + + case STRING: + value = tokenizer.consumeString(); + break; + + case BYTES: + value = tokenizer.consumeByteString(); + break; + + case ENUM: + final EnumDescriptor enumType = field.getEnumType(); + + if (tokenizer.lookingAtInteger()) { + final int number = tokenizer.consumeInt32(); + value = enumType.findValueByNumber(number); + if (value == null) { + throw tokenizer.parseExceptionPreviousToken( + "Enum type \"" + enumType.getFullName() + + "\" has no value with number " + number + '.'); + } + } else { + final String id = tokenizer.consumeIdentifier(); + value = enumType.findValueByName(id); + if (value == null) { + throw tokenizer.parseExceptionPreviousToken( + "Enum type \"" + enumType.getFullName() + + "\" has no value named \"" + id + "\"."); + } + } + + break; + + case MESSAGE: + case GROUP: + throw new RuntimeException("Can't get here."); + } + } + + if (field.isRepeated()) { + target.addRepeatedField(field, value); + } else if ((singularOverwritePolicy + == SingularOverwritePolicy.FORBID_SINGULAR_OVERWRITES) + && target.hasField(field)) { + throw tokenizer.parseExceptionPreviousToken("Non-repeated field \"" + + field.getFullName() + "\" cannot be overwritten."); + } else if ((singularOverwritePolicy + == SingularOverwritePolicy.FORBID_SINGULAR_OVERWRITES) + && field.getContainingOneof() != null + && target.hasOneof(field.getContainingOneof())) { + Descriptors.OneofDescriptor oneof = field.getContainingOneof(); + throw tokenizer.parseExceptionPreviousToken("Field \"" + + field.getFullName() + "\" is specified along with field \"" + + target.getOneofFieldDescriptor(oneof).getFullName() + + "\", another member of oneof \"" + oneof.getName() + "\"."); + } else { + target.setField(field, value); + } + } + + /** + * Skips the next field including the field's name and value. + */ + private void skipField(Tokenizer tokenizer) throws ParseException { + if (tokenizer.tryConsume("[")) { + // Extension name. + do { + tokenizer.consumeIdentifier(); + } while (tokenizer.tryConsume(".")); + tokenizer.consume("]"); + } else { + tokenizer.consumeIdentifier(); + } + + // Try to guess the type of this field. + // If this field is not a message, there should be a ":" between the + // field name and the field value and also the field value should not + // start with "{" or "<" which indicates the beginning of a message body. + // If there is no ":" or there is a "{" or "<" after ":", this field has + // to be a message or the input is ill-formed. + if (tokenizer.tryConsume(":") + && !tokenizer.lookingAt("<") + && !tokenizer.lookingAt("{")) { + skipFieldValue(tokenizer); + } else { + skipFieldMessage(tokenizer); + } + // For historical reasons, fields may optionally be separated by commas or + // semicolons. + if (!tokenizer.tryConsume(";")) { + tokenizer.tryConsume(","); + } + } + + /** + * Skips the whole body of a message including the beginning delimiter and + * the ending delimiter. + */ + private void skipFieldMessage(Tokenizer tokenizer) throws ParseException { + final String delimiter; + if (tokenizer.tryConsume("<")) { + delimiter = ">"; + } else { + tokenizer.consume("{"); + delimiter = "}"; + } + while (!tokenizer.lookingAt(">") && !tokenizer.lookingAt("}")) { + skipField(tokenizer); + } + tokenizer.consume(delimiter); + } + + /** + * Skips a field value. + */ + private void skipFieldValue(Tokenizer tokenizer) throws ParseException { + if (tokenizer.tryConsumeString()) { + while (tokenizer.tryConsumeString()) {} + return; + } + if (!tokenizer.tryConsumeIdentifier() // includes enum & boolean + && !tokenizer.tryConsumeInt64() // includes int32 + && !tokenizer.tryConsumeUInt64() // includes uint32 + && !tokenizer.tryConsumeDouble() + && !tokenizer.tryConsumeFloat()) { + throw tokenizer.parseException( + "Invalid field value: " + tokenizer.currentToken); + } + } + } + + // ================================================================= + // Utility functions + // + // Some of these methods are package-private because Descriptors.java uses + // them. + + private interface ByteSequence { + int size(); + byte byteAt(int offset); + } + + /** + * Escapes bytes in the format used in protocol buffer text format, which + * is the same as the format used for C string literals. All bytes + * that are not printable 7-bit ASCII characters are escaped, as well as + * backslash, single-quote, and double-quote characters. Characters for + * which no defined short-hand escape sequence is defined will be escaped + * using 3-digit octal sequences. + */ + public static String escapeBytes(final ByteSequence input) { + final StringBuilder builder = new StringBuilder(input.size()); + for (int i = 0; i < input.size(); i++) { + final byte b = input.byteAt(i); + switch (b) { + // Java does not recognize \a or \v, apparently. + case 0x07: builder.append("\\a"); break; + case '\b': builder.append("\\b"); break; + case '\f': builder.append("\\f"); break; + case '\n': builder.append("\\n"); break; + case '\r': builder.append("\\r"); break; + case '\t': builder.append("\\t"); break; + case 0x0b: builder.append("\\v"); break; + case '\\': builder.append("\\\\"); break; + case '\'': builder.append("\\\'"); break; + case '"' : builder.append("\\\""); break; + default: + // 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('\\'); + builder.append((char) ('0' + ((b >>> 6) & 3))); + builder.append((char) ('0' + ((b >>> 3) & 7))); + builder.append((char) ('0' + (b & 7))); + } + break; + } + } + return builder.toString(); + } + + /** + * Escapes bytes in the format used in protocol buffer text format, which + * is the same as the format used for C string literals. All bytes + * that are not printable 7-bit ASCII characters are escaped, as well as + * backslash, single-quote, and double-quote characters. Characters for + * which no defined short-hand escape sequence is defined will be escaped + * using 3-digit octal sequences. + */ + public static String escapeBytes(final ByteString input) { + return escapeBytes(new ByteSequence() { + @Override + public int size() { + return input.size(); + } + @Override + public byte byteAt(int offset) { + return input.byteAt(offset); + } + }); + } + + /** + * Like {@link #escapeBytes(ByteString)}, but used for byte array. + */ + public static String escapeBytes(final byte[] input) { + return escapeBytes(new ByteSequence() { + @Override + public int size() { + return input.length; + } + @Override + public byte byteAt(int offset) { + return input[offset]; + } + }); + } + + /** + * Un-escape a byte sequence as escaped using + * {@link #escapeBytes(ByteString)}. Two-digit hex escapes (starting with + * "\x") are also recognized. + */ + public static ByteString unescapeBytes(final CharSequence charString) + throws InvalidEscapeSequenceException { + // First convert the Java character sequence to UTF-8 bytes. + ByteString input = ByteString.copyFromUtf8(charString.toString()); + // Then unescape certain byte sequences introduced by ASCII '\\'. The valid + // escapes can all be expressed with ASCII characters, so it is safe to + // operate on bytes here. + // + // Unescaping the input byte array will result in a byte sequence that's no + // longer than the input. That's because each escape sequence is between + // two and four bytes long and stands for a single byte. + final byte[] result = new byte[input.size()]; + int pos = 0; + for (int i = 0; i < input.size(); i++) { + byte c = input.byteAt(i); + if (c == '\\') { + if (i + 1 < input.size()) { + ++i; + c = input.byteAt(i); + if (isOctal(c)) { + // Octal escape. + int code = digitValue(c); + if (i + 1 < input.size() && isOctal(input.byteAt(i + 1))) { + ++i; + code = code * 8 + digitValue(input.byteAt(i)); + } + if (i + 1 < input.size() && isOctal(input.byteAt(i + 1))) { + ++i; + code = code * 8 + digitValue(input.byteAt(i)); + } + // TODO: Check that 0 <= code && code <= 0xFF. + result[pos++] = (byte) code; + } else { + switch (c) { + case 'a' : result[pos++] = 0x07; break; + case 'b' : result[pos++] = '\b'; break; + case 'f' : result[pos++] = '\f'; break; + case 'n' : result[pos++] = '\n'; break; + case 'r' : result[pos++] = '\r'; break; + case 't' : result[pos++] = '\t'; break; + case 'v' : result[pos++] = 0x0b; break; + case '\\': result[pos++] = '\\'; break; + case '\'': result[pos++] = '\''; break; + case '"' : result[pos++] = '\"'; break; + + case 'x': + // hex escape + int code = 0; + if (i + 1 < input.size() && isHex(input.byteAt(i + 1))) { + ++i; + code = digitValue(input.byteAt(i)); + } else { + throw new InvalidEscapeSequenceException( + "Invalid escape sequence: '\\x' with no digits"); + } + if (i + 1 < input.size() && isHex(input.byteAt(i + 1))) { + ++i; + code = code * 16 + digitValue(input.byteAt(i)); + } + result[pos++] = (byte) code; + break; + + default: + throw new InvalidEscapeSequenceException( + "Invalid escape sequence: '\\" + (char) c + '\''); + } + } + } else { + throw new InvalidEscapeSequenceException( + "Invalid escape sequence: '\\' at end of string."); + } + } else { + result[pos++] = c; + } + } + + return ByteString.copyFrom(result, 0, pos); + } + + /** + * Thrown by {@link TextFormat#unescapeBytes} and + * {@link TextFormat#unescapeText} when an invalid escape sequence is seen. + */ + public static class InvalidEscapeSequenceException extends IOException { + private static final long serialVersionUID = -8164033650142593304L; + + InvalidEscapeSequenceException(final String description) { + super(description); + } + } + + /** + * Like {@link #escapeBytes(ByteString)}, but escapes a text string. + * Non-ASCII characters are first encoded as UTF-8, then each byte is escaped + * individually as a 3-digit octal escape. Yes, it's weird. + */ + static String escapeText(final String input) { + return escapeBytes(ByteString.copyFromUtf8(input)); + } + + /** + * Escape double quotes and backslashes in a String for unicode output of a message. + */ + public static String escapeDoubleQuotesAndBackslashes(final String input) { + return input.replace("\\", "\\\\").replace("\"", "\\\""); + } + + /** + * Un-escape a text string as escaped using {@link #escapeText(String)}. + * Two-digit hex escapes (starting with "\x") are also recognized. + */ + static String unescapeText(final String input) + throws InvalidEscapeSequenceException { + return unescapeBytes(input).toStringUtf8(); + } + + /** Is this an octal digit? */ + private static boolean isOctal(final byte c) { + return '0' <= c && c <= '7'; + } + + /** Is this a hex digit? */ + private static boolean isHex(final byte c) { + return ('0' <= c && c <= '9') + || ('a' <= c && c <= 'f') + || ('A' <= c && c <= 'F'); + } + + /** + * Interpret a character as a digit (in any base up to 36) and return the + * numeric value. This is like {@code Character.digit()} but we don't accept + * non-ASCII digits. + */ + private static int digitValue(final byte c) { + if ('0' <= c && c <= '9') { + return c - '0'; + } else if ('a' <= c && c <= 'z') { + return c - 'a' + 10; + } else { + return c - 'A' + 10; + } + } + + /** + * Parse a 32-bit signed integer from the text. Unlike the Java standard + * {@code Integer.parseInt()}, this function recognizes the prefixes "0x" + * and "0" to signify hexadecimal and octal numbers, respectively. + */ + static int parseInt32(final String text) throws NumberFormatException { + return (int) parseInteger(text, true, false); + } + + /** + * Parse a 32-bit unsigned integer from the text. Unlike the Java standard + * {@code Integer.parseInt()}, this function recognizes the prefixes "0x" + * and "0" to signify hexadecimal and octal numbers, respectively. The + * result is coerced to a (signed) {@code int} when returned since Java has + * no unsigned integer type. + */ + static int parseUInt32(final String text) throws NumberFormatException { + return (int) parseInteger(text, false, false); + } + + /** + * Parse a 64-bit signed integer from the text. Unlike the Java standard + * {@code Integer.parseInt()}, this function recognizes the prefixes "0x" + * and "0" to signify hexadecimal and octal numbers, respectively. + */ + static long parseInt64(final String text) throws NumberFormatException { + return parseInteger(text, true, true); + } + + /** + * Parse a 64-bit unsigned integer from the text. Unlike the Java standard + * {@code Integer.parseInt()}, this function recognizes the prefixes "0x" + * and "0" to signify hexadecimal and octal numbers, respectively. The + * result is coerced to a (signed) {@code long} when returned since Java has + * no unsigned long type. + */ + static long parseUInt64(final String text) throws NumberFormatException { + return parseInteger(text, false, true); + } + + private static long parseInteger(final String text, + final boolean isSigned, + final boolean isLong) + throws NumberFormatException { + int pos = 0; + + boolean negative = false; + if (text.startsWith("-", pos)) { + if (!isSigned) { + throw new NumberFormatException("Number must be positive: " + text); + } + ++pos; + negative = true; + } + + int radix = 10; + if (text.startsWith("0x", pos)) { + pos += 2; + radix = 16; + } else if (text.startsWith("0", pos)) { + radix = 8; + } + + final String numberText = text.substring(pos); + + long result = 0; + if (numberText.length() < 16) { + // Can safely assume no overflow. + result = Long.parseLong(numberText, radix); + if (negative) { + result = -result; + } + + // Check bounds. + // No need to check for 64-bit numbers since they'd have to be 16 chars + // or longer to overflow. + if (!isLong) { + if (isSigned) { + if (result > Integer.MAX_VALUE || result < Integer.MIN_VALUE) { + throw new NumberFormatException( + "Number out of range for 32-bit signed integer: " + text); + } + } else { + if (result >= (1L << 32) || result < 0) { + throw new NumberFormatException( + "Number out of range for 32-bit unsigned integer: " + text); + } + } + } + } else { + BigInteger bigValue = new BigInteger(numberText, radix); + if (negative) { + bigValue = bigValue.negate(); + } + + // Check bounds. + if (!isLong) { + if (isSigned) { + if (bigValue.bitLength() > 31) { + throw new NumberFormatException( + "Number out of range for 32-bit signed integer: " + text); + } + } else { + if (bigValue.bitLength() > 32) { + throw new NumberFormatException( + "Number out of range for 32-bit unsigned integer: " + text); + } + } + } else { + if (isSigned) { + if (bigValue.bitLength() > 63) { + throw new NumberFormatException( + "Number out of range for 64-bit signed integer: " + text); + } + } else { + if (bigValue.bitLength() > 64) { + throw new NumberFormatException( + "Number out of range for 64-bit unsigned integer: " + text); + } + } + } + + result = bigValue.longValue(); + } + + return result; + } +} diff --git a/java/core/src/main/java/com/google/protobuf/TextFormatEscaper.java b/java/core/src/main/java/com/google/protobuf/TextFormatEscaper.java new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/TextFormatEscaper.java diff --git a/java/core/src/main/java/com/google/protobuf/UninitializedMessageException.java b/java/core/src/main/java/com/google/protobuf/UninitializedMessageException.java new file mode 100644 index 00000000..5714c063 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/UninitializedMessageException.java @@ -0,0 +1,99 @@ +// 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.Collections; +import java.util.List; + +/** + * Thrown when attempting to build a protocol message that is missing required + * fields. This is a {@code RuntimeException} because it normally represents + * a programming error: it happens when some code which constructs a message + * fails to set all the fields. {@code parseFrom()} methods <b>do not</b> + * throw this; they throw an {@link InvalidProtocolBufferException} if + * required fields are missing, because it is not a programming error to + * receive an incomplete message. In other words, + * {@code UninitializedMessageException} should never be thrown by correct + * code, but {@code InvalidProtocolBufferException} might be. + * + * @author kenton@google.com Kenton Varda + */ +public class UninitializedMessageException extends RuntimeException { + private static final long serialVersionUID = -7466929953374883507L; + + public UninitializedMessageException(final MessageLite message) { + super("Message was missing required fields. (Lite runtime could not " + + "determine which fields were missing)."); + missingFields = null; + } + + public UninitializedMessageException(final List<String> missingFields) { + super(buildDescription(missingFields)); + this.missingFields = missingFields; + } + + private final List<String> missingFields; + + /** + * Get a list of human-readable names of required fields missing from this + * message. Each name is a full path to a field, e.g. "foo.bar[5].baz". + * Returns null if the lite runtime was used, since it lacks the ability to + * find missing fields. + */ + public List<String> getMissingFields() { + return Collections.unmodifiableList(missingFields); + } + + /** + * Converts this exception to an {@link InvalidProtocolBufferException}. + * When a parsed message is missing required fields, this should be thrown + * instead of {@code UninitializedMessageException}. + */ + public InvalidProtocolBufferException asInvalidProtocolBufferException() { + return new InvalidProtocolBufferException(getMessage()); + } + + /** Construct the description string for this exception. */ + private static String buildDescription(final List<String> missingFields) { + final StringBuilder description = + new StringBuilder("Message missing required fields: "); + boolean first = true; + for (final String field : missingFields) { + if (first) { + first = false; + } else { + description.append(", "); + } + description.append(field); + } + return description.toString(); + } +} diff --git a/java/core/src/main/java/com/google/protobuf/UnknownFieldSet.java b/java/core/src/main/java/com/google/protobuf/UnknownFieldSet.java new file mode 100644 index 00000000..7cd2250e --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/UnknownFieldSet.java @@ -0,0 +1,1010 @@ +// 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.AbstractMessageLite.Builder.LimitedInputStream; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +/** + * {@code UnknownFieldSet} 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. + * + * <p>Every {@link Message} contains an {@code UnknownFieldSet} (and every + * {@link Message.Builder} contains an {@link Builder}). + * + * <p>Most users will never need to use this class. + * + * @author kenton@google.com Kenton Varda + */ +public final class UnknownFieldSet implements MessageLite { + private UnknownFieldSet() {} + + /** Create a new {@link Builder}. */ + public static Builder newBuilder() { + return Builder.create(); + } + + /** + * Create a new {@link Builder} and initialize it to be a copy + * of {@code copyFrom}. + */ + public static Builder newBuilder(final UnknownFieldSet copyFrom) { + return newBuilder().mergeFrom(copyFrom); + } + + /** Get an empty {@code UnknownFieldSet}. */ + public static UnknownFieldSet getDefaultInstance() { + return defaultInstance; + } + public UnknownFieldSet getDefaultInstanceForType() { + return defaultInstance; + } + private static final UnknownFieldSet defaultInstance = + new UnknownFieldSet(Collections.<Integer, Field>emptyMap()); + + /** + * Construct an {@code UnknownFieldSet} around the given map. The map is + * expected to be immutable. + */ + private UnknownFieldSet(final Map<Integer, Field> fields) { + this.fields = fields; + } + private Map<Integer, Field> fields; + + + @Override + public boolean equals(final Object other) { + if (this == other) { + return true; + } + return (other instanceof UnknownFieldSet) && + fields.equals(((UnknownFieldSet) other).fields); + } + + @Override + public int hashCode() { + return fields.hashCode(); + } + + /** Get a map of fields in the set by number. */ + public Map<Integer, Field> asMap() { + return fields; + } + + /** Check if the given field number is present in the set. */ + public boolean hasField(final int number) { + return fields.containsKey(number); + } + + /** + * Get a field by number. Returns an empty field if not present. Never + * returns {@code null}. + */ + public Field getField(final int number) { + final Field result = fields.get(number); + return (result == null) ? Field.getDefaultInstance() : result; + } + + /** Serializes the set and writes it to {@code output}. */ + public void writeTo(final CodedOutputStream output) throws IOException { + for (final Map.Entry<Integer, Field> entry : fields.entrySet()) { + entry.getValue().writeTo(entry.getKey(), output); + } + } + + /** + * Converts the set to a string in protocol buffer text format. This is + * just a trivial wrapper around + * {@link TextFormat#printToString(UnknownFieldSet)}. + */ + @Override + public String toString() { + return TextFormat.printToString(this); + } + + /** + * Serializes the message to a {@code ByteString} and returns it. This is + * just a trivial wrapper around {@link #writeTo(CodedOutputStream)}. + */ + public ByteString toByteString() { + try { + final ByteString.CodedBuilder out = + ByteString.newCodedBuilder(getSerializedSize()); + writeTo(out.getCodedOutput()); + return out.build(); + } catch (final IOException e) { + throw new RuntimeException( + "Serializing to a ByteString threw an IOException (should " + + "never happen).", e); + } + } + + /** + * Serializes the message to a {@code byte} array and returns it. This is + * just a trivial wrapper around {@link #writeTo(CodedOutputStream)}. + */ + public byte[] toByteArray() { + try { + final byte[] result = new byte[getSerializedSize()]; + final CodedOutputStream output = CodedOutputStream.newInstance(result); + writeTo(output); + output.checkNoSpaceLeft(); + return result; + } catch (final IOException e) { + throw new RuntimeException( + "Serializing to a byte array threw an IOException " + + "(should never happen).", e); + } + } + + /** + * Serializes the message and writes it to {@code output}. This is just a + * trivial wrapper around {@link #writeTo(CodedOutputStream)}. + */ + public void writeTo(final OutputStream output) throws IOException { + final CodedOutputStream codedOutput = CodedOutputStream.newInstance(output); + writeTo(codedOutput); + codedOutput.flush(); + } + + public void writeDelimitedTo(OutputStream output) throws IOException { + final CodedOutputStream codedOutput = CodedOutputStream.newInstance(output); + codedOutput.writeRawVarint32(getSerializedSize()); + writeTo(codedOutput); + codedOutput.flush(); + } + + /** Get the number of bytes required to encode this set. */ + public int getSerializedSize() { + int result = 0; + for (final Map.Entry<Integer, Field> entry : fields.entrySet()) { + result += entry.getValue().getSerializedSize(entry.getKey()); + } + return result; + } + + /** + * Serializes the set and writes it to {@code output} using + * {@code MessageSet} wire format. + */ + public void writeAsMessageSetTo(final CodedOutputStream output) + throws IOException { + for (final Map.Entry<Integer, Field> entry : fields.entrySet()) { + entry.getValue().writeAsMessageSetExtensionTo( + entry.getKey(), output); + } + } + + /** + * Get the number of bytes required to encode this set using + * {@code MessageSet} wire format. + */ + public int getSerializedSizeAsMessageSet() { + int result = 0; + for (final Map.Entry<Integer, Field> entry : fields.entrySet()) { + result += entry.getValue().getSerializedSizeAsMessageSetExtension( + entry.getKey()); + } + return result; + } + + public boolean isInitialized() { + // UnknownFieldSets do not have required fields, so they are always + // initialized. + return true; + } + + /** Parse an {@code UnknownFieldSet} from the given input stream. */ + public static UnknownFieldSet parseFrom(final CodedInputStream input) + throws IOException { + return newBuilder().mergeFrom(input).build(); + } + + /** Parse {@code data} as an {@code UnknownFieldSet} and return it. */ + public static UnknownFieldSet parseFrom(final ByteString data) + throws InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).build(); + } + + /** Parse {@code data} as an {@code UnknownFieldSet} and return it. */ + public static UnknownFieldSet parseFrom(final byte[] data) + throws InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).build(); + } + + /** Parse an {@code UnknownFieldSet} from {@code input} and return it. */ + public static UnknownFieldSet parseFrom(final InputStream input) + throws IOException { + return newBuilder().mergeFrom(input).build(); + } + + public Builder newBuilderForType() { + return newBuilder(); + } + + public Builder toBuilder() { + return newBuilder().mergeFrom(this); + } + + /** + * Builder for {@link UnknownFieldSet}s. + * + * <p>Note that this class maintains {@link Field.Builder}s for all fields + * in the set. Thus, adding one element to an existing {@link Field} does not + * require making a copy. This is important for efficient parsing of + * unknown repeated fields. However, it implies that {@link Field}s cannot + * be constructed independently, nor can two {@link UnknownFieldSet}s share + * the same {@code Field} object. + * + * <p>Use {@link UnknownFieldSet#newBuilder()} to construct a {@code Builder}. + */ + public static final class Builder implements MessageLite.Builder { + // This constructor should never be called directly (except from 'create'). + private Builder() {} + + private Map<Integer, Field> fields; + + // Optimization: We keep around a builder for the last field that was + // modified so that we can efficiently add to it multiple times in a + // row (important when parsing an unknown repeated field). + private int lastFieldNumber; + private Field.Builder lastField; + + private static Builder create() { + Builder builder = new Builder(); + builder.reinitialize(); + return builder; + } + + /** + * Get a field builder for the given field number which includes any + * values that already exist. + */ + private Field.Builder getFieldBuilder(final int number) { + if (lastField != null) { + if (number == lastFieldNumber) { + return lastField; + } + // Note: addField() will reset lastField and lastFieldNumber. + addField(lastFieldNumber, lastField.build()); + } + if (number == 0) { + return null; + } else { + final Field existing = fields.get(number); + lastFieldNumber = number; + lastField = Field.newBuilder(); + if (existing != null) { + lastField.mergeFrom(existing); + } + return lastField; + } + } + + /** + * Build the {@link UnknownFieldSet} and return it. + * + * <p>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 NullPointerException} to be + * thrown. + */ + public UnknownFieldSet build() { + getFieldBuilder(0); // Force lastField to be built. + final UnknownFieldSet result; + if (fields.isEmpty()) { + result = getDefaultInstance(); + } else { + result = new UnknownFieldSet(Collections.unmodifiableMap(fields)); + } + fields = null; + return result; + } + + public UnknownFieldSet buildPartial() { + // No required fields, so this is the same as build(). + return build(); + } + + @Override + public Builder clone() { + getFieldBuilder(0); // Force lastField to be built. + return UnknownFieldSet.newBuilder().mergeFrom( + new UnknownFieldSet(fields)); + } + + public UnknownFieldSet getDefaultInstanceForType() { + return UnknownFieldSet.getDefaultInstance(); + } + + private void reinitialize() { + fields = Collections.emptyMap(); + lastFieldNumber = 0; + lastField = null; + } + + /** Reset the builder to an empty set. */ + public Builder clear() { + reinitialize(); + return this; + } + + /** Clear fields from the set with a given field number. */ + public Builder clearField(final int number) { + if (number == 0) { + throw new IllegalArgumentException("Zero is not a valid field number."); + } + if (lastField != null && lastFieldNumber == number) { + // Discard this. + lastField = null; + lastFieldNumber = 0; + } + if (fields.containsKey(number)) { + fields.remove(number); + } + return this; + } + + /** + * Merge the fields from {@code other} into this set. If a field number + * exists in both sets, {@code other}'s values for that field will be + * appended to the values in this set. + */ + public Builder mergeFrom(final UnknownFieldSet other) { + if (other != getDefaultInstance()) { + for (final Map.Entry<Integer, Field> entry : other.fields.entrySet()) { + mergeField(entry.getKey(), entry.getValue()); + } + } + return this; + } + + /** + * Add a field to the {@code UnknownFieldSet}. If a field with the same + * number already exists, the two are merged. + */ + public Builder mergeField(final int number, final Field field) { + if (number == 0) { + throw new IllegalArgumentException("Zero is not a valid field number."); + } + if (hasField(number)) { + getFieldBuilder(number).mergeFrom(field); + } else { + // Optimization: We could call getFieldBuilder(number).mergeFrom(field) + // in this case, but that would create a copy of the Field object. + // We'd rather reuse the one passed to us, so call addField() instead. + addField(number, field); + } + return this; + } + + /** + * Convenience method for merging a new field containing a single varint + * value. This is used in particular when an unknown enum value is + * encountered. + */ + public Builder mergeVarintField(final int number, final int value) { + if (number == 0) { + throw new IllegalArgumentException("Zero is not a valid field number."); + } + getFieldBuilder(number).addVarint(value); + return this; + } + + + /** + * Convenience method for merging a length-delimited field. + * + * <p>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) { + throw new IllegalArgumentException("Zero is not a valid field number."); + } + return number == lastFieldNumber || fields.containsKey(number); + } + + /** + * Add a field to the {@code UnknownFieldSet}. If a field with the same + * number already exists, it is removed. + */ + public Builder addField(final int number, final Field field) { + if (number == 0) { + throw new IllegalArgumentException("Zero is not a valid field number."); + } + if (lastField != null && lastFieldNumber == number) { + // Discard this. + lastField = null; + lastFieldNumber = 0; + } + if (fields.isEmpty()) { + fields = new TreeMap<Integer,Field>(); + } + fields.put(number, field); + return this; + } + + /** + * Get all present {@code Field}s as an immutable {@code Map}. If more + * fields are added, the changes may or may not be reflected in this map. + */ + public Map<Integer, Field> asMap() { + getFieldBuilder(0); // Force lastField to be built. + return Collections.unmodifiableMap(fields); + } + + /** + * Parse an entire message from {@code input} and merge its fields into + * this set. + */ + public Builder mergeFrom(final CodedInputStream input) throws IOException { + while (true) { + final int tag = input.readTag(); + if (tag == 0 || !mergeFieldFrom(tag, input)) { + break; + } + } + return this; + } + + /** + * Parse a single field from {@code input} and merge it into this set. + * @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 { + final int number = WireFormat.getTagFieldNumber(tag); + switch (WireFormat.getTagWireType(tag)) { + case WireFormat.WIRETYPE_VARINT: + getFieldBuilder(number).addVarint(input.readInt64()); + return true; + case WireFormat.WIRETYPE_FIXED64: + getFieldBuilder(number).addFixed64(input.readFixed64()); + return true; + case WireFormat.WIRETYPE_LENGTH_DELIMITED: + getFieldBuilder(number).addLengthDelimited(input.readBytes()); + return true; + case WireFormat.WIRETYPE_START_GROUP: + final Builder subBuilder = newBuilder(); + input.readGroup(number, subBuilder, + ExtensionRegistry.getEmptyRegistry()); + getFieldBuilder(number).addGroup(subBuilder.build()); + return true; + case WireFormat.WIRETYPE_END_GROUP: + return false; + case WireFormat.WIRETYPE_FIXED32: + getFieldBuilder(number).addFixed32(input.readFixed32()); + return true; + default: + throw InvalidProtocolBufferException.invalidWireType(); + } + } + + /** + * Parse {@code data} as an {@code UnknownFieldSet} and merge it with the + * set being built. This is just a small wrapper around + * {@link #mergeFrom(CodedInputStream)}. + */ + public Builder mergeFrom(final ByteString data) + throws InvalidProtocolBufferException { + try { + final CodedInputStream input = data.newCodedInput(); + mergeFrom(input); + input.checkLastTagWas(0); + return this; + } catch (final InvalidProtocolBufferException e) { + throw e; + } catch (final IOException e) { + throw new RuntimeException( + "Reading from a ByteString threw an IOException (should " + + "never happen).", e); + } + } + + /** + * Parse {@code data} as an {@code UnknownFieldSet} and merge it with the + * set being built. This is just a small wrapper around + * {@link #mergeFrom(CodedInputStream)}. + */ + public Builder mergeFrom(final byte[] data) + throws InvalidProtocolBufferException { + try { + final CodedInputStream input = CodedInputStream.newInstance(data); + mergeFrom(input); + input.checkLastTagWas(0); + return this; + } catch (final InvalidProtocolBufferException e) { + throw e; + } catch (final IOException e) { + throw new RuntimeException( + "Reading from a byte array threw an IOException (should " + + "never happen).", e); + } + } + + /** + * Parse an {@code UnknownFieldSet} from {@code input} and merge it with the + * set being built. This is just a small wrapper around + * {@link #mergeFrom(CodedInputStream)}. + */ + public Builder mergeFrom(final InputStream input) throws IOException { + final CodedInputStream codedInput = CodedInputStream.newInstance(input); + mergeFrom(codedInput); + codedInput.checkLastTagWas(0); + return this; + } + + public boolean mergeDelimitedFrom(InputStream input) + throws IOException { + final int firstByte = input.read(); + if (firstByte == -1) { + return false; + } + final int size = CodedInputStream.readRawVarint32(firstByte, input); + final InputStream limitedInput = new LimitedInputStream(input, size); + mergeFrom(limitedInput); + return true; + } + + public boolean mergeDelimitedFrom( + InputStream input, + ExtensionRegistryLite extensionRegistry) throws IOException { + // UnknownFieldSet has no extensions. + return mergeDelimitedFrom(input); + } + + public Builder mergeFrom( + CodedInputStream input, + ExtensionRegistryLite extensionRegistry) throws IOException { + // UnknownFieldSet has no extensions. + return mergeFrom(input); + } + + public Builder mergeFrom( + ByteString data, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + // UnknownFieldSet has no extensions. + return mergeFrom(data); + } + + public Builder mergeFrom(byte[] data, int off, int len) + throws InvalidProtocolBufferException { + try { + final CodedInputStream input = + CodedInputStream.newInstance(data, off, len); + mergeFrom(input); + input.checkLastTagWas(0); + return this; + } catch (InvalidProtocolBufferException e) { + throw e; + } catch (IOException e) { + throw new RuntimeException( + "Reading from a byte array threw an IOException (should " + + "never happen).", e); + } + } + + public Builder mergeFrom( + byte[] data, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + // UnknownFieldSet has no extensions. + return mergeFrom(data); + } + + public Builder mergeFrom( + byte[] data, int off, int len, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + // UnknownFieldSet has no extensions. + return mergeFrom(data, off, len); + } + + public Builder mergeFrom( + InputStream input, + ExtensionRegistryLite extensionRegistry) throws IOException { + // UnknownFieldSet has no extensions. + return mergeFrom(input); + } + + public boolean isInitialized() { + // UnknownFieldSets do not have required fields, so they are always + // initialized. + return true; + } + } + + /** + * Represents a single field in an {@code UnknownFieldSet}. + * + * <p>A {@code Field} consists of five lists of values. The lists correspond + * to the five "wire types" used in the protocol buffer binary format. + * The wire type of each field can be determined from the encoded form alone, + * without knowing the field's declared type. So, we are able to parse + * unknown values at least this far and separate them. Normally, only one + * of the five lists will contain any values, since it is impossible to + * define a valid message type that declares two different types for the + * same field number. However, the code is designed to allow for the case + * where the same unknown field number is encountered using multiple different + * wire types. + * + * <p>{@code Field} is an immutable class. To construct one, you must use a + * {@link Builder}. + * + * @see UnknownFieldSet + */ + public static final class Field { + private Field() {} + + /** Construct a new {@link Builder}. */ + public static Builder newBuilder() { + return Builder.create(); + } + + /** + * Construct a new {@link Builder} and initialize it to a copy of + * {@code copyFrom}. + */ + public static Builder newBuilder(final Field copyFrom) { + return newBuilder().mergeFrom(copyFrom); + } + + /** Get an empty {@code Field}. */ + public static Field getDefaultInstance() { + return fieldDefaultInstance; + } + private static final Field fieldDefaultInstance = newBuilder().build(); + + /** Get the list of varint values for this field. */ + public List<Long> getVarintList() { return varint; } + + /** Get the list of fixed32 values for this field. */ + public List<Integer> getFixed32List() { return fixed32; } + + /** Get the list of fixed64 values for this field. */ + public List<Long> getFixed64List() { return fixed64; } + + /** Get the list of length-delimited values for this field. */ + public List<ByteString> getLengthDelimitedList() { return lengthDelimited; } + + /** + * Get the list of embedded group values for this field. These are + * represented using {@link UnknownFieldSet}s rather than {@link Message}s + * since the group's type is presumably unknown. + */ + public List<UnknownFieldSet> getGroupList() { return group; } + + @Override + public boolean equals(final Object other) { + if (this == other) { + return true; + } + if (!(other instanceof Field)) { + return false; + } + return Arrays.equals(getIdentityArray(), + ((Field) other).getIdentityArray()); + } + + @Override + public int hashCode() { + return Arrays.hashCode(getIdentityArray()); + } + + /** + * Returns the array of objects to be used to uniquely identify this + * {@link Field} instance. + */ + private Object[] getIdentityArray() { + return new Object[] { + varint, + fixed32, + fixed64, + lengthDelimited, + group}; + } + + /** + * Serializes the field, including field number, and writes it to + * {@code output}. + */ + public void writeTo(final int fieldNumber, final CodedOutputStream output) + throws IOException { + for (final long value : varint) { + output.writeUInt64(fieldNumber, value); + } + for (final int value : fixed32) { + output.writeFixed32(fieldNumber, value); + } + for (final long value : fixed64) { + output.writeFixed64(fieldNumber, value); + } + for (final ByteString value : lengthDelimited) { + output.writeBytes(fieldNumber, value); + } + for (final UnknownFieldSet value : group) { + output.writeGroup(fieldNumber, value); + } + } + + /** + * Get the number of bytes required to encode this field, including field + * number. + */ + public int getSerializedSize(final int fieldNumber) { + int result = 0; + for (final long value : varint) { + result += CodedOutputStream.computeUInt64Size(fieldNumber, value); + } + for (final int value : fixed32) { + result += CodedOutputStream.computeFixed32Size(fieldNumber, value); + } + for (final long value : fixed64) { + result += CodedOutputStream.computeFixed64Size(fieldNumber, value); + } + for (final ByteString value : lengthDelimited) { + result += CodedOutputStream.computeBytesSize(fieldNumber, value); + } + for (final UnknownFieldSet value : group) { + result += CodedOutputStream.computeGroupSize(fieldNumber, value); + } + return result; + } + + /** + * Serializes the field, including field number, and writes it to + * {@code output}, using {@code MessageSet} wire format. + */ + public void writeAsMessageSetExtensionTo( + final int fieldNumber, + final CodedOutputStream output) + throws IOException { + for (final ByteString value : lengthDelimited) { + output.writeRawMessageSetExtension(fieldNumber, value); + } + } + + /** + * Get the number of bytes required to encode this field, including field + * number, using {@code MessageSet} wire format. + */ + public int getSerializedSizeAsMessageSetExtension(final int fieldNumber) { + int result = 0; + for (final ByteString value : lengthDelimited) { + result += CodedOutputStream.computeRawMessageSetExtensionSize( + fieldNumber, value); + } + return result; + } + + private List<Long> varint; + private List<Integer> fixed32; + private List<Long> fixed64; + private List<ByteString> lengthDelimited; + private List<UnknownFieldSet> group; + + /** + * Used to build a {@link Field} within an {@link UnknownFieldSet}. + * + * <p>Use {@link Field#newBuilder()} to construct a {@code Builder}. + */ + public static final class Builder { + // This constructor should never be called directly (except from 'create'). + private Builder() {} + + private static Builder create() { + Builder builder = new Builder(); + builder.result = new Field(); + return builder; + } + + private Field result; + + /** + * Build the field. After {@code build()} has been called, the + * {@code Builder} is no longer usable. Calling any other method will + * result in undefined behavior and can cause a + * {@code NullPointerException} to be thrown. + */ + public Field build() { + if (result.varint == null) { + result.varint = Collections.emptyList(); + } else { + result.varint = Collections.unmodifiableList(result.varint); + } + if (result.fixed32 == null) { + result.fixed32 = Collections.emptyList(); + } else { + result.fixed32 = Collections.unmodifiableList(result.fixed32); + } + if (result.fixed64 == null) { + result.fixed64 = Collections.emptyList(); + } else { + result.fixed64 = Collections.unmodifiableList(result.fixed64); + } + if (result.lengthDelimited == null) { + result.lengthDelimited = Collections.emptyList(); + } else { + result.lengthDelimited = + Collections.unmodifiableList(result.lengthDelimited); + } + if (result.group == null) { + result.group = Collections.emptyList(); + } else { + result.group = Collections.unmodifiableList(result.group); + } + + final Field returnMe = result; + result = null; + return returnMe; + } + + /** Discard the field's contents. */ + public Builder clear() { + result = new Field(); + return this; + } + + /** + * Merge the values in {@code other} into this field. For each list + * of values, {@code other}'s values are append to the ones in this + * field. + */ + public Builder mergeFrom(final Field other) { + if (!other.varint.isEmpty()) { + if (result.varint == null) { + result.varint = new ArrayList<Long>(); + } + result.varint.addAll(other.varint); + } + if (!other.fixed32.isEmpty()) { + if (result.fixed32 == null) { + result.fixed32 = new ArrayList<Integer>(); + } + result.fixed32.addAll(other.fixed32); + } + if (!other.fixed64.isEmpty()) { + if (result.fixed64 == null) { + result.fixed64 = new ArrayList<Long>(); + } + result.fixed64.addAll(other.fixed64); + } + if (!other.lengthDelimited.isEmpty()) { + if (result.lengthDelimited == null) { + result.lengthDelimited = new ArrayList<ByteString>(); + } + result.lengthDelimited.addAll(other.lengthDelimited); + } + if (!other.group.isEmpty()) { + if (result.group == null) { + result.group = new ArrayList<UnknownFieldSet>(); + } + result.group.addAll(other.group); + } + return this; + } + + /** Add a varint value. */ + public Builder addVarint(final long value) { + if (result.varint == null) { + result.varint = new ArrayList<Long>(); + } + result.varint.add(value); + return this; + } + + /** Add a fixed32 value. */ + public Builder addFixed32(final int value) { + if (result.fixed32 == null) { + result.fixed32 = new ArrayList<Integer>(); + } + result.fixed32.add(value); + return this; + } + + /** Add a fixed64 value. */ + public Builder addFixed64(final long value) { + if (result.fixed64 == null) { + result.fixed64 = new ArrayList<Long>(); + } + result.fixed64.add(value); + return this; + } + + /** Add a length-delimited value. */ + public Builder addLengthDelimited(final ByteString value) { + if (result.lengthDelimited == null) { + result.lengthDelimited = new ArrayList<ByteString>(); + } + result.lengthDelimited.add(value); + return this; + } + + /** Add an embedded group. */ + public Builder addGroup(final UnknownFieldSet value) { + if (result.group == null) { + result.group = new ArrayList<UnknownFieldSet>(); + } + result.group.add(value); + return this; + } + } + } + + /** + * Parser to implement MessageLite interface. + */ + public static final class Parser extends AbstractParser<UnknownFieldSet> { + public UnknownFieldSet parsePartialFrom( + CodedInputStream input, ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input); + } catch (InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (IOException e) { + throw new InvalidProtocolBufferException(e.getMessage()) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + } + + private static final Parser PARSER = new Parser(); + public final Parser getParserForType() { + return PARSER; + } +} diff --git a/java/core/src/main/java/com/google/protobuf/UnknownFieldSetLite.java b/java/core/src/main/java/com/google/protobuf/UnknownFieldSetLite.java new file mode 100644 index 00000000..435ad4d4 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/UnknownFieldSetLite.java @@ -0,0 +1,458 @@ +// 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; +import java.util.Arrays; + +/** + * {@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. + * + * <p>For use by generated code only. + * + * @author dweis@google.com (Daniel Weis) + */ +public final class UnknownFieldSetLite { + + // Arbitrarily chosen. + // TODO(dweis): Tune this number? + private static final int MIN_CAPACITY = 8; + + private static final UnknownFieldSetLite DEFAULT_INSTANCE = + new UnknownFieldSetLite(0, new int[0], new Object[0], false /* isMutable */); + + /** + * Get an empty {@code UnknownFieldSetLite}. + * + * <p>For use by generated code only. + */ + public static UnknownFieldSetLite getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + /** + * Returns an empty {@code UnknownFieldSetLite.Builder}. + * + * <p>For use by generated code only. + */ + public static Builder newBuilder() { + return new Builder(); + } + + /** + * Returns a new mutable instance. + */ + static UnknownFieldSetLite newInstance() { + return new UnknownFieldSetLite(); + } + + /** + * Returns a mutable {@code UnknownFieldSetLite} that is the composite of {@code first} and + * {@code second}. + */ + static UnknownFieldSetLite mutableCopyOf(UnknownFieldSetLite first, UnknownFieldSetLite second) { + int count = first.count + second.count; + int[] tags = Arrays.copyOf(first.tags, count); + System.arraycopy(second.tags, 0, tags, first.count, second.count); + Object[] objects = Arrays.copyOf(first.objects, count); + System.arraycopy(second.objects, 0, objects, first.count, second.count); + return new UnknownFieldSetLite(count, tags, objects, true /* isMutable */); + } + + /** + * The number of elements in the set. + */ + private int count; + + /** + * The tag numbers for the elements in the set. + */ + private int[] tags; + + /** + * The boxed values of the elements in the set. + */ + private Object[] objects; + + /** + * The lazily computed serialized size of the set. + */ + private int memoizedSerializedSize = -1; + + /** + * Indicates that this object is mutable. + */ + private boolean isMutable; + + /** + * Constructs a mutable {@code UnknownFieldSetLite}. + */ + private UnknownFieldSetLite() { + this(0, new int[MIN_CAPACITY], new Object[MIN_CAPACITY], true /* isMutable */); + } + + /** + * Constructs the {@code UnknownFieldSetLite}. + */ + private UnknownFieldSetLite(int count, int[] tags, Object[] objects, boolean isMutable) { + this.count = count; + this.tags = tags; + this.objects = objects; + this.isMutable = isMutable; + } + + /** + * Marks this object as immutable. + * + * <p>Future calls to methods that attempt to modify this object will throw. + */ + public void makeImmutable() { + this.isMutable = false; + } + + /** + * Throws an {@link UnsupportedOperationException} if immutable. + */ + void checkMutable() { + if (!isMutable) { + throw new UnsupportedOperationException(); + } + } + + /** + * Serializes the set and writes it to {@code output}. + * + * <p>For use by generated code only. + */ + public void writeTo(CodedOutputStream output) throws IOException { + for (int i = 0; i < count; i++) { + int tag = tags[i]; + int fieldNumber = WireFormat.getTagFieldNumber(tag); + switch (WireFormat.getTagWireType(tag)) { + case WireFormat.WIRETYPE_VARINT: + output.writeUInt64(fieldNumber, (Long) objects[i]); + break; + case WireFormat.WIRETYPE_FIXED32: + output.writeFixed32(fieldNumber, (Integer) objects[i]); + break; + case WireFormat.WIRETYPE_FIXED64: + output.writeFixed64(fieldNumber, (Long) objects[i]); + break; + case WireFormat.WIRETYPE_LENGTH_DELIMITED: + output.writeBytes(fieldNumber, (ByteString) objects[i]); + break; + case WireFormat.WIRETYPE_START_GROUP: + output.writeTag(fieldNumber, WireFormat.WIRETYPE_START_GROUP); + ((UnknownFieldSetLite) objects[i]).writeTo(output); + output.writeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP); + break; + default: + throw InvalidProtocolBufferException.invalidWireType(); + } + } + } + + /** + * Get the number of bytes required to encode this set. + * + * <p>For use by generated code only. + */ + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) { + return size; + } + + size = 0; + for (int i = 0; i < count; i++) { + int tag = tags[i]; + int fieldNumber = WireFormat.getTagFieldNumber(tag); + switch (WireFormat.getTagWireType(tag)) { + case WireFormat.WIRETYPE_VARINT: + size += CodedOutputStream.computeUInt64Size(fieldNumber, (Long) objects[i]); + break; + case WireFormat.WIRETYPE_FIXED32: + size += CodedOutputStream.computeFixed32Size(fieldNumber, (Integer) objects[i]); + break; + case WireFormat.WIRETYPE_FIXED64: + size += CodedOutputStream.computeFixed64Size(fieldNumber, (Long) objects[i]); + break; + case WireFormat.WIRETYPE_LENGTH_DELIMITED: + size += CodedOutputStream.computeBytesSize(fieldNumber, (ByteString) objects[i]); + break; + case WireFormat.WIRETYPE_START_GROUP: + size += CodedOutputStream.computeTagSize(fieldNumber) * 2 + + ((UnknownFieldSetLite) objects[i]).getSerializedSize(); + break; + default: + throw new IllegalStateException(InvalidProtocolBufferException.invalidWireType()); + } + } + + memoizedSerializedSize = size; + + return size; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj == null) { + return false; + } + + if (!(obj instanceof UnknownFieldSetLite)) { + return false; + } + + UnknownFieldSetLite other = (UnknownFieldSetLite) obj; + if (count != other.count + // TODO(dweis): Only have to compare up to count but at worst 2x worse than we need to do. + || !Arrays.equals(tags, other.tags) + || !Arrays.deepEquals(objects, other.objects)) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int hashCode = 17; + + hashCode = 31 * hashCode + count; + hashCode = 31 * hashCode + Arrays.hashCode(tags); + hashCode = 31 * hashCode + Arrays.deepHashCode(objects); + + return hashCode; + } + + private void storeField(int tag, Object value) { + ensureCapacity(); + + tags[count] = tag; + objects[count] = value; + count++; + } + + /** + * Ensures that our arrays are long enough to store more metadata. + */ + private void ensureCapacity() { + if (count == tags.length) { + int increment = count < (MIN_CAPACITY / 2) ? MIN_CAPACITY : count >> 1; + int newLength = count + increment; + + tags = Arrays.copyOf(tags, newLength); + objects = Arrays.copyOf(objects, newLength); + } + } + + /** + * Parse a single field from {@code input} and merge it into this set. + * + * <p>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. + */ + boolean mergeFieldFrom(final int tag, final CodedInputStream input) throws IOException { + checkMutable(); + final int fieldNumber = WireFormat.getTagFieldNumber(tag); + switch (WireFormat.getTagWireType(tag)) { + case WireFormat.WIRETYPE_VARINT: + storeField(tag, input.readInt64()); + return true; + case WireFormat.WIRETYPE_FIXED32: + storeField(tag, input.readFixed32()); + return true; + case WireFormat.WIRETYPE_FIXED64: + storeField(tag, input.readFixed64()); + return true; + case WireFormat.WIRETYPE_LENGTH_DELIMITED: + storeField(tag, input.readBytes()); + return true; + case WireFormat.WIRETYPE_START_GROUP: + final UnknownFieldSetLite subFieldSet = new UnknownFieldSetLite(); + subFieldSet.mergeFrom(input); + input.checkLastTagWas( + WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP)); + storeField(tag, subFieldSet); + 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. + * + * <p>For use by generated code only. + */ + UnknownFieldSetLite mergeVarintField(int fieldNumber, int value) { + checkMutable(); + if (fieldNumber == 0) { + throw new IllegalArgumentException("Zero is not a valid field number."); + } + + storeField(WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_VARINT), (long) value); + + return this; + } + + /** + * Convenience method for merging a length-delimited field. + * + * <p>For use by generated code only. + */ + UnknownFieldSetLite mergeLengthDelimitedField(final int fieldNumber, final ByteString value) { + checkMutable(); + if (fieldNumber == 0) { + throw new IllegalArgumentException("Zero is not a valid field number."); + } + + storeField(WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED), value); + + return this; + } + + /** + * Parse an entire message from {@code input} and merge its fields into + * this set. + */ + private UnknownFieldSetLite 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; + } + + /** + * Builder for {@link UnknownFieldSetLite}s. + * + * <p>Use {@link UnknownFieldSet#newBuilder()} to construct a {@code Builder}. + * + * <p>For use by generated code only. + */ + // TODO(dweis): Update the mutable API to no longer need this builder and delete. + public static final class Builder { + + private UnknownFieldSetLite set; + + private Builder() { + this.set = null; + } + + /** + * Ensures internal state is initialized for use. + */ + private void ensureNotBuilt() { + if (set == null) { + set = new UnknownFieldSetLite(); + } + + set.checkMutable(); + } + + /** + * Parse a single field from {@code input} and merge it into this set. + * + * <p>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. + */ + boolean mergeFieldFrom(final int tag, final CodedInputStream input) throws IOException { + ensureNotBuilt(); + return set.mergeFieldFrom(tag, input); + } + + /** + * Convenience method for merging a new field containing a single varint + * value. This is used in particular when an unknown enum value is + * encountered. + * + * <p>For use by generated code only. + */ + Builder mergeVarintField(int fieldNumber, int value) { + ensureNotBuilt(); + set.mergeVarintField(fieldNumber, value); + return this; + } + + /** + * Convenience method for merging a length-delimited field. + * + * <p>For use by generated code only. + */ + public Builder mergeLengthDelimitedField(final int fieldNumber, final ByteString value) { + ensureNotBuilt(); + set.mergeLengthDelimitedField(fieldNumber, value); + return this; + } + + /** + * Build the {@link UnknownFieldSetLite} and return it. + * + * <p>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 an + * {@code UnsupportedOperationException} to be thrown. + * + * <p>For use by generated code only. + */ + public UnknownFieldSetLite build() { + if (set == null) { + return DEFAULT_INSTANCE; + } + + set.checkMutable(); + set.makeImmutable(); + + return set; + } + } +} diff --git a/java/core/src/main/java/com/google/protobuf/UnmodifiableLazyStringList.java b/java/core/src/main/java/com/google/protobuf/UnmodifiableLazyStringList.java new file mode 100644 index 00000000..5257c5a2 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/UnmodifiableLazyStringList.java @@ -0,0 +1,210 @@ +// 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.AbstractList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.RandomAccess; + +/** + * An implementation of {@link LazyStringList} that wraps another + * {@link LazyStringList} such that it cannot be modified via the wrapper. + * + * @author jonp@google.com (Jon Perlow) + */ +public class UnmodifiableLazyStringList extends AbstractList<String> + implements LazyStringList, RandomAccess { + + private final LazyStringList list; + + public UnmodifiableLazyStringList(LazyStringList list) { + this.list = list; + } + + @Override + public String get(int index) { + return list.get(index); + } + + @Override + public Object getRaw(int index) { + return list.getRaw(index); + } + + @Override + public int size() { + return list.size(); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public ByteString getByteString(int index) { + return list.getByteString(index); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public void add(ByteString element) { + throw new UnsupportedOperationException(); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public void set(int index, ByteString element) { + throw new UnsupportedOperationException(); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public boolean addAllByteString(Collection<? extends ByteString> element) { + throw new UnsupportedOperationException(); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public byte[] getByteArray(int index) { + return list.getByteArray(index); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public void add(byte[] element) { + throw new UnsupportedOperationException(); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public void set(int index, byte[] element) { + throw new UnsupportedOperationException(); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public boolean addAllByteArray(Collection<byte[]> element) { + throw new UnsupportedOperationException(); + } + + @Override + public ListIterator<String> listIterator(final int index) { + return new ListIterator<String>() { + ListIterator<String> iter = list.listIterator(index); + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public boolean hasNext() { + return iter.hasNext(); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public String next() { + return iter.next(); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public boolean hasPrevious() { + return iter.hasPrevious(); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public String previous() { + return iter.previous(); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public int nextIndex() { + return iter.nextIndex(); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public int previousIndex() { + return iter.previousIndex(); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public void remove() { + throw new UnsupportedOperationException(); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public void set(String o) { + throw new UnsupportedOperationException(); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public void add(String o) { + throw new UnsupportedOperationException(); + } + }; + } + + @Override + public Iterator<String> iterator() { + return new Iterator<String>() { + Iterator<String> iter = list.iterator(); + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public boolean hasNext() { + return iter.hasNext(); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public String next() { + return iter.next(); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public List<?> getUnderlyingElements() { + // The returned value is already unmodifiable. + return list.getUnderlyingElements(); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public void mergeFrom(LazyStringList other) { + throw new UnsupportedOperationException(); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public List<byte[]> asByteArrayList() { + return Collections.unmodifiableList(list.asByteArrayList()); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public List<ByteString> asByteStringList() { + return Collections.unmodifiableList(list.asByteStringList()); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public LazyStringList getUnmodifiableView() { + return this; + } +} diff --git a/java/core/src/main/java/com/google/protobuf/UnsafeByteStrings.java b/java/core/src/main/java/com/google/protobuf/UnsafeByteStrings.java new file mode 100644 index 00000000..c1997515 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/UnsafeByteStrings.java @@ -0,0 +1,55 @@ +// 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.nio.ByteBuffer; + +/** + * Provides unsafe factory methods for {@link ByteString} instances. + * + * <p><strong>DISCLAIMER:</strong> The methods in this class should only be called if it is + * guaranteed that the the buffer backing the {@link ByteString} will never change! Mutation of a + * {@link ByteString} can lead to unexpected and undesirable consequences in your application, + * and will likely be difficult to debug. Proceed with caution! + */ +public final class UnsafeByteStrings { + private UnsafeByteStrings() {} + + /** + * An unsafe operation that returns a {@link ByteString} that is backed by the provided buffer. + * + * @param buffer the Java NIO buffer to be wrapped. + * @return a {@link ByteString} backed by the provided buffer. + */ + public static ByteString unsafeWrap(ByteBuffer buffer) { + return new NioByteString(buffer); + } +} diff --git a/java/core/src/main/java/com/google/protobuf/Utf8.java b/java/core/src/main/java/com/google/protobuf/Utf8.java new file mode 100644 index 00000000..48c7e9e6 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/Utf8.java @@ -0,0 +1,481 @@ +// 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; + +/** + * A set of low-level, high-performance static utility methods related + * to the UTF-8 character encoding. This class has no dependencies + * outside of the core JDK libraries. + * + * <p>There are several variants of UTF-8. The one implemented by + * this class is the restricted definition of UTF-8 introduced in + * Unicode 3.1, which mandates the rejection of "overlong" byte + * sequences as well as rejection of 3-byte surrogate codepoint byte + * sequences. Note that the UTF-8 decoder included in Oracle's JDK + * has been modified to also reject "overlong" byte sequences, but (as + * of 2011) still accepts 3-byte surrogate codepoint byte sequences. + * + * <p>The byte sequences considered valid by this class are exactly + * those that can be roundtrip converted to Strings and back to bytes + * using the UTF-8 charset, without loss: <pre> {@code + * Arrays.equals(bytes, new String(bytes, Internal.UTF_8).getBytes(Internal.UTF_8)) + * }</pre> + * + * <p>See the Unicode Standard,</br> + * Table 3-6. <em>UTF-8 Bit Distribution</em>,</br> + * Table 3-7. <em>Well Formed UTF-8 Byte Sequences</em>. + * + * <p>This class supports decoding of partial byte sequences, so that the + * bytes in a complete UTF-8 byte sequences can be stored in multiple + * segments. Methods typically return {@link #MALFORMED} if the partial + * byte sequence is definitely not well-formed, {@link #COMPLETE} if it is + * well-formed in the absence of additional input, or if the byte sequence + * apparently terminated in the middle of a character, an opaque integer + * "state" value containing enough information to decode the character when + * passed to a subsequent invocation of a partial decoding method. + * + * @author martinrb@google.com (Martin Buchholz) + */ +final class Utf8 { + private Utf8() {} + + /** + * Maximum number of bytes per Java UTF-16 char in UTF-8. + * @see java.nio.charset.CharsetEncoder#maxBytesPerChar() + */ + static final int MAX_BYTES_PER_CHAR = 3; + + /** + * State value indicating that the byte sequence is well-formed and + * complete (no further bytes are needed to complete a character). + */ + public static final int COMPLETE = 0; + + /** + * State value indicating that the byte sequence is definitely not + * well-formed. + */ + public static final int MALFORMED = -1; + + // Other state values include the partial bytes of the incomplete + // character to be decoded in the simplest way: we pack the bytes + // into the state int in little-endian order. For example: + // + // int state = byte1 ^ (byte2 << 8) ^ (byte3 << 16); + // + // Such a state is unpacked thus (note the ~ operation for byte2 to + // undo byte1's sign-extension bits): + // + // int byte1 = (byte) state; + // int byte2 = (byte) ~(state >> 8); + // int byte3 = (byte) (state >> 16); + // + // We cannot store a zero byte in the state because it would be + // indistinguishable from the absence of a byte. But we don't need + // to, because partial bytes must always be negative. When building + // a state, we ensure that byte1 is negative and subsequent bytes + // are valid trailing bytes. + + /** + * Returns {@code true} if the given byte array is a well-formed + * UTF-8 byte sequence. + * + * <p>This is a convenience method, equivalent to a call to {@code + * isValidUtf8(bytes, 0, bytes.length)}. + */ + public static boolean isValidUtf8(byte[] bytes) { + return isValidUtf8(bytes, 0, bytes.length); + } + + /** + * Returns {@code true} if the given byte array slice is a + * well-formed UTF-8 byte sequence. The range of bytes to be + * checked extends from index {@code index}, inclusive, to {@code + * limit}, exclusive. + * + * <p>This is a convenience method, equivalent to {@code + * partialIsValidUtf8(bytes, index, limit) == Utf8.COMPLETE}. + */ + public static boolean isValidUtf8(byte[] bytes, int index, int limit) { + return partialIsValidUtf8(bytes, index, limit) == COMPLETE; + } + + /** + * Tells whether the given byte array slice is a well-formed, + * malformed, or incomplete UTF-8 byte sequence. The range of bytes + * to be checked extends from index {@code index}, inclusive, to + * {@code limit}, exclusive. + * + * @param state either {@link Utf8#COMPLETE} (if this is the initial decoding + * operation) or the value returned from a call to a partial decoding method + * for the previous bytes + * + * @return {@link #MALFORMED} if the partial byte sequence is + * definitely not well-formed, {@link #COMPLETE} if it is well-formed + * (no additional input needed), or if the byte sequence is + * "incomplete", i.e. apparently terminated in the middle of a character, + * an opaque integer "state" value containing enough information to + * decode the character when passed to a subsequent invocation of a + * partial decoding method. + */ + public static int partialIsValidUtf8( + int state, byte[] bytes, int index, int limit) { + if (state != COMPLETE) { + // The previous decoding operation was incomplete (or malformed). + // We look for a well-formed sequence consisting of bytes from + // the previous decoding operation (stored in state) together + // with bytes from the array slice. + // + // We expect such "straddler characters" to be rare. + + if (index >= limit) { // No bytes? No progress. + return state; + } + int byte1 = (byte) state; + // byte1 is never ASCII. + if (byte1 < (byte) 0xE0) { + // two-byte form + + // Simultaneously checks for illegal trailing-byte in + // leading position and overlong 2-byte form. + if (byte1 < (byte) 0xC2 || + // byte2 trailing-byte test + bytes[index++] > (byte) 0xBF) { + return MALFORMED; + } + } else if (byte1 < (byte) 0xF0) { + // three-byte form + + // Get byte2 from saved state or array + int byte2 = (byte) ~(state >> 8); + if (byte2 == 0) { + byte2 = bytes[index++]; + if (index >= limit) { + return incompleteStateFor(byte1, byte2); + } + } + if (byte2 > (byte) 0xBF || + // overlong? 5 most significant bits must not all be zero + (byte1 == (byte) 0xE0 && byte2 < (byte) 0xA0) || + // illegal surrogate codepoint? + (byte1 == (byte) 0xED && byte2 >= (byte) 0xA0) || + // byte3 trailing-byte test + bytes[index++] > (byte) 0xBF) { + return MALFORMED; + } + } else { + // four-byte form + + // Get byte2 and byte3 from saved state or array + int byte2 = (byte) ~(state >> 8); + int byte3 = 0; + if (byte2 == 0) { + byte2 = bytes[index++]; + if (index >= limit) { + return incompleteStateFor(byte1, byte2); + } + } else { + byte3 = (byte) (state >> 16); + } + if (byte3 == 0) { + byte3 = bytes[index++]; + if (index >= limit) { + return incompleteStateFor(byte1, byte2, byte3); + } + } + + // If we were called with state == MALFORMED, then byte1 is 0xFF, + // which never occurs in well-formed UTF-8, and so we will return + // MALFORMED again below. + + if (byte2 > (byte) 0xBF || + // Check that 1 <= plane <= 16. Tricky optimized form of: + // if (byte1 > (byte) 0xF4 || + // byte1 == (byte) 0xF0 && byte2 < (byte) 0x90 || + // byte1 == (byte) 0xF4 && byte2 > (byte) 0x8F) + (((byte1 << 28) + (byte2 - (byte) 0x90)) >> 30) != 0 || + // byte3 trailing-byte test + byte3 > (byte) 0xBF || + // byte4 trailing-byte test + bytes[index++] > (byte) 0xBF) { + return MALFORMED; + } + } + } + + return partialIsValidUtf8(bytes, index, limit); + } + + /** + * Tells whether the given byte array slice is a well-formed, + * malformed, or incomplete UTF-8 byte sequence. The range of bytes + * to be checked extends from index {@code index}, inclusive, to + * {@code limit}, exclusive. + * + * <p>This is a convenience method, equivalent to a call to {@code + * partialIsValidUtf8(Utf8.COMPLETE, bytes, index, limit)}. + * + * @return {@link #MALFORMED} if the partial byte sequence is + * definitely not well-formed, {@link #COMPLETE} if it is well-formed + * (no additional input needed), or if the byte sequence is + * "incomplete", i.e. apparently terminated in the middle of a character, + * an opaque integer "state" value containing enough information to + * decode the character when passed to a subsequent invocation of a + * partial decoding method. + */ + public static int partialIsValidUtf8( + byte[] bytes, int index, int limit) { + // Optimize for 100% ASCII. + // Hotspot loves small simple top-level loops like this. + while (index < limit && bytes[index] >= 0) { + index++; + } + + return (index >= limit) ? COMPLETE : + partialIsValidUtf8NonAscii(bytes, index, limit); + } + + private static int partialIsValidUtf8NonAscii( + byte[] bytes, int index, int limit) { + for (;;) { + int byte1, byte2; + + // Optimize for interior runs of ASCII bytes. + do { + if (index >= limit) { + return COMPLETE; + } + } while ((byte1 = bytes[index++]) >= 0); + + if (byte1 < (byte) 0xE0) { + // two-byte form + + if (index >= limit) { + return byte1; + } + + // Simultaneously checks for illegal trailing-byte in + // leading position and overlong 2-byte form. + if (byte1 < (byte) 0xC2 || + bytes[index++] > (byte) 0xBF) { + return MALFORMED; + } + } else if (byte1 < (byte) 0xF0) { + // three-byte form + + if (index >= limit - 1) { // incomplete sequence + return incompleteStateFor(bytes, index, limit); + } + if ((byte2 = bytes[index++]) > (byte) 0xBF || + // overlong? 5 most significant bits must not all be zero + (byte1 == (byte) 0xE0 && byte2 < (byte) 0xA0) || + // check for illegal surrogate codepoints + (byte1 == (byte) 0xED && byte2 >= (byte) 0xA0) || + // byte3 trailing-byte test + bytes[index++] > (byte) 0xBF) { + return MALFORMED; + } + } else { + // four-byte form + + if (index >= limit - 2) { // incomplete sequence + return incompleteStateFor(bytes, index, limit); + } + if ((byte2 = bytes[index++]) > (byte) 0xBF || + // Check that 1 <= plane <= 16. Tricky optimized form of: + // if (byte1 > (byte) 0xF4 || + // byte1 == (byte) 0xF0 && byte2 < (byte) 0x90 || + // byte1 == (byte) 0xF4 && byte2 > (byte) 0x8F) + (((byte1 << 28) + (byte2 - (byte) 0x90)) >> 30) != 0 || + // byte3 trailing-byte test + bytes[index++] > (byte) 0xBF || + // byte4 trailing-byte test + bytes[index++] > (byte) 0xBF) { + return MALFORMED; + } + } + } + } + + private static int incompleteStateFor(int byte1) { + return (byte1 > (byte) 0xF4) ? + MALFORMED : byte1; + } + + private static int incompleteStateFor(int byte1, int byte2) { + return (byte1 > (byte) 0xF4 || + byte2 > (byte) 0xBF) ? + MALFORMED : byte1 ^ (byte2 << 8); + } + + private static int incompleteStateFor(int byte1, int byte2, int byte3) { + return (byte1 > (byte) 0xF4 || + byte2 > (byte) 0xBF || + byte3 > (byte) 0xBF) ? + MALFORMED : byte1 ^ (byte2 << 8) ^ (byte3 << 16); + } + + private static int incompleteStateFor(byte[] bytes, int index, int limit) { + int byte1 = bytes[index - 1]; + switch (limit - index) { + case 0: return incompleteStateFor(byte1); + case 1: return incompleteStateFor(byte1, bytes[index]); + case 2: return incompleteStateFor(byte1, bytes[index], bytes[index + 1]); + default: throw new AssertionError(); + } + } + + + // These UTF-8 handling methods are copied from Guava's Utf8 class with a modification to throw + // a protocol buffer local exception. This exception is then caught in CodedOutputStream so it can + // fallback to more lenient behavior. + + static class UnpairedSurrogateException extends IllegalArgumentException { + + private UnpairedSurrogateException(int index, int length) { + super("Unpaired surrogate at index " + index + " of " + length); + } + } + + /** + * Returns the number of bytes in the UTF-8-encoded form of {@code sequence}. For a string, + * this method is equivalent to {@code string.getBytes(UTF_8).length}, but is more efficient in + * both time and space. + * + * @throws IllegalArgumentException if {@code sequence} contains ill-formed UTF-16 (unpaired + * surrogates) + */ + static int encodedLength(CharSequence sequence) { + // Warning to maintainers: this implementation is highly optimized. + int utf16Length = sequence.length(); + int utf8Length = utf16Length; + int i = 0; + + // This loop optimizes for pure ASCII. + while (i < utf16Length && sequence.charAt(i) < 0x80) { + i++; + } + + // This loop optimizes for chars less than 0x800. + for (; i < utf16Length; i++) { + char c = sequence.charAt(i); + if (c < 0x800) { + utf8Length += ((0x7f - c) >>> 31); // branch free! + } else { + utf8Length += encodedLengthGeneral(sequence, i); + break; + } + } + + if (utf8Length < utf16Length) { + // Necessary and sufficient condition for overflow because of maximum 3x expansion + throw new IllegalArgumentException("UTF-8 length does not fit in int: " + + (utf8Length + (1L << 32))); + } + return utf8Length; + } + + private static int encodedLengthGeneral(CharSequence sequence, int start) { + int utf16Length = sequence.length(); + int utf8Length = 0; + for (int i = start; i < utf16Length; i++) { + char c = sequence.charAt(i); + if (c < 0x800) { + utf8Length += (0x7f - c) >>> 31; // branch free! + } else { + utf8Length += 2; + // jdk7+: if (Character.isSurrogate(c)) { + if (Character.MIN_SURROGATE <= c && c <= Character.MAX_SURROGATE) { + // Check that we have a well-formed surrogate pair. + int cp = Character.codePointAt(sequence, i); + if (cp < Character.MIN_SUPPLEMENTARY_CODE_POINT) { + throw new UnpairedSurrogateException(i, utf16Length); + } + i++; + } + } + } + return utf8Length; + } + + static int encode(CharSequence sequence, byte[] bytes, int offset, int length) { + int utf16Length = sequence.length(); + int j = offset; + int i = 0; + int limit = offset + length; + // Designed to take advantage of + // https://wikis.oracle.com/display/HotSpotInternals/RangeCheckElimination + for (char c; i < utf16Length && i + j < limit && (c = sequence.charAt(i)) < 0x80; i++) { + bytes[j + i] = (byte) c; + } + if (i == utf16Length) { + return j + utf16Length; + } + j += i; + for (char c; i < utf16Length; i++) { + c = sequence.charAt(i); + if (c < 0x80 && j < limit) { + bytes[j++] = (byte) c; + } else if (c < 0x800 && j <= limit - 2) { // 11 bits, two UTF-8 bytes + bytes[j++] = (byte) ((0xF << 6) | (c >>> 6)); + bytes[j++] = (byte) (0x80 | (0x3F & c)); + } else if ((c < Character.MIN_SURROGATE || Character.MAX_SURROGATE < c) && j <= limit - 3) { + // Maximum single-char code point is 0xFFFF, 16 bits, three UTF-8 bytes + bytes[j++] = (byte) ((0xF << 5) | (c >>> 12)); + bytes[j++] = (byte) (0x80 | (0x3F & (c >>> 6))); + bytes[j++] = (byte) (0x80 | (0x3F & c)); + } else if (j <= limit - 4) { + // Minimum code point represented by a surrogate pair is 0x10000, 17 bits, four UTF-8 bytes + final char low; + if (i + 1 == sequence.length() + || !Character.isSurrogatePair(c, (low = sequence.charAt(++i)))) { + throw new UnpairedSurrogateException((i - 1), utf16Length); + } + int codePoint = Character.toCodePoint(c, low); + bytes[j++] = (byte) ((0xF << 4) | (codePoint >>> 18)); + bytes[j++] = (byte) (0x80 | (0x3F & (codePoint >>> 12))); + bytes[j++] = (byte) (0x80 | (0x3F & (codePoint >>> 6))); + bytes[j++] = (byte) (0x80 | (0x3F & codePoint)); + } else { + // If we are surrogates and we're not a surrogate pair, always throw an + // IllegalArgumentException instead of an ArrayOutOfBoundsException. + if ((Character.MIN_SURROGATE <= c && c <= Character.MAX_SURROGATE) + && (i + 1 == sequence.length() + || !Character.isSurrogatePair(c, sequence.charAt(i + 1)))) { + throw new UnpairedSurrogateException(i, utf16Length); + } + throw new ArrayIndexOutOfBoundsException("Failed writing " + c + " at index " + j); + } + } + return j; + } + // End Guava UTF-8 methods. +} diff --git a/java/core/src/main/java/com/google/protobuf/WireFormat.java b/java/core/src/main/java/com/google/protobuf/WireFormat.java new file mode 100644 index 00000000..8dbe1ae3 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/WireFormat.java @@ -0,0 +1,245 @@ +// 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; + +/** + * This class is used internally by the Protocol Buffer library and generated + * message implementations. It is public only because those generated messages + * do not reside in the {@code protobuf} package. Others should not use this + * class directly. + * + * This class contains constants and helper functions useful for dealing with + * the Protocol Buffer wire format. + * + * @author kenton@google.com Kenton Varda + */ +public final class WireFormat { + // Do not allow instantiation. + private WireFormat() {} + + public static final int WIRETYPE_VARINT = 0; + public static final int WIRETYPE_FIXED64 = 1; + public static final int WIRETYPE_LENGTH_DELIMITED = 2; + public static final int WIRETYPE_START_GROUP = 3; + public static final int WIRETYPE_END_GROUP = 4; + public static final int WIRETYPE_FIXED32 = 5; + + static final int TAG_TYPE_BITS = 3; + static final int TAG_TYPE_MASK = (1 << TAG_TYPE_BITS) - 1; + + /** Given a tag value, determines the wire type (the lower 3 bits). */ + public static int getTagWireType(final int tag) { + return tag & TAG_TYPE_MASK; + } + + /** Given a tag value, determines the field number (the upper 29 bits). */ + public static int getTagFieldNumber(final int tag) { + return tag >>> TAG_TYPE_BITS; + } + + /** Makes a tag value given a field number and wire type. */ + static int makeTag(final int fieldNumber, final int wireType) { + return (fieldNumber << TAG_TYPE_BITS) | wireType; + } + + /** + * Lite equivalent to {@link Descriptors.FieldDescriptor.JavaType}. This is + * only here to support the lite runtime and should not be used by users. + */ + public enum JavaType { + INT(0), + LONG(0L), + FLOAT(0F), + DOUBLE(0D), + BOOLEAN(false), + STRING(""), + BYTE_STRING(ByteString.EMPTY), + ENUM(null), + MESSAGE(null); + + JavaType(final Object defaultDefault) { + this.defaultDefault = defaultDefault; + } + + /** + * The default default value for fields of this type, if it's a primitive + * type. + */ + Object getDefaultDefault() { + return defaultDefault; + } + + private final Object defaultDefault; + } + + /** + * Lite equivalent to {@link Descriptors.FieldDescriptor.Type}. This is + * only here to support the lite runtime and should not be used by users. + */ + public enum FieldType { + DOUBLE (JavaType.DOUBLE , WIRETYPE_FIXED64 ), + FLOAT (JavaType.FLOAT , WIRETYPE_FIXED32 ), + INT64 (JavaType.LONG , WIRETYPE_VARINT ), + UINT64 (JavaType.LONG , WIRETYPE_VARINT ), + INT32 (JavaType.INT , WIRETYPE_VARINT ), + FIXED64 (JavaType.LONG , WIRETYPE_FIXED64 ), + FIXED32 (JavaType.INT , WIRETYPE_FIXED32 ), + BOOL (JavaType.BOOLEAN , WIRETYPE_VARINT ), + STRING (JavaType.STRING , WIRETYPE_LENGTH_DELIMITED) { + public boolean isPackable() { return false; } + }, + GROUP (JavaType.MESSAGE , WIRETYPE_START_GROUP ) { + public boolean isPackable() { return false; } + }, + MESSAGE (JavaType.MESSAGE , WIRETYPE_LENGTH_DELIMITED) { + public boolean isPackable() { return false; } + }, + BYTES (JavaType.BYTE_STRING, WIRETYPE_LENGTH_DELIMITED) { + public boolean isPackable() { return false; } + }, + UINT32 (JavaType.INT , WIRETYPE_VARINT ), + ENUM (JavaType.ENUM , WIRETYPE_VARINT ), + SFIXED32(JavaType.INT , WIRETYPE_FIXED32 ), + SFIXED64(JavaType.LONG , WIRETYPE_FIXED64 ), + SINT32 (JavaType.INT , WIRETYPE_VARINT ), + SINT64 (JavaType.LONG , WIRETYPE_VARINT ); + + FieldType(final JavaType javaType, final int wireType) { + this.javaType = javaType; + this.wireType = wireType; + } + + private final JavaType javaType; + private final int wireType; + + public JavaType getJavaType() { return javaType; } + public int getWireType() { return wireType; } + + public boolean isPackable() { return true; } + } + + // Field numbers for fields in MessageSet wire format. + static final int MESSAGE_SET_ITEM = 1; + static final int MESSAGE_SET_TYPE_ID = 2; + static final int MESSAGE_SET_MESSAGE = 3; + + // Tag numbers. + static final int MESSAGE_SET_ITEM_TAG = + makeTag(MESSAGE_SET_ITEM, WIRETYPE_START_GROUP); + static final int MESSAGE_SET_ITEM_END_TAG = + makeTag(MESSAGE_SET_ITEM, WIRETYPE_END_GROUP); + static final int MESSAGE_SET_TYPE_ID_TAG = + makeTag(MESSAGE_SET_TYPE_ID, WIRETYPE_VARINT); + static final int MESSAGE_SET_MESSAGE_TAG = + makeTag(MESSAGE_SET_MESSAGE, WIRETYPE_LENGTH_DELIMITED); + + /** + * Validation level for handling incoming string field data which potentially + * contain non-UTF8 bytes. + */ + enum Utf8Validation { + /** Eagerly parses to String; silently accepts invalid UTF8 bytes. */ + LOOSE { + Object readString(CodedInputStream input) throws IOException { + return input.readString(); + } + }, + /** Eagerly parses to String; throws an IOException on invalid bytes. */ + STRICT { + Object readString(CodedInputStream input) throws IOException { + return input.readStringRequireUtf8(); + } + }, + /** Keep data as ByteString; validation/conversion to String is lazy. */ + LAZY { + Object readString(CodedInputStream input) throws IOException { + return input.readBytes(); + } + }; + + /** Read a string field from the input with the proper UTF8 validation. */ + abstract Object readString(CodedInputStream input) throws IOException; + } + + /** + * Read a field of any primitive type for immutable messages from a + * CodedInputStream. Enums, groups, and embedded messages are not handled by + * this method. + * + * @param input The stream from which to read. + * @param type Declared type of the field. + * @param utf8Validation Different string UTF8 validation level for handling + * string fields. + * @return An object representing the field's value, of the exact + * type which would be returned by + * {@link Message#getField(Descriptors.FieldDescriptor)} for + * this field. + */ + static Object readPrimitiveField( + CodedInputStream input, + FieldType type, + Utf8Validation utf8Validation) throws IOException { + switch (type) { + case DOUBLE : return input.readDouble (); + case FLOAT : return input.readFloat (); + case INT64 : return input.readInt64 (); + case UINT64 : return input.readUInt64 (); + case INT32 : return input.readInt32 (); + case FIXED64 : return input.readFixed64 (); + case FIXED32 : return input.readFixed32 (); + case BOOL : return input.readBool (); + case BYTES : return input.readBytes (); + case UINT32 : return input.readUInt32 (); + case SFIXED32: return input.readSFixed32(); + case SFIXED64: return input.readSFixed64(); + case SINT32 : return input.readSInt32 (); + case SINT64 : return input.readSInt64 (); + + case STRING : return utf8Validation.readString(input); + case GROUP: + throw new IllegalArgumentException( + "readPrimitiveField() cannot handle nested groups."); + case MESSAGE: + throw new IllegalArgumentException( + "readPrimitiveField() cannot handle embedded messages."); + case ENUM: + // We don't handle enums because we don't know what to do if the + // value is not recognized. + throw new IllegalArgumentException( + "readPrimitiveField() cannot handle enums."); + } + + throw new RuntimeException( + "There is no way to get here, but the compiler thinks otherwise."); + } +} diff --git a/java/core/src/test/java/com/google/protobuf/AbstractMessageTest.java b/java/core/src/test/java/com/google/protobuf/AbstractMessageTest.java new file mode 100644 index 00000000..d964ef59 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/AbstractMessageTest.java @@ -0,0 +1,527 @@ +// 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 protobuf_unittest.UnittestOptimizeFor.TestOptimizedForSize; +import protobuf_unittest.UnittestProto; +import protobuf_unittest.UnittestProto.ForeignMessage; +import protobuf_unittest.UnittestProto.TestAllExtensions; +import protobuf_unittest.UnittestProto.TestAllTypes; +import protobuf_unittest.UnittestProto.TestPackedTypes; +import protobuf_unittest.UnittestProto.TestRequired; +import protobuf_unittest.UnittestProto.TestRequiredForeign; +import protobuf_unittest.UnittestProto.TestUnpackedTypes; + +import junit.framework.TestCase; + +import java.util.Map; + +/** + * Unit test for {@link AbstractMessage}. + * + * @author kenton@google.com Kenton Varda + */ +public class AbstractMessageTest extends TestCase { + /** + * Extends AbstractMessage and wraps some other message object. The methods + * of the Message interface which aren't explicitly implemented by + * AbstractMessage are forwarded to the wrapped object. This allows us to + * test that AbstractMessage's implementations work even if the wrapped + * object does not use them. + */ + private static class AbstractMessageWrapper extends AbstractMessage { + private final Message wrappedMessage; + + public AbstractMessageWrapper(Message wrappedMessage) { + this.wrappedMessage = wrappedMessage; + } + + public Descriptors.Descriptor getDescriptorForType() { + return wrappedMessage.getDescriptorForType(); + } + public AbstractMessageWrapper getDefaultInstanceForType() { + return new AbstractMessageWrapper( + wrappedMessage.getDefaultInstanceForType()); + } + public Map<Descriptors.FieldDescriptor, Object> getAllFields() { + return wrappedMessage.getAllFields(); + } + public boolean hasField(Descriptors.FieldDescriptor field) { + return wrappedMessage.hasField(field); + } + public Object getField(Descriptors.FieldDescriptor field) { + return wrappedMessage.getField(field); + } + public int getRepeatedFieldCount(Descriptors.FieldDescriptor field) { + return wrappedMessage.getRepeatedFieldCount(field); + } + public Object getRepeatedField( + Descriptors.FieldDescriptor field, int index) { + return wrappedMessage.getRepeatedField(field, index); + } + public UnknownFieldSet getUnknownFields() { + return wrappedMessage.getUnknownFields(); + } + public Builder newBuilderForType() { + return new Builder(wrappedMessage.newBuilderForType()); + } + public Builder toBuilder() { + return new Builder(wrappedMessage.toBuilder()); + } + + static class Builder extends AbstractMessage.Builder<Builder> { + private final Message.Builder wrappedBuilder; + + public Builder(Message.Builder wrappedBuilder) { + this.wrappedBuilder = wrappedBuilder; + } + + public AbstractMessageWrapper build() { + return new AbstractMessageWrapper(wrappedBuilder.build()); + } + public AbstractMessageWrapper buildPartial() { + return new AbstractMessageWrapper(wrappedBuilder.buildPartial()); + } + public Builder clone() { + return new Builder(wrappedBuilder.clone()); + } + public boolean isInitialized() { + return clone().buildPartial().isInitialized(); + } + public Descriptors.Descriptor getDescriptorForType() { + return wrappedBuilder.getDescriptorForType(); + } + public AbstractMessageWrapper getDefaultInstanceForType() { + return new AbstractMessageWrapper( + wrappedBuilder.getDefaultInstanceForType()); + } + public Map<Descriptors.FieldDescriptor, Object> getAllFields() { + return wrappedBuilder.getAllFields(); + } + public Builder newBuilderForField(Descriptors.FieldDescriptor field) { + return new Builder(wrappedBuilder.newBuilderForField(field)); + } + public boolean hasField(Descriptors.FieldDescriptor field) { + return wrappedBuilder.hasField(field); + } + public Object getField(Descriptors.FieldDescriptor field) { + return wrappedBuilder.getField(field); + } + public Builder setField(Descriptors.FieldDescriptor field, Object value) { + wrappedBuilder.setField(field, value); + return this; + } + public Builder clearField(Descriptors.FieldDescriptor field) { + wrappedBuilder.clearField(field); + return this; + } + public int getRepeatedFieldCount(Descriptors.FieldDescriptor field) { + return wrappedBuilder.getRepeatedFieldCount(field); + } + public Object getRepeatedField( + Descriptors.FieldDescriptor field, int index) { + return wrappedBuilder.getRepeatedField(field, index); + } + public Builder setRepeatedField(Descriptors.FieldDescriptor field, + int index, Object value) { + wrappedBuilder.setRepeatedField(field, index, value); + return this; + } + public Builder addRepeatedField( + Descriptors.FieldDescriptor field, Object value) { + wrappedBuilder.addRepeatedField(field, value); + return this; + } + public UnknownFieldSet getUnknownFields() { + return wrappedBuilder.getUnknownFields(); + } + public Builder setUnknownFields(UnknownFieldSet unknownFields) { + wrappedBuilder.setUnknownFields(unknownFields); + return this; + } + @Override + public Message.Builder getFieldBuilder(FieldDescriptor field) { + return wrappedBuilder.getFieldBuilder(field); + } + } + public Parser<? extends Message> getParserForType() { + return wrappedMessage.getParserForType(); + } + } + + // ================================================================= + + TestUtil.ReflectionTester reflectionTester = + new TestUtil.ReflectionTester(TestAllTypes.getDescriptor(), null); + + TestUtil.ReflectionTester extensionsReflectionTester = + new TestUtil.ReflectionTester(TestAllExtensions.getDescriptor(), + TestUtil.getExtensionRegistry()); + + public void testClear() throws Exception { + AbstractMessageWrapper message = + new AbstractMessageWrapper.Builder( + TestAllTypes.newBuilder(TestUtil.getAllSet())) + .clear().build(); + TestUtil.assertClear((TestAllTypes) message.wrappedMessage); + } + + public void testCopy() throws Exception { + AbstractMessageWrapper message = + new AbstractMessageWrapper.Builder(TestAllTypes.newBuilder()) + .mergeFrom(TestUtil.getAllSet()).build(); + TestUtil.assertAllFieldsSet((TestAllTypes) message.wrappedMessage); + } + + public void testSerializedSize() throws Exception { + TestAllTypes message = TestUtil.getAllSet(); + Message abstractMessage = new AbstractMessageWrapper(TestUtil.getAllSet()); + + assertEquals(message.getSerializedSize(), + abstractMessage.getSerializedSize()); + } + + public void testSerialization() throws Exception { + Message abstractMessage = new AbstractMessageWrapper(TestUtil.getAllSet()); + + TestUtil.assertAllFieldsSet( + TestAllTypes.parseFrom(abstractMessage.toByteString())); + + assertEquals(TestUtil.getAllSet().toByteString(), + abstractMessage.toByteString()); + } + + public void testParsing() throws Exception { + AbstractMessageWrapper.Builder builder = + new AbstractMessageWrapper.Builder(TestAllTypes.newBuilder()); + AbstractMessageWrapper message = + builder.mergeFrom(TestUtil.getAllSet().toByteString()).build(); + TestUtil.assertAllFieldsSet((TestAllTypes) message.wrappedMessage); + } + + public void testParsingUninitialized() throws Exception { + TestRequiredForeign.Builder builder = TestRequiredForeign.newBuilder(); + builder.getOptionalMessageBuilder().setDummy2(10); + ByteString bytes = builder.buildPartial().toByteString(); + Message.Builder abstractMessageBuilder = + new AbstractMessageWrapper.Builder(TestRequiredForeign.newBuilder()); + // mergeFrom() should not throw initialization error. + abstractMessageBuilder.mergeFrom(bytes).buildPartial(); + try { + abstractMessageBuilder.mergeFrom(bytes).build(); + fail(); + } catch (UninitializedMessageException ex) { + // pass + } + + // test DynamicMessage directly. + Message.Builder dynamicMessageBuilder = DynamicMessage.newBuilder( + TestRequiredForeign.getDescriptor()); + // mergeFrom() should not throw initialization error. + dynamicMessageBuilder.mergeFrom(bytes).buildPartial(); + try { + dynamicMessageBuilder.mergeFrom(bytes).build(); + fail(); + } catch (UninitializedMessageException ex) { + // pass + } + } + + public void testPackedSerialization() throws Exception { + Message abstractMessage = + new AbstractMessageWrapper(TestUtil.getPackedSet()); + + TestUtil.assertPackedFieldsSet( + TestPackedTypes.parseFrom(abstractMessage.toByteString())); + + assertEquals(TestUtil.getPackedSet().toByteString(), + abstractMessage.toByteString()); + } + + public void testPackedParsing() throws Exception { + AbstractMessageWrapper.Builder builder = + new AbstractMessageWrapper.Builder(TestPackedTypes.newBuilder()); + AbstractMessageWrapper message = + builder.mergeFrom(TestUtil.getPackedSet().toByteString()).build(); + TestUtil.assertPackedFieldsSet((TestPackedTypes) message.wrappedMessage); + } + + public void testUnpackedSerialization() throws Exception { + Message abstractMessage = + new AbstractMessageWrapper(TestUtil.getUnpackedSet()); + + TestUtil.assertUnpackedFieldsSet( + TestUnpackedTypes.parseFrom(abstractMessage.toByteString())); + + assertEquals(TestUtil.getUnpackedSet().toByteString(), + abstractMessage.toByteString()); + } + + public void testParsePackedToUnpacked() throws Exception { + AbstractMessageWrapper.Builder builder = + new AbstractMessageWrapper.Builder(TestUnpackedTypes.newBuilder()); + AbstractMessageWrapper message = + builder.mergeFrom(TestUtil.getPackedSet().toByteString()).build(); + TestUtil.assertUnpackedFieldsSet( + (TestUnpackedTypes) message.wrappedMessage); + } + + public void testParseUnpackedToPacked() throws Exception { + AbstractMessageWrapper.Builder builder = + new AbstractMessageWrapper.Builder(TestPackedTypes.newBuilder()); + AbstractMessageWrapper message = + builder.mergeFrom(TestUtil.getUnpackedSet().toByteString()).build(); + TestUtil.assertPackedFieldsSet((TestPackedTypes) message.wrappedMessage); + } + + public void testUnpackedParsing() throws Exception { + AbstractMessageWrapper.Builder builder = + new AbstractMessageWrapper.Builder(TestUnpackedTypes.newBuilder()); + AbstractMessageWrapper message = + builder.mergeFrom(TestUtil.getUnpackedSet().toByteString()).build(); + TestUtil.assertUnpackedFieldsSet( + (TestUnpackedTypes) message.wrappedMessage); + } + + public void testOptimizedForSize() throws Exception { + // We're mostly only checking that this class was compiled successfully. + TestOptimizedForSize message = + TestOptimizedForSize.newBuilder().setI(1).build(); + message = TestOptimizedForSize.parseFrom(message.toByteString()); + assertEquals(2, message.getSerializedSize()); + } + + // ----------------------------------------------------------------- + // Tests for isInitialized(). + + private static final TestRequired TEST_REQUIRED_UNINITIALIZED = + TestRequired.getDefaultInstance(); + private static final TestRequired TEST_REQUIRED_INITIALIZED = + TestRequired.newBuilder().setA(1).setB(2).setC(3).build(); + + public void testIsInitialized() throws Exception { + TestRequired.Builder builder = TestRequired.newBuilder(); + AbstractMessageWrapper.Builder abstractBuilder = + new AbstractMessageWrapper.Builder(builder); + + assertFalse(abstractBuilder.isInitialized()); + assertEquals("a, b, c", abstractBuilder.getInitializationErrorString()); + builder.setA(1); + assertFalse(abstractBuilder.isInitialized()); + assertEquals("b, c", abstractBuilder.getInitializationErrorString()); + builder.setB(1); + assertFalse(abstractBuilder.isInitialized()); + assertEquals("c", abstractBuilder.getInitializationErrorString()); + builder.setC(1); + assertTrue(abstractBuilder.isInitialized()); + assertEquals("", abstractBuilder.getInitializationErrorString()); + } + + public void testForeignIsInitialized() throws Exception { + TestRequiredForeign.Builder builder = TestRequiredForeign.newBuilder(); + AbstractMessageWrapper.Builder abstractBuilder = + new AbstractMessageWrapper.Builder(builder); + + assertTrue(abstractBuilder.isInitialized()); + assertEquals("", abstractBuilder.getInitializationErrorString()); + + builder.setOptionalMessage(TEST_REQUIRED_UNINITIALIZED); + assertFalse(abstractBuilder.isInitialized()); + assertEquals( + "optional_message.a, optional_message.b, optional_message.c", + abstractBuilder.getInitializationErrorString()); + + builder.setOptionalMessage(TEST_REQUIRED_INITIALIZED); + assertTrue(abstractBuilder.isInitialized()); + assertEquals("", abstractBuilder.getInitializationErrorString()); + + builder.addRepeatedMessage(TEST_REQUIRED_UNINITIALIZED); + assertFalse(abstractBuilder.isInitialized()); + assertEquals( + "repeated_message[0].a, repeated_message[0].b, repeated_message[0].c", + abstractBuilder.getInitializationErrorString()); + + builder.setRepeatedMessage(0, TEST_REQUIRED_INITIALIZED); + assertTrue(abstractBuilder.isInitialized()); + assertEquals("", abstractBuilder.getInitializationErrorString()); + } + + // ----------------------------------------------------------------- + // Tests for mergeFrom + + static final TestAllTypes MERGE_SOURCE = + TestAllTypes.newBuilder() + .setOptionalInt32(1) + .setOptionalString("foo") + .setOptionalForeignMessage(ForeignMessage.getDefaultInstance()) + .addRepeatedString("bar") + .build(); + + static final TestAllTypes MERGE_DEST = + TestAllTypes.newBuilder() + .setOptionalInt64(2) + .setOptionalString("baz") + .setOptionalForeignMessage(ForeignMessage.newBuilder().setC(3).build()) + .addRepeatedString("qux") + .build(); + + static final String MERGE_RESULT_TEXT = + "optional_int32: 1\n" + + "optional_int64: 2\n" + + "optional_string: \"foo\"\n" + + "optional_foreign_message {\n" + + " c: 3\n" + + "}\n" + + "repeated_string: \"qux\"\n" + + "repeated_string: \"bar\"\n"; + + public void testMergeFrom() throws Exception { + AbstractMessageWrapper result = + new AbstractMessageWrapper.Builder( + TestAllTypes.newBuilder(MERGE_DEST)) + .mergeFrom(MERGE_SOURCE).build(); + + assertEquals(MERGE_RESULT_TEXT, result.toString()); + } + + // ----------------------------------------------------------------- + // Tests for equals and hashCode + + public void testEqualsAndHashCode() throws Exception { + TestAllTypes a = TestUtil.getAllSet(); + TestAllTypes b = TestAllTypes.newBuilder().build(); + TestAllTypes c = TestAllTypes.newBuilder(b).addRepeatedString("x").build(); + TestAllTypes d = TestAllTypes.newBuilder(c).addRepeatedString("y").build(); + TestAllExtensions e = TestUtil.getAllExtensionsSet(); + TestAllExtensions f = TestAllExtensions.newBuilder(e) + .addExtension(UnittestProto.repeatedInt32Extension, 999).build(); + + checkEqualsIsConsistent(a); + checkEqualsIsConsistent(b); + checkEqualsIsConsistent(c); + checkEqualsIsConsistent(d); + checkEqualsIsConsistent(e); + checkEqualsIsConsistent(f); + + checkNotEqual(a, b); + checkNotEqual(a, c); + checkNotEqual(a, d); + checkNotEqual(a, e); + checkNotEqual(a, f); + + checkNotEqual(b, c); + checkNotEqual(b, d); + checkNotEqual(b, e); + checkNotEqual(b, f); + + checkNotEqual(c, d); + checkNotEqual(c, e); + checkNotEqual(c, f); + + checkNotEqual(d, e); + checkNotEqual(d, f); + + checkNotEqual(e, f); + + // Deserializing into the TestEmptyMessage such that every field + // is an {@link UnknownFieldSet.Field}. + UnittestProto.TestEmptyMessage eUnknownFields = + UnittestProto.TestEmptyMessage.parseFrom(e.toByteArray()); + UnittestProto.TestEmptyMessage fUnknownFields = + UnittestProto.TestEmptyMessage.parseFrom(f.toByteArray()); + checkNotEqual(eUnknownFields, fUnknownFields); + checkEqualsIsConsistent(eUnknownFields); + checkEqualsIsConsistent(fUnknownFields); + + // Subsequent reconstitutions should be identical + UnittestProto.TestEmptyMessage eUnknownFields2 = + UnittestProto.TestEmptyMessage.parseFrom(e.toByteArray()); + checkEqualsIsConsistent(eUnknownFields, eUnknownFields2); + } + + + /** + * Asserts that the given proto has symmetric equals and hashCode methods. + */ + private void checkEqualsIsConsistent(Message message) { + // Object should be equal to itself. + assertEquals(message, message); + + // Object should be equal to a dynamic copy of itself. + DynamicMessage dynamic = DynamicMessage.newBuilder(message).build(); + checkEqualsIsConsistent(message, dynamic); + } + + /** + * Asserts that the given protos are equal and have the same hash code. + */ + private void checkEqualsIsConsistent(Message message1, Message message2) { + assertEquals(message1, message2); + assertEquals(message2, message1); + assertEquals(message2.hashCode(), message1.hashCode()); + } + + /** + * Asserts that the given protos are not equal and have different hash codes. + * + * @warning It's valid for non-equal objects to have the same hash code, so + * this test is stricter than it needs to be. However, this should happen + * relatively rarely. + */ + private void checkNotEqual(Message m1, Message m2) { + String equalsError = String.format("%s should not be equal to %s", m1, m2); + assertFalse(equalsError, m1.equals(m2)); + assertFalse(equalsError, m2.equals(m1)); + + assertFalse( + String.format("%s should have a different hash code from %s", m1, m2), + m1.hashCode() == m2.hashCode()); + } + + public void testCheckByteStringIsUtf8OnUtf8() { + ByteString byteString = ByteString.copyFromUtf8("some text"); + AbstractMessageLite.checkByteStringIsUtf8(byteString); + // No exception thrown. + } + + public void testCheckByteStringIsUtf8OnNonUtf8() { + ByteString byteString = + ByteString.copyFrom(new byte[]{(byte) 0x80}); // A lone continuation byte. + try { + AbstractMessageLite.checkByteStringIsUtf8(byteString); + fail("Expected AbstractMessageLite.checkByteStringIsUtf8 to throw IllegalArgumentException"); + } catch (IllegalArgumentException exception) { + assertEquals("Byte string is not UTF-8.", exception.getMessage()); + } + } + +} diff --git a/java/core/src/test/java/com/google/protobuf/AnyTest.java b/java/core/src/test/java/com/google/protobuf/AnyTest.java new file mode 100644 index 00000000..e169f69d --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/AnyTest.java @@ -0,0 +1,92 @@ +// 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 any_test.AnyTestProto.TestAny; +import protobuf_unittest.UnittestProto.TestAllTypes; + +import junit.framework.TestCase; + +/** + * Unit tests for Any message. + */ +public class AnyTest extends TestCase { + public void testAnyGeneratedApi() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TestUtil.setAllFields(builder); + TestAllTypes message = builder.build(); + + TestAny container = TestAny.newBuilder() + .setValue(Any.pack(message)).build(); + + assertTrue(container.getValue().is(TestAllTypes.class)); + assertFalse(container.getValue().is(TestAny.class)); + + TestAllTypes result = container.getValue().unpack(TestAllTypes.class); + TestUtil.assertAllFieldsSet(result); + + + // Unpacking to a wrong type will throw an exception. + try { + TestAny wrongMessage = container.getValue().unpack(TestAny.class); + fail("Exception is expected."); + } catch (InvalidProtocolBufferException e) { + // expected. + } + + // Test that unpacking throws an exception if parsing fails. + TestAny.Builder containerBuilder = container.toBuilder(); + containerBuilder.getValueBuilder().setValue( + ByteString.copyFrom(new byte[]{0x11})); + container = containerBuilder.build(); + try { + TestAllTypes parsingFailed = container.getValue().unpack(TestAllTypes.class); + fail("Exception is expected."); + } catch (InvalidProtocolBufferException e) { + // expected. + } + } + + public void testCachedUnpackResult() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TestUtil.setAllFields(builder); + TestAllTypes message = builder.build(); + + TestAny container = TestAny.newBuilder() + .setValue(Any.pack(message)).build(); + + assertTrue(container.getValue().is(TestAllTypes.class)); + + TestAllTypes result1 = container.getValue().unpack(TestAllTypes.class); + TestAllTypes result2 = container.getValue().unpack(TestAllTypes.class); + assertTrue(result1 == result2); + } +} diff --git a/java/core/src/test/java/com/google/protobuf/BooleanArrayListTest.java b/java/core/src/test/java/com/google/protobuf/BooleanArrayListTest.java new file mode 100644 index 00000000..b8ad1fe4 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/BooleanArrayListTest.java @@ -0,0 +1,469 @@ +// 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 static java.util.Arrays.asList; + +import junit.framework.TestCase; + +import java.util.Collections; +import java.util.ConcurrentModificationException; +import java.util.Iterator; + +/** + * Tests for {@link BooleanArrayList}. + * + * @author dweis@google.com (Daniel Weis) + */ +public class BooleanArrayListTest extends TestCase { + + private static final BooleanArrayList UNARY_LIST = newImmutableBooleanArrayList(true); + private static final BooleanArrayList TERTIARY_LIST = + newImmutableBooleanArrayList(true, true, false); + + private BooleanArrayList list; + + @Override + protected void setUp() throws Exception { + list = new BooleanArrayList(); + } + + public void testEmptyListReturnsSameInstance() { + assertSame(BooleanArrayList.emptyList(), BooleanArrayList.emptyList()); + } + + public void testEmptyListIsImmutable() { + assertImmutable(BooleanArrayList.emptyList()); + } + + public void testMakeImmutable() { + list.addBoolean(true); + list.addBoolean(false); + list.addBoolean(true); + list.addBoolean(true); + list.makeImmutable(); + assertImmutable(list); + } + + public void testCopyConstructor() { + BooleanArrayList copy = new BooleanArrayList(TERTIARY_LIST); + assertEquals(TERTIARY_LIST, copy); + + copy = new BooleanArrayList(BooleanArrayList.emptyList()); + assertEquals(BooleanArrayList.emptyList(), copy); + + copy = new BooleanArrayList(asList(false, false, true)); + assertEquals(asList(false, false, true), copy); + + copy = new BooleanArrayList(Collections.<Boolean>emptyList()); + assertEquals(BooleanArrayList.emptyList(), copy); + } + + public void testModificationWithIteration() { + list.addAll(asList(true, false, false, true)); + Iterator<Boolean> iterator = list.iterator(); + assertEquals(4, list.size()); + assertEquals(true, (boolean) list.get(0)); + assertEquals(true, (boolean) iterator.next()); + list.set(0, true); + assertEquals(false, (boolean) iterator.next()); + + list.remove(0); + try { + iterator.next(); + fail(); + } catch (ConcurrentModificationException e) { + // expected + } + + iterator = list.iterator(); + list.add(0, false); + try { + iterator.next(); + fail(); + } catch (ConcurrentModificationException e) { + // expected + } + } + + public void testGet() { + assertEquals(true, (boolean) TERTIARY_LIST.get(0)); + assertEquals(true, (boolean) TERTIARY_LIST.get(1)); + assertEquals(false, (boolean) TERTIARY_LIST.get(2)); + + try { + TERTIARY_LIST.get(-1); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + + try { + TERTIARY_LIST.get(3); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + public void testGetInt() { + assertEquals(true, TERTIARY_LIST.getBoolean(0)); + assertEquals(true, TERTIARY_LIST.getBoolean(1)); + assertEquals(false, TERTIARY_LIST.getBoolean(2)); + + try { + TERTIARY_LIST.get(-1); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + + try { + TERTIARY_LIST.get(3); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + public void testSize() { + assertEquals(0, BooleanArrayList.emptyList().size()); + assertEquals(1, UNARY_LIST.size()); + assertEquals(3, TERTIARY_LIST.size()); + + list.addBoolean(true); + list.addBoolean(false); + list.addBoolean(false); + list.addBoolean(false); + assertEquals(4, list.size()); + + list.remove(0); + assertEquals(3, list.size()); + + list.add(true); + assertEquals(4, list.size()); + } + + public void testSet() { + list.addBoolean(false); + list.addBoolean(false); + + assertEquals(false, (boolean) list.set(0, true)); + assertEquals(true, list.getBoolean(0)); + + assertEquals(false, (boolean) list.set(1, false)); + assertEquals(false, list.getBoolean(1)); + + try { + list.set(-1, true); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + + try { + list.set(2, false); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + public void testSetInt() { + list.addBoolean(true); + list.addBoolean(true); + + assertEquals(true, list.setBoolean(0, false)); + assertEquals(false, list.getBoolean(0)); + + assertEquals(true, list.setBoolean(1, false)); + assertEquals(false, list.getBoolean(1)); + + try { + list.setBoolean(-1, false); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + + try { + list.setBoolean(2, true); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + public void testAdd() { + assertEquals(0, list.size()); + + assertTrue(list.add(true)); + assertEquals(asList(true), list); + + assertTrue(list.add(false)); + list.add(0, false); + assertEquals(asList(false, true, false), list); + + list.add(0, false); + list.add(0, true); + // Force a resize by getting up to 11 elements. + for (int i = 0; i < 6; i++) { + list.add(true); + } + assertEquals(asList(true, false, false, true, false, true, true, true, true, true, true), list); + + try { + list.add(-1, false); + } catch (IndexOutOfBoundsException e) { + // expected + } + + try { + list.add(4, true); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + public void testAddInt() { + assertEquals(0, list.size()); + + list.addBoolean(true); + assertEquals(asList(true), list); + + list.addBoolean(false); + assertEquals(asList(true, false), list); + } + + public void testAddAll() { + assertEquals(0, list.size()); + + assertTrue(list.addAll(Collections.singleton(false))); + assertEquals(1, list.size()); + assertEquals(false, (boolean) list.get(0)); + assertEquals(false, list.getBoolean(0)); + + assertTrue(list.addAll(asList(true, false, false, false, true))); + assertEquals(asList(false, true, false, false, false, true), list); + + assertTrue(list.addAll(TERTIARY_LIST)); + assertEquals(asList(false, true, false, false, false, true, true, true, false), list); + + assertFalse(list.addAll(Collections.<Boolean>emptyList())); + assertFalse(list.addAll(BooleanArrayList.emptyList())); + } + + public void testRemove() { + list.addAll(TERTIARY_LIST); + assertEquals(true, (boolean) list.remove(0)); + assertEquals(asList(true, false), list); + + assertTrue(list.remove(Boolean.TRUE)); + assertEquals(asList(false), list); + + assertFalse(list.remove(Boolean.TRUE)); + assertEquals(asList(false), list); + + assertEquals(false, (boolean) list.remove(0)); + assertEquals(asList(), list); + + try { + list.remove(-1); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + + try { + list.remove(0); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + private void assertImmutable(BooleanArrayList list) { + try { + list.add(false); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.add(0, true); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(Collections.<Boolean>emptyList()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(Collections.singletonList(false)); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(new BooleanArrayList()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(UNARY_LIST); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(0, Collections.singleton(true)); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(0, UNARY_LIST); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(0, Collections.<Boolean>emptyList()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addBoolean(true); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.clear(); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.remove(1); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.remove(new Object()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.removeAll(Collections.<Boolean>emptyList()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.removeAll(Collections.singleton(Boolean.TRUE)); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.removeAll(UNARY_LIST); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.retainAll(Collections.<Boolean>emptyList()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.retainAll(Collections.singleton(Boolean.TRUE)); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.retainAll(UNARY_LIST); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.set(0, true); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.setBoolean(0, false); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + } + + private static BooleanArrayList newImmutableBooleanArrayList(boolean... elements) { + BooleanArrayList list = new BooleanArrayList(); + for (boolean element : elements) { + list.addBoolean(element); + } + list.makeImmutable(); + return list; + } +} diff --git a/java/core/src/test/java/com/google/protobuf/BoundedByteStringTest.java b/java/core/src/test/java/com/google/protobuf/BoundedByteStringTest.java new file mode 100644 index 00000000..2dfae2e6 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/BoundedByteStringTest.java @@ -0,0 +1,101 @@ +// 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.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.UnsupportedEncodingException; + + +/** + * This class tests {@link BoundedByteString}, which extends {@link LiteralByteString}, + * by inheriting the tests from {@link LiteralByteStringTest}. The only method which + * is strange enough that it needs to be overridden here is {@link #testToString()}. + * + * @author carlanton@google.com (Carl Haverl) + */ +public class BoundedByteStringTest extends LiteralByteStringTest { + + @Override + protected void setUp() throws Exception { + classUnderTest = "BoundedByteString"; + byte[] sourceBytes = ByteStringTest.getTestBytes(2341, 11337766L); + int from = 100; + int to = sourceBytes.length - 100; + stringUnderTest = ByteString.copyFrom(sourceBytes).substring(from, to); + referenceBytes = new byte[to - from]; + System.arraycopy(sourceBytes, from, referenceBytes, 0, to - from); + expectedHashCode = 727575887; + } + + @Override + public void testToString() throws UnsupportedEncodingException { + String testString = "I love unicode \u1234\u5678 characters"; + LiteralByteString unicode = new LiteralByteString(testString.getBytes(Internal.UTF_8)); + ByteString chopped = unicode.substring(2, unicode.size() - 6); + assertEquals(classUnderTest + ".substring() must have the expected type", + classUnderTest, getActualClassName(chopped)); + + String roundTripString = chopped.toString(UTF_8); + assertEquals(classUnderTest + " unicode bytes must match", + testString.substring(2, testString.length() - 6), roundTripString); + } + + @Override + public void testCharsetToString() { + String testString = "I love unicode \u1234\u5678 characters"; + LiteralByteString unicode = new LiteralByteString(testString.getBytes(Internal.UTF_8)); + ByteString chopped = unicode.substring(2, unicode.size() - 6); + assertEquals(classUnderTest + ".substring() must have the expected type", + classUnderTest, getActualClassName(chopped)); + + String roundTripString = chopped.toString(Internal.UTF_8); + assertEquals(classUnderTest + " unicode bytes must match", + testString.substring(2, testString.length() - 6), roundTripString); + } + + @Override + public void testJavaSerialization() throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(out); + oos.writeObject(stringUnderTest); + oos.close(); + byte[] pickled = out.toByteArray(); + InputStream in = new ByteArrayInputStream(pickled); + ObjectInputStream ois = new ObjectInputStream(in); + Object o = ois.readObject(); + assertTrue("Didn't get a ByteString back", o instanceof ByteString); + assertEquals("Should get an equal ByteString back", stringUnderTest, o); + } +} diff --git a/java/core/src/test/java/com/google/protobuf/ByteStringTest.java b/java/core/src/test/java/com/google/protobuf/ByteStringTest.java new file mode 100644 index 00000000..36f64251 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/ByteStringTest.java @@ -0,0 +1,760 @@ +// 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.ByteString.Output; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Random; + +/** + * Test methods with implementations in {@link ByteString}, plus do some top-level "integration" + * tests. + * + * @author carlanton@google.com (Carl Haverl) + */ +public class ByteStringTest extends TestCase { + + private static final Charset UTF_16 = Charset.forName("UTF-16"); + + static byte[] getTestBytes(int size, long seed) { + Random random = new Random(seed); + byte[] result = new byte[size]; + random.nextBytes(result); + return result; + } + + private byte[] getTestBytes(int size) { + return getTestBytes(size, 445566L); + } + + private byte[] getTestBytes() { + return getTestBytes(1000); + } + + // Compare the entire left array with a subset of the right array. + private boolean isArrayRange(byte[] left, byte[] right, int rightOffset, int length) { + boolean stillEqual = (left.length == length); + for (int i = 0; (stillEqual && i < length); ++i) { + stillEqual = (left[i] == right[rightOffset + i]); + } + return stillEqual; + } + + // Returns true only if the given two arrays have identical contents. + private boolean isArray(byte[] left, byte[] right) { + return left.length == right.length && isArrayRange(left, right, 0, left.length); + } + + public void testSubstring_BeginIndex() { + byte[] bytes = getTestBytes(); + ByteString substring = ByteString.copyFrom(bytes).substring(500); + assertTrue("substring must contain the tail of the string", + isArrayRange(substring.toByteArray(), bytes, 500, bytes.length - 500)); + } + + public void testCopyFrom_BytesOffsetSize() { + byte[] bytes = getTestBytes(); + ByteString byteString = ByteString.copyFrom(bytes, 500, 200); + assertTrue("copyFrom sub-range must contain the expected bytes", + isArrayRange(byteString.toByteArray(), bytes, 500, 200)); + } + + public void testCopyFrom_Bytes() { + byte[] bytes = getTestBytes(); + ByteString byteString = ByteString.copyFrom(bytes); + assertTrue("copyFrom must contain the expected bytes", + isArray(byteString.toByteArray(), bytes)); + } + + public void testCopyFrom_ByteBufferSize() { + byte[] bytes = getTestBytes(); + ByteBuffer byteBuffer = ByteBuffer.allocate(bytes.length); + byteBuffer.put(bytes); + byteBuffer.position(500); + ByteString byteString = ByteString.copyFrom(byteBuffer, 200); + assertTrue("copyFrom byteBuffer sub-range must contain the expected bytes", + isArrayRange(byteString.toByteArray(), bytes, 500, 200)); + } + + public void testCopyFrom_ByteBuffer() { + byte[] bytes = getTestBytes(); + ByteBuffer byteBuffer = ByteBuffer.allocate(bytes.length); + byteBuffer.put(bytes); + byteBuffer.position(500); + ByteString byteString = ByteString.copyFrom(byteBuffer); + assertTrue("copyFrom byteBuffer sub-range must contain the expected bytes", + isArrayRange(byteString.toByteArray(), bytes, 500, bytes.length - 500)); + } + + public void testCopyFrom_StringEncoding() { + String testString = "I love unicode \u1234\u5678 characters"; + ByteString byteString = ByteString.copyFrom(testString, UTF_16); + byte[] testBytes = testString.getBytes(UTF_16); + assertTrue("copyFrom string must respect the charset", + isArrayRange(byteString.toByteArray(), testBytes, 0, testBytes.length)); + } + + public void testCopyFrom_Utf8() { + String testString = "I love unicode \u1234\u5678 characters"; + ByteString byteString = ByteString.copyFromUtf8(testString); + byte[] testBytes = testString.getBytes(Internal.UTF_8); + assertTrue("copyFromUtf8 string must respect the charset", + isArrayRange(byteString.toByteArray(), testBytes, 0, testBytes.length)); + } + + public void testCopyFrom_Iterable() { + byte[] testBytes = getTestBytes(77777, 113344L); + final List<ByteString> pieces = makeConcretePieces(testBytes); + // Call copyFrom() on a Collection + ByteString byteString = ByteString.copyFrom(pieces); + assertTrue("copyFrom a List must contain the expected bytes", + isArrayRange(byteString.toByteArray(), testBytes, 0, testBytes.length)); + // Call copyFrom on an iteration that's not a collection + ByteString byteStringAlt = ByteString.copyFrom(new Iterable<ByteString>() { + @Override + public Iterator<ByteString> iterator() { + return pieces.iterator(); + } + }); + assertEquals("copyFrom from an Iteration must contain the expected bytes", + byteString, byteStringAlt); + } + + public void testCopyTo_TargetOffset() { + byte[] bytes = getTestBytes(); + ByteString byteString = ByteString.copyFrom(bytes); + byte[] target = new byte[bytes.length + 1000]; + byteString.copyTo(target, 400); + assertTrue("copyFrom byteBuffer sub-range must contain the expected bytes", + isArrayRange(bytes, target, 400, bytes.length)); + } + + public void testReadFrom_emptyStream() throws IOException { + ByteString byteString = + ByteString.readFrom(new ByteArrayInputStream(new byte[0])); + assertSame("reading an empty stream must result in the EMPTY constant " + + "byte string", ByteString.EMPTY, byteString); + } + + public void testReadFrom_smallStream() throws IOException { + assertReadFrom(getTestBytes(10)); + } + + public void testReadFrom_mutating() throws IOException { + byte[] capturedArray = null; + EvilInputStream eis = new EvilInputStream(); + ByteString byteString = ByteString.readFrom(eis); + + capturedArray = eis.capturedArray; + byte[] originalValue = byteString.toByteArray(); + for (int x = 0; x < capturedArray.length; ++x) { + capturedArray[x] = (byte) 0; + } + + byte[] newValue = byteString.toByteArray(); + assertTrue("copyFrom byteBuffer must not grant access to underlying array", + Arrays.equals(originalValue, newValue)); + } + + // Tests sizes that are near the rope copy-out threshold. + public void testReadFrom_mediumStream() throws IOException { + assertReadFrom(getTestBytes(ByteString.CONCATENATE_BY_COPY_SIZE - 1)); + assertReadFrom(getTestBytes(ByteString.CONCATENATE_BY_COPY_SIZE)); + assertReadFrom(getTestBytes(ByteString.CONCATENATE_BY_COPY_SIZE + 1)); + assertReadFrom(getTestBytes(200)); + } + + // Tests sizes that are over multi-segment rope threshold. + public void testReadFrom_largeStream() throws IOException { + assertReadFrom(getTestBytes(0x100)); + assertReadFrom(getTestBytes(0x101)); + assertReadFrom(getTestBytes(0x110)); + assertReadFrom(getTestBytes(0x1000)); + assertReadFrom(getTestBytes(0x1001)); + assertReadFrom(getTestBytes(0x1010)); + assertReadFrom(getTestBytes(0x10000)); + assertReadFrom(getTestBytes(0x10001)); + assertReadFrom(getTestBytes(0x10010)); + } + + // Tests sizes that are near the read buffer size. + public void testReadFrom_byteBoundaries() throws IOException { + final int min = ByteString.MIN_READ_FROM_CHUNK_SIZE; + final int max = ByteString.MAX_READ_FROM_CHUNK_SIZE; + + assertReadFrom(getTestBytes(min - 1)); + assertReadFrom(getTestBytes(min)); + assertReadFrom(getTestBytes(min + 1)); + + assertReadFrom(getTestBytes(min * 2 - 1)); + assertReadFrom(getTestBytes(min * 2)); + assertReadFrom(getTestBytes(min * 2 + 1)); + + assertReadFrom(getTestBytes(min * 4 - 1)); + assertReadFrom(getTestBytes(min * 4)); + assertReadFrom(getTestBytes(min * 4 + 1)); + + assertReadFrom(getTestBytes(min * 8 - 1)); + assertReadFrom(getTestBytes(min * 8)); + assertReadFrom(getTestBytes(min * 8 + 1)); + + assertReadFrom(getTestBytes(max - 1)); + assertReadFrom(getTestBytes(max)); + assertReadFrom(getTestBytes(max + 1)); + + assertReadFrom(getTestBytes(max * 2 - 1)); + assertReadFrom(getTestBytes(max * 2)); + assertReadFrom(getTestBytes(max * 2 + 1)); + } + + // Tests that IOExceptions propagate through ByteString.readFrom(). + public void testReadFrom_IOExceptions() { + try { + ByteString.readFrom(new FailStream()); + fail("readFrom must throw the underlying IOException"); + + } catch (IOException e) { + assertEquals("readFrom must throw the expected exception", + "synthetic failure", e.getMessage()); + } + } + + // Tests that ByteString.readFrom works with streams that don't + // always fill their buffers. + public void testReadFrom_reluctantStream() throws IOException { + final byte[] data = getTestBytes(0x1000); + + ByteString byteString = ByteString.readFrom(new ReluctantStream(data)); + assertTrue("readFrom byte stream must contain the expected bytes", + isArray(byteString.toByteArray(), data)); + + // Same test as above, but with some specific chunk sizes. + assertReadFromReluctantStream(data, 100); + assertReadFromReluctantStream(data, 248); + assertReadFromReluctantStream(data, 249); + assertReadFromReluctantStream(data, 250); + assertReadFromReluctantStream(data, 251); + assertReadFromReluctantStream(data, 0x1000); + assertReadFromReluctantStream(data, 0x1001); + } + + // Fails unless ByteString.readFrom reads the bytes correctly from a + // reluctant stream with the given chunkSize parameter. + private void assertReadFromReluctantStream(byte[] bytes, int chunkSize) + throws IOException { + ByteString b = ByteString.readFrom(new ReluctantStream(bytes), chunkSize); + assertTrue("readFrom byte stream must contain the expected bytes", + isArray(b.toByteArray(), bytes)); + } + + // Tests that ByteString.readFrom works with streams that implement + // available(). + public void testReadFrom_available() throws IOException { + final byte[] data = getTestBytes(0x1001); + + ByteString byteString = ByteString.readFrom(new AvailableStream(data)); + assertTrue("readFrom byte stream must contain the expected bytes", + isArray(byteString.toByteArray(), data)); + } + + // Fails unless ByteString.readFrom reads the bytes correctly. + private void assertReadFrom(byte[] bytes) throws IOException { + ByteString byteString = + ByteString.readFrom(new ByteArrayInputStream(bytes)); + assertTrue("readFrom byte stream must contain the expected bytes", + isArray(byteString.toByteArray(), bytes)); + } + + // A stream that fails when read. + private static final class FailStream extends InputStream { + @Override public int read() throws IOException { + throw new IOException("synthetic failure"); + } + } + + // A stream that simulates blocking by only producing 250 characters + // per call to read(byte[]). + private static class ReluctantStream extends InputStream { + protected final byte[] data; + protected int pos = 0; + + public ReluctantStream(byte[] data) { + this.data = data; + } + + @Override public int read() { + if (pos == data.length) { + return -1; + } else { + return data[pos++]; + } + } + + @Override public int read(byte[] buf) { + return read(buf, 0, buf.length); + } + + @Override public int read(byte[] buf, int offset, int size) { + if (pos == data.length) { + return -1; + } + int count = Math.min(Math.min(size, data.length - pos), 250); + System.arraycopy(data, pos, buf, offset, count); + pos += count; + return count; + } + } + + // Same as above, but also implements available(). + private static final class AvailableStream extends ReluctantStream { + public AvailableStream(byte[] data) { + super(data); + } + + @Override public int available() { + return Math.min(250, data.length - pos); + } + } + + // A stream which exposes the byte array passed into read(byte[], int, int). + private static class EvilInputStream extends InputStream { + public byte[] capturedArray = null; + + @Override + public int read(byte[] buf, int off, int len) { + if (capturedArray != null) { + return -1; + } else { + capturedArray = buf; + for (int x = 0; x < len; ++x) { + buf[x] = (byte) x; + } + return len; + } + } + + @Override + public int read() { + // Purposefully do nothing. + return -1; + } + } + + // A stream which exposes the byte array passed into write(byte[], int, int). + private static class EvilOutputStream extends OutputStream { + public byte[] capturedArray = null; + + @Override + public void write(byte[] buf, int off, int len) { + if (capturedArray == null) { + capturedArray = buf; + } + } + + @Override + public void write(int ignored) { + // Purposefully do nothing. + } + } + + public void testToStringUtf8() { + String testString = "I love unicode \u1234\u5678 characters"; + byte[] testBytes = testString.getBytes(Internal.UTF_8); + ByteString byteString = ByteString.copyFrom(testBytes); + assertEquals("copyToStringUtf8 must respect the charset", + testString, byteString.toStringUtf8()); + } + + public void testNewOutput_InitialCapacity() throws IOException { + byte[] bytes = getTestBytes(); + ByteString.Output output = ByteString.newOutput(bytes.length + 100); + output.write(bytes); + ByteString byteString = output.toByteString(); + assertTrue( + "String built from newOutput(int) must contain the expected bytes", + isArrayRange(bytes, byteString.toByteArray(), 0, bytes.length)); + } + + // Test newOutput() using a variety of buffer sizes and a variety of (fixed) + // write sizes + public void testNewOutput_ArrayWrite() { + byte[] bytes = getTestBytes(); + int length = bytes.length; + int[] bufferSizes = {128, 256, length / 2, length - 1, length, length + 1, + 2 * length, 3 * length}; + int[] writeSizes = {1, 4, 5, 7, 23, bytes.length}; + + for (int bufferSize : bufferSizes) { + for (int writeSize : writeSizes) { + // Test writing the entire output writeSize bytes at a time. + ByteString.Output output = ByteString.newOutput(bufferSize); + for (int i = 0; i < length; i += writeSize) { + output.write(bytes, i, Math.min(writeSize, length - i)); + } + ByteString byteString = output.toByteString(); + assertTrue("String built from newOutput() must contain the expected bytes", + isArrayRange(bytes, byteString.toByteArray(), 0, bytes.length)); + } + } + } + + // Test newOutput() using a variety of buffer sizes, but writing all the + // characters using write(byte); + public void testNewOutput_WriteChar() { + byte[] bytes = getTestBytes(); + int length = bytes.length; + int[] bufferSizes = {0, 1, 128, 256, length / 2, + length - 1, length, length + 1, + 2 * length, 3 * length}; + for (int bufferSize : bufferSizes) { + ByteString.Output output = ByteString.newOutput(bufferSize); + for (byte byteValue : bytes) { + output.write(byteValue); + } + ByteString byteString = output.toByteString(); + assertTrue("String built from newOutput() must contain the expected bytes", + isArrayRange(bytes, byteString.toByteArray(), 0, bytes.length)); + } + } + + // Test newOutput() in which we write the bytes using a variety of methods + // and sizes, and in which we repeatedly call toByteString() in the middle. + public void testNewOutput_Mixed() { + Random rng = new Random(1); + byte[] bytes = getTestBytes(); + int length = bytes.length; + int[] bufferSizes = {0, 1, 128, 256, length / 2, + length - 1, length, length + 1, + 2 * length, 3 * length}; + + for (int bufferSize : bufferSizes) { + // Test writing the entire output using a mixture of write sizes and + // methods; + ByteString.Output output = ByteString.newOutput(bufferSize); + int position = 0; + while (position < bytes.length) { + if (rng.nextBoolean()) { + int count = 1 + rng.nextInt(bytes.length - position); + output.write(bytes, position, count); + position += count; + } else { + output.write(bytes[position]); + position++; + } + assertEquals("size() returns the right value", position, output.size()); + assertTrue("newOutput() substring must have correct bytes", + isArrayRange(output.toByteString().toByteArray(), + bytes, 0, position)); + } + ByteString byteString = output.toByteString(); + assertTrue("String built from newOutput() must contain the expected bytes", + isArrayRange(bytes, byteString.toByteArray(), 0, bytes.length)); + } + } + + public void testNewOutputEmpty() { + // Make sure newOutput() correctly builds empty byte strings + ByteString byteString = ByteString.newOutput().toByteString(); + assertEquals(ByteString.EMPTY, byteString); + } + + public void testNewOutput_Mutating() throws IOException { + Output os = ByteString.newOutput(5); + os.write(new byte[] {1, 2, 3, 4, 5}); + EvilOutputStream eos = new EvilOutputStream(); + os.writeTo(eos); + byte[] capturedArray = eos.capturedArray; + ByteString byteString = os.toByteString(); + byte[] oldValue = byteString.toByteArray(); + Arrays.fill(capturedArray, (byte) 0); + byte[] newValue = byteString.toByteArray(); + assertTrue("Output must not provide access to the underlying byte array", + Arrays.equals(oldValue, newValue)); + } + + public void testNewCodedBuilder() throws IOException { + byte[] bytes = getTestBytes(); + ByteString.CodedBuilder builder = ByteString.newCodedBuilder(bytes.length); + builder.getCodedOutput().writeRawBytes(bytes); + ByteString byteString = builder.build(); + assertTrue("String built from newCodedBuilder() must contain the expected bytes", + isArrayRange(bytes, byteString.toByteArray(), 0, bytes.length)); + } + + public void testSubstringParity() { + byte[] bigBytes = getTestBytes(2048 * 1024, 113344L); + int start = 512 * 1024 - 3333; + int end = 512 * 1024 + 7777; + ByteString concreteSubstring = ByteString.copyFrom(bigBytes).substring(start, end); + boolean ok = true; + for (int i = start; ok && i < end; ++i) { + ok = (bigBytes[i] == concreteSubstring.byteAt(i - start)); + } + assertTrue("Concrete substring didn't capture the right bytes", ok); + + ByteString literalString = ByteString.copyFrom(bigBytes, start, end - start); + assertTrue("Substring must be equal to literal string", + concreteSubstring.equals(literalString)); + assertEquals("Substring must have same hashcode as literal string", + literalString.hashCode(), concreteSubstring.hashCode()); + } + + public void testCompositeSubstring() { + byte[] referenceBytes = getTestBytes(77748, 113344L); + + List<ByteString> pieces = makeConcretePieces(referenceBytes); + ByteString listString = ByteString.copyFrom(pieces); + + int from = 1000; + int to = 40000; + ByteString compositeSubstring = listString.substring(from, to); + byte[] substringBytes = compositeSubstring.toByteArray(); + boolean stillEqual = true; + for (int i = 0; stillEqual && i < to - from; ++i) { + stillEqual = referenceBytes[from + i] == substringBytes[i]; + } + assertTrue("Substring must return correct bytes", stillEqual); + + stillEqual = true; + for (int i = 0; stillEqual && i < to - from; ++i) { + stillEqual = referenceBytes[from + i] == compositeSubstring.byteAt(i); + } + assertTrue("Substring must support byteAt() correctly", stillEqual); + + ByteString literalSubstring = ByteString.copyFrom(referenceBytes, from, to - from); + assertTrue("Composite substring must equal a literal substring over the same bytes", + compositeSubstring.equals(literalSubstring)); + assertTrue("Literal substring must equal a composite substring over the same bytes", + literalSubstring.equals(compositeSubstring)); + + assertEquals("We must get the same hashcodes for composite and literal substrings", + literalSubstring.hashCode(), compositeSubstring.hashCode()); + + assertFalse("We can't be equal to a proper substring", + compositeSubstring.equals(literalSubstring.substring(0, literalSubstring.size() - 1))); + } + + public void testCopyFromList() { + byte[] referenceBytes = getTestBytes(77748, 113344L); + ByteString literalString = ByteString.copyFrom(referenceBytes); + + List<ByteString> pieces = makeConcretePieces(referenceBytes); + ByteString listString = ByteString.copyFrom(pieces); + + assertTrue("Composite string must be equal to literal string", + listString.equals(literalString)); + assertEquals("Composite string must have same hashcode as literal string", + literalString.hashCode(), listString.hashCode()); + } + + public void testConcat() { + byte[] referenceBytes = getTestBytes(77748, 113344L); + ByteString literalString = ByteString.copyFrom(referenceBytes); + + List<ByteString> pieces = makeConcretePieces(referenceBytes); + + Iterator<ByteString> iter = pieces.iterator(); + ByteString concatenatedString = iter.next(); + while (iter.hasNext()) { + concatenatedString = concatenatedString.concat(iter.next()); + } + + assertTrue("Concatenated string must be equal to literal string", + concatenatedString.equals(literalString)); + assertEquals("Concatenated string must have same hashcode as literal string", + literalString.hashCode(), concatenatedString.hashCode()); + } + + /** + * Test the Rope implementation can deal with Empty nodes, even though we + * guard against them. See also {@link LiteralByteStringTest#testConcat_empty()}. + */ + public void testConcat_empty() { + byte[] referenceBytes = getTestBytes(7748, 113344L); + ByteString literalString = ByteString.copyFrom(referenceBytes); + + ByteString duo = RopeByteString.newInstanceForTest(literalString, literalString); + ByteString temp = RopeByteString.newInstanceForTest( + RopeByteString.newInstanceForTest(literalString, ByteString.EMPTY), + RopeByteString.newInstanceForTest(ByteString.EMPTY, literalString)); + ByteString quintet = RopeByteString.newInstanceForTest(temp, ByteString.EMPTY); + + assertTrue("String with concatenated nulls must equal simple concatenate", + duo.equals(quintet)); + assertEquals("String with concatenated nulls have same hashcode as simple concatenate", + duo.hashCode(), quintet.hashCode()); + + ByteString.ByteIterator duoIter = duo.iterator(); + ByteString.ByteIterator quintetIter = quintet.iterator(); + boolean stillEqual = true; + while (stillEqual && quintetIter.hasNext()) { + stillEqual = (duoIter.nextByte() == quintetIter.nextByte()); + } + assertTrue("We must get the same characters by iterating", stillEqual); + assertFalse("Iterator must be exhausted", duoIter.hasNext()); + try { + duoIter.nextByte(); + fail("Should have thrown an exception."); + } catch (NoSuchElementException e) { + // This is success + } + try { + quintetIter.nextByte(); + fail("Should have thrown an exception."); + } catch (NoSuchElementException e) { + // This is success + } + + // Test that even if we force empty strings in as rope leaves in this + // configuration, we always get a (possibly Bounded) LiteralByteString + // for a length 1 substring. + // + // It is possible, using the testing factory method to create deeply nested + // trees of empty leaves, to make a string that will fail this test. + for (int i = 1; i < duo.size(); ++i) { + assertTrue("Substrings of size() < 2 must not be RopeByteStrings", + duo.substring(i - 1, i) instanceof LiteralByteString); + } + for (int i = 1; i < quintet.size(); ++i) { + assertTrue("Substrings of size() < 2 must not be RopeByteStrings", + quintet.substring(i - 1, i) instanceof LiteralByteString); + } + } + + public void testStartsWith() { + byte[] bytes = getTestBytes(1000, 1234L); + ByteString string = ByteString.copyFrom(bytes); + ByteString prefix = ByteString.copyFrom(bytes, 0, 500); + ByteString suffix = ByteString.copyFrom(bytes, 400, 600); + assertTrue(string.startsWith(ByteString.EMPTY)); + assertTrue(string.startsWith(string)); + assertTrue(string.startsWith(prefix)); + assertFalse(string.startsWith(suffix)); + assertFalse(prefix.startsWith(suffix)); + assertFalse(suffix.startsWith(prefix)); + assertFalse(ByteString.EMPTY.startsWith(prefix)); + assertTrue(ByteString.EMPTY.startsWith(ByteString.EMPTY)); + } + + public void testEndsWith() { + byte[] bytes = getTestBytes(1000, 1234L); + ByteString string = ByteString.copyFrom(bytes); + ByteString prefix = ByteString.copyFrom(bytes, 0, 500); + ByteString suffix = ByteString.copyFrom(bytes, 400, 600); + assertTrue(string.endsWith(ByteString.EMPTY)); + assertTrue(string.endsWith(string)); + assertTrue(string.endsWith(suffix)); + assertFalse(string.endsWith(prefix)); + assertFalse(suffix.endsWith(prefix)); + assertFalse(prefix.endsWith(suffix)); + assertFalse(ByteString.EMPTY.endsWith(suffix)); + assertTrue(ByteString.EMPTY.endsWith(ByteString.EMPTY)); + } + + static List<ByteString> makeConcretePieces(byte[] referenceBytes) { + List<ByteString> pieces = new ArrayList<ByteString>(); + // Starting length should be small enough that we'll do some concatenating by + // copying if we just concatenate all these pieces together. + for (int start = 0, length = 16; start < referenceBytes.length; start += length) { + length = (length << 1) - 1; + if (start + length > referenceBytes.length) { + length = referenceBytes.length - start; + } + pieces.add(ByteString.copyFrom(referenceBytes, start, length)); + } + return pieces; + } + + private byte[] substringUsingWriteTo( + ByteString data, int offset, int length) throws IOException { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + data.writeTo(output, offset, length); + return output.toByteArray(); + } + + public void testWriteToOutputStream() throws Exception { + // Choose a size large enough so when two ByteStrings are concatenated they + // won't be merged into one byte array due to some optimizations. + final int dataSize = ByteString.CONCATENATE_BY_COPY_SIZE + 1; + byte[] data1 = new byte[dataSize]; + for (int i = 0; i < data1.length; i++) { + data1[i] = (byte) 1; + } + data1[1] = (byte) 11; + // Test LiteralByteString.writeTo(OutputStream,int,int) + LiteralByteString left = new LiteralByteString(data1); + byte[] result = substringUsingWriteTo(left, 1, 1); + assertEquals(1, result.length); + assertEquals((byte) 11, result[0]); + + byte[] data2 = new byte[dataSize]; + for (int i = 0; i < data1.length; i++) { + data2[i] = (byte) 2; + } + LiteralByteString right = new LiteralByteString(data2); + // Concatenate two ByteStrings to create a RopeByteString. + ByteString root = left.concat(right); + // Make sure we are actually testing a RopeByteString with a simple tree + // structure. + assertEquals(1, root.getTreeDepth()); + // Write parts of the left node. + result = substringUsingWriteTo(root, 0, dataSize); + assertEquals(dataSize, result.length); + assertEquals((byte) 1, result[0]); + assertEquals((byte) 1, result[dataSize - 1]); + // Write parts of the right node. + result = substringUsingWriteTo(root, dataSize, dataSize); + assertEquals(dataSize, result.length); + assertEquals((byte) 2, result[0]); + assertEquals((byte) 2, result[dataSize - 1]); + // Write a segment of bytes that runs across both nodes. + result = substringUsingWriteTo(root, dataSize / 2, dataSize); + assertEquals(dataSize, result.length); + assertEquals((byte) 1, result[0]); + assertEquals((byte) 1, result[dataSize - dataSize / 2 - 1]); + assertEquals((byte) 2, result[dataSize - dataSize / 2]); + assertEquals((byte) 2, result[dataSize - 1]); + } +} diff --git a/java/core/src/test/java/com/google/protobuf/CheckUtf8Test.java b/java/core/src/test/java/com/google/protobuf/CheckUtf8Test.java new file mode 100644 index 00000000..3d6381c9 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/CheckUtf8Test.java @@ -0,0 +1,140 @@ +// 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 proto2_test_check_utf8.TestCheckUtf8.BytesWrapper; +import proto2_test_check_utf8.TestCheckUtf8.StringWrapper; +import proto2_test_check_utf8_size.TestCheckUtf8Size.BytesWrapperSize; +import proto2_test_check_utf8_size.TestCheckUtf8Size.StringWrapperSize; +import junit.framework.TestCase; + +/** + * Test that protos generated with file option java_string_check_utf8 do in + * fact perform appropriate UTF-8 checks. + * + * @author jbaum@google.com (Jacob Butcher) + */ +public class CheckUtf8Test extends TestCase { + + private static final String UTF8_BYTE_STRING_TEXT = "some text"; + private static final ByteString UTF8_BYTE_STRING = + ByteString.copyFromUtf8(UTF8_BYTE_STRING_TEXT); + private static final ByteString NON_UTF8_BYTE_STRING = + ByteString.copyFrom(new byte[]{(byte) 0x80}); // A lone continuation byte. + + public void testBuildRequiredStringWithGoodUtf8() throws Exception { + assertEquals(UTF8_BYTE_STRING_TEXT, + StringWrapper.newBuilder().setReqBytes(UTF8_BYTE_STRING).getReq()); + } + + public void testParseRequiredStringWithGoodUtf8() throws Exception { + ByteString serialized = + BytesWrapper.newBuilder().setReq(UTF8_BYTE_STRING).build().toByteString(); + assertEquals(UTF8_BYTE_STRING_TEXT, StringWrapper.parser().parseFrom(serialized).getReq()); + } + + public void testBuildRequiredStringWithBadUtf8() throws Exception { + try { + StringWrapper.newBuilder().setReqBytes(NON_UTF8_BYTE_STRING); + fail("Expected IllegalArgumentException for non UTF-8 byte string."); + } catch (IllegalArgumentException exception) { + assertEquals("Byte string is not UTF-8.", exception.getMessage()); + } + } + + public void testBuildOptionalStringWithBadUtf8() throws Exception { + try { + StringWrapper.newBuilder().setOptBytes(NON_UTF8_BYTE_STRING); + fail("Expected IllegalArgumentException for non UTF-8 byte string."); + } catch (IllegalArgumentException exception) { + assertEquals("Byte string is not UTF-8.", exception.getMessage()); + } + } + + public void testBuildRepeatedStringWithBadUtf8() throws Exception { + try { + StringWrapper.newBuilder().addRepBytes(NON_UTF8_BYTE_STRING); + fail("Expected IllegalArgumentException for non UTF-8 byte string."); + } catch (IllegalArgumentException exception) { + assertEquals("Byte string is not UTF-8.", exception.getMessage()); + } + } + + public void testParseRequiredStringWithBadUtf8() throws Exception { + ByteString serialized = + BytesWrapper.newBuilder().setReq(NON_UTF8_BYTE_STRING).build().toByteString(); + try { + StringWrapper.parser().parseFrom(serialized); + fail("Expected InvalidProtocolBufferException for non UTF-8 byte string."); + } catch (InvalidProtocolBufferException exception) { + assertEquals("Protocol message had invalid UTF-8.", exception.getMessage()); + } + } + + public void testBuildRequiredStringWithBadUtf8Size() throws Exception { + try { + StringWrapperSize.newBuilder().setReqBytes(NON_UTF8_BYTE_STRING); + fail("Expected IllegalArgumentException for non UTF-8 byte string."); + } catch (IllegalArgumentException exception) { + assertEquals("Byte string is not UTF-8.", exception.getMessage()); + } + } + + public void testBuildOptionalStringWithBadUtf8Size() throws Exception { + try { + StringWrapperSize.newBuilder().setOptBytes(NON_UTF8_BYTE_STRING); + fail("Expected IllegalArgumentException for non UTF-8 byte string."); + } catch (IllegalArgumentException exception) { + assertEquals("Byte string is not UTF-8.", exception.getMessage()); + } + } + + public void testBuildRepeatedStringWithBadUtf8Size() throws Exception { + try { + StringWrapperSize.newBuilder().addRepBytes(NON_UTF8_BYTE_STRING); + fail("Expected IllegalArgumentException for non UTF-8 byte string."); + } catch (IllegalArgumentException exception) { + assertEquals("Byte string is not UTF-8.", exception.getMessage()); + } + } + + public void testParseRequiredStringWithBadUtf8Size() throws Exception { + ByteString serialized = + BytesWrapperSize.newBuilder().setReq(NON_UTF8_BYTE_STRING).build().toByteString(); + try { + StringWrapperSize.parser().parseFrom(serialized); + fail("Expected InvalidProtocolBufferException for non UTF-8 byte string."); + } catch (InvalidProtocolBufferException exception) { + assertEquals("Protocol message had invalid UTF-8.", exception.getMessage()); + } + } + +} diff --git a/java/core/src/test/java/com/google/protobuf/CodedInputStreamTest.java b/java/core/src/test/java/com/google/protobuf/CodedInputStreamTest.java new file mode 100644 index 00000000..18d8142c --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/CodedInputStreamTest.java @@ -0,0 +1,769 @@ +// 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.UnittestProto.BoolMessage; +import protobuf_unittest.UnittestProto.Int32Message; +import protobuf_unittest.UnittestProto.Int64Message; +import protobuf_unittest.UnittestProto.TestAllTypes; +import protobuf_unittest.UnittestProto.TestRecursiveMessage; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; + +/** + * Unit test for {@link CodedInputStream}. + * + * @author kenton@google.com Kenton Varda + */ +public class CodedInputStreamTest extends TestCase { + /** + * Helper to construct a byte array from a bunch of bytes. The inputs are + * actually ints so that I can use hex notation and not get stupid errors + * about precision. + */ + private byte[] bytes(int... bytesAsInts) { + byte[] bytes = new byte[bytesAsInts.length]; + for (int i = 0; i < bytesAsInts.length; i++) { + bytes[i] = (byte) bytesAsInts[i]; + } + return bytes; + } + + /** + * An InputStream which limits the number of bytes it reads at a time. + * We use this to make sure that CodedInputStream doesn't screw up when + * reading in small blocks. + */ + private static final class SmallBlockInputStream extends FilterInputStream { + private final int blockSize; + + public SmallBlockInputStream(byte[] data, int blockSize) { + this(new ByteArrayInputStream(data), blockSize); + } + + public SmallBlockInputStream(InputStream in, int blockSize) { + super(in); + this.blockSize = blockSize; + } + + public int read(byte[] b) throws IOException { + return super.read(b, 0, Math.min(b.length, blockSize)); + } + + public int read(byte[] b, int off, int len) throws IOException { + return super.read(b, off, Math.min(len, blockSize)); + } + } + + private void assertDataConsumed(byte[] data, CodedInputStream input) + throws IOException { + assertEquals(data.length, input.getTotalBytesRead()); + assertTrue(input.isAtEnd()); + } + + /** + * Parses the given bytes using readRawVarint32() and readRawVarint64() and + * checks that the result matches the given value. + */ + private void assertReadVarint(byte[] data, long value) throws Exception { + CodedInputStream input = CodedInputStream.newInstance(data); + assertEquals((int) value, input.readRawVarint32()); + assertDataConsumed(data, input); + + input = CodedInputStream.newInstance(data); + assertEquals(value, input.readRawVarint64()); + assertDataConsumed(data, input); + + input = CodedInputStream.newInstance(data); + assertEquals(value, input.readRawVarint64SlowPath()); + assertDataConsumed(data, input); + + input = CodedInputStream.newInstance(data); + assertTrue(input.skipField(WireFormat.WIRETYPE_VARINT)); + assertDataConsumed(data, input); + + // Try different block sizes. + for (int blockSize = 1; blockSize <= 16; blockSize *= 2) { + input = CodedInputStream.newInstance( + new SmallBlockInputStream(data, blockSize)); + assertEquals((int) value, input.readRawVarint32()); + assertDataConsumed(data, input); + + input = CodedInputStream.newInstance( + new SmallBlockInputStream(data, blockSize)); + assertEquals(value, input.readRawVarint64()); + assertDataConsumed(data, input); + + input = CodedInputStream.newInstance( + new SmallBlockInputStream(data, blockSize)); + assertEquals(value, input.readRawVarint64SlowPath()); + assertDataConsumed(data, input); + + input = CodedInputStream.newInstance( + new SmallBlockInputStream(data, blockSize)); + assertTrue(input.skipField(WireFormat.WIRETYPE_VARINT)); + assertDataConsumed(data, input); + } + + // Try reading direct from an InputStream. We want to verify that it + // doesn't read past the end of the input, so we copy to a new, bigger + // array first. + byte[] longerData = new byte[data.length + 1]; + System.arraycopy(data, 0, longerData, 0, data.length); + InputStream rawInput = new ByteArrayInputStream(longerData); + assertEquals((int) value, CodedInputStream.readRawVarint32(rawInput)); + assertEquals(1, rawInput.available()); + } + + /** + * Parses the given bytes using readRawVarint32() and readRawVarint64() and + * expects them to fail with an InvalidProtocolBufferException whose + * description matches the given one. + */ + private void assertReadVarintFailure( + InvalidProtocolBufferException expected, byte[] data) + throws Exception { + CodedInputStream input = CodedInputStream.newInstance(data); + try { + input.readRawVarint32(); + fail("Should have thrown an exception."); + } catch (InvalidProtocolBufferException e) { + assertEquals(expected.getMessage(), e.getMessage()); + } + + input = CodedInputStream.newInstance(data); + try { + input.readRawVarint64(); + fail("Should have thrown an exception."); + } catch (InvalidProtocolBufferException e) { + assertEquals(expected.getMessage(), e.getMessage()); + } + + input = CodedInputStream.newInstance(data); + try { + input.readRawVarint64SlowPath(); + fail("Should have thrown an exception."); + } catch (InvalidProtocolBufferException e) { + assertEquals(expected.getMessage(), e.getMessage()); + } + + // Make sure we get the same error when reading direct from an InputStream. + try { + CodedInputStream.readRawVarint32(new ByteArrayInputStream(data)); + fail("Should have thrown an exception."); + } catch (InvalidProtocolBufferException e) { + assertEquals(expected.getMessage(), e.getMessage()); + } + } + + /** Tests readRawVarint32() and readRawVarint64(). */ + public void testReadVarint() throws Exception { + assertReadVarint(bytes(0x00), 0); + assertReadVarint(bytes(0x01), 1); + assertReadVarint(bytes(0x7f), 127); + // 14882 + assertReadVarint(bytes(0xa2, 0x74), (0x22 << 0) | (0x74 << 7)); + // 2961488830 + assertReadVarint(bytes(0xbe, 0xf7, 0x92, 0x84, 0x0b), + (0x3e << 0) | (0x77 << 7) | (0x12 << 14) | (0x04 << 21) | + (0x0bL << 28)); + + // 64-bit + // 7256456126 + assertReadVarint(bytes(0xbe, 0xf7, 0x92, 0x84, 0x1b), + (0x3e << 0) | (0x77 << 7) | (0x12 << 14) | (0x04 << 21) | + (0x1bL << 28)); + // 41256202580718336 + assertReadVarint( + bytes(0x80, 0xe6, 0xeb, 0x9c, 0xc3, 0xc9, 0xa4, 0x49), + (0x00 << 0) | (0x66 << 7) | (0x6b << 14) | (0x1c << 21) | + (0x43L << 28) | (0x49L << 35) | (0x24L << 42) | (0x49L << 49)); + // 11964378330978735131 + assertReadVarint( + bytes(0x9b, 0xa8, 0xf9, 0xc2, 0xbb, 0xd6, 0x80, 0x85, 0xa6, 0x01), + (0x1b << 0) | (0x28 << 7) | (0x79 << 14) | (0x42 << 21) | + (0x3bL << 28) | (0x56L << 35) | (0x00L << 42) | + (0x05L << 49) | (0x26L << 56) | (0x01L << 63)); + + // Failures + assertReadVarintFailure( + InvalidProtocolBufferException.malformedVarint(), + bytes(0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x00)); + assertReadVarintFailure( + InvalidProtocolBufferException.truncatedMessage(), + bytes(0x80)); + } + + /** + * Parses the given bytes using readRawLittleEndian32() and checks + * that the result matches the given value. + */ + private void assertReadLittleEndian32(byte[] data, int value) + throws Exception { + CodedInputStream input = CodedInputStream.newInstance(data); + assertEquals(value, input.readRawLittleEndian32()); + assertTrue(input.isAtEnd()); + + // Try different block sizes. + for (int blockSize = 1; blockSize <= 16; blockSize *= 2) { + input = CodedInputStream.newInstance( + new SmallBlockInputStream(data, blockSize)); + assertEquals(value, input.readRawLittleEndian32()); + assertTrue(input.isAtEnd()); + } + } + + /** + * Parses the given bytes using readRawLittleEndian64() and checks + * that the result matches the given value. + */ + private void assertReadLittleEndian64(byte[] data, long value) + throws Exception { + CodedInputStream input = CodedInputStream.newInstance(data); + assertEquals(value, input.readRawLittleEndian64()); + assertTrue(input.isAtEnd()); + + // Try different block sizes. + for (int blockSize = 1; blockSize <= 16; blockSize *= 2) { + input = CodedInputStream.newInstance( + new SmallBlockInputStream(data, blockSize)); + assertEquals(value, input.readRawLittleEndian64()); + assertTrue(input.isAtEnd()); + } + } + + /** Tests readRawLittleEndian32() and readRawLittleEndian64(). */ + public void testReadLittleEndian() throws Exception { + assertReadLittleEndian32(bytes(0x78, 0x56, 0x34, 0x12), 0x12345678); + assertReadLittleEndian32(bytes(0xf0, 0xde, 0xbc, 0x9a), 0x9abcdef0); + + assertReadLittleEndian64( + bytes(0xf0, 0xde, 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12), + 0x123456789abcdef0L); + assertReadLittleEndian64( + bytes(0x78, 0x56, 0x34, 0x12, 0xf0, 0xde, 0xbc, 0x9a), + 0x9abcdef012345678L); + } + + /** Test decodeZigZag32() and decodeZigZag64(). */ + public void testDecodeZigZag() throws Exception { + assertEquals( 0, CodedInputStream.decodeZigZag32(0)); + assertEquals(-1, CodedInputStream.decodeZigZag32(1)); + assertEquals( 1, CodedInputStream.decodeZigZag32(2)); + assertEquals(-2, CodedInputStream.decodeZigZag32(3)); + assertEquals(0x3FFFFFFF, CodedInputStream.decodeZigZag32(0x7FFFFFFE)); + assertEquals(0xC0000000, CodedInputStream.decodeZigZag32(0x7FFFFFFF)); + assertEquals(0x7FFFFFFF, CodedInputStream.decodeZigZag32(0xFFFFFFFE)); + assertEquals(0x80000000, CodedInputStream.decodeZigZag32(0xFFFFFFFF)); + + assertEquals( 0, CodedInputStream.decodeZigZag64(0)); + assertEquals(-1, CodedInputStream.decodeZigZag64(1)); + assertEquals( 1, CodedInputStream.decodeZigZag64(2)); + assertEquals(-2, CodedInputStream.decodeZigZag64(3)); + assertEquals(0x000000003FFFFFFFL, + CodedInputStream.decodeZigZag64(0x000000007FFFFFFEL)); + assertEquals(0xFFFFFFFFC0000000L, + CodedInputStream.decodeZigZag64(0x000000007FFFFFFFL)); + assertEquals(0x000000007FFFFFFFL, + CodedInputStream.decodeZigZag64(0x00000000FFFFFFFEL)); + assertEquals(0xFFFFFFFF80000000L, + CodedInputStream.decodeZigZag64(0x00000000FFFFFFFFL)); + assertEquals(0x7FFFFFFFFFFFFFFFL, + CodedInputStream.decodeZigZag64(0xFFFFFFFFFFFFFFFEL)); + assertEquals(0x8000000000000000L, + CodedInputStream.decodeZigZag64(0xFFFFFFFFFFFFFFFFL)); + } + + /** Tests reading and parsing a whole message with every field type. */ + public void testReadWholeMessage() throws Exception { + TestAllTypes message = TestUtil.getAllSet(); + + byte[] rawBytes = message.toByteArray(); + assertEquals(rawBytes.length, message.getSerializedSize()); + + TestAllTypes message2 = TestAllTypes.parseFrom(rawBytes); + TestUtil.assertAllFieldsSet(message2); + + // Try different block sizes. + for (int blockSize = 1; blockSize < 256; blockSize *= 2) { + message2 = TestAllTypes.parseFrom( + new SmallBlockInputStream(rawBytes, blockSize)); + TestUtil.assertAllFieldsSet(message2); + } + } + + /** Tests skipField(). */ + public void testSkipWholeMessage() throws Exception { + TestAllTypes message = TestUtil.getAllSet(); + byte[] rawBytes = message.toByteArray(); + + // Create two parallel inputs. Parse one as unknown fields while using + // skipField() to skip each field on the other. Expect the same tags. + CodedInputStream input1 = CodedInputStream.newInstance(rawBytes); + CodedInputStream input2 = CodedInputStream.newInstance(rawBytes); + UnknownFieldSet.Builder unknownFields = UnknownFieldSet.newBuilder(); + + while (true) { + int tag = input1.readTag(); + assertEquals(tag, input2.readTag()); + if (tag == 0) { + break; + } + unknownFields.mergeFieldFrom(tag, input1); + input2.skipField(tag); + } + } + + + /** + * Test that a bug in skipRawBytes() has been fixed: if the skip skips + * exactly up to a limit, this should not break things. + */ + public void testSkipRawBytesBug() throws Exception { + byte[] rawBytes = new byte[] { 1, 2 }; + CodedInputStream input = CodedInputStream.newInstance(rawBytes); + + int limit = input.pushLimit(1); + input.skipRawBytes(1); + input.popLimit(limit); + assertEquals(2, input.readRawByte()); + } + + /** + * Test that a bug in skipRawBytes() has been fixed: if the skip skips + * past the end of a buffer with a limit that has been set past the end of + * that buffer, this should not break things. + */ + public void testSkipRawBytesPastEndOfBufferWithLimit() throws Exception { + byte[] rawBytes = new byte[] { 1, 2, 3, 4, 5 }; + CodedInputStream input = CodedInputStream.newInstance( + new SmallBlockInputStream(rawBytes, 3)); + + int limit = input.pushLimit(4); + // In order to expose the bug we need to read at least one byte to prime the + // buffer inside the CodedInputStream. + assertEquals(1, input.readRawByte()); + // Skip to the end of the limit. + input.skipRawBytes(3); + assertTrue(input.isAtEnd()); + input.popLimit(limit); + assertEquals(5, input.readRawByte()); + } + + public void testReadHugeBlob() throws Exception { + // Allocate and initialize a 1MB blob. + byte[] blob = new byte[1 << 20]; + for (int i = 0; i < blob.length; i++) { + blob[i] = (byte) i; + } + + // Make a message containing it. + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TestUtil.setAllFields(builder); + builder.setOptionalBytes(ByteString.copyFrom(blob)); + TestAllTypes message = builder.build(); + + // Serialize and parse it. Make sure to parse from an InputStream, not + // directly from a ByteString, so that CodedInputStream uses buffered + // reading. + TestAllTypes message2 = + TestAllTypes.parseFrom(message.toByteString().newInput()); + + assertEquals(message.getOptionalBytes(), message2.getOptionalBytes()); + + // Make sure all the other fields were parsed correctly. + TestAllTypes message3 = TestAllTypes.newBuilder(message2) + .setOptionalBytes(TestUtil.getAllSet().getOptionalBytes()) + .build(); + TestUtil.assertAllFieldsSet(message3); + } + + public void testReadMaliciouslyLargeBlob() throws Exception { + ByteString.Output rawOutput = ByteString.newOutput(); + CodedOutputStream output = CodedOutputStream.newInstance(rawOutput); + + int tag = WireFormat.makeTag(1, WireFormat.WIRETYPE_LENGTH_DELIMITED); + output.writeRawVarint32(tag); + output.writeRawVarint32(0x7FFFFFFF); + output.writeRawBytes(new byte[32]); // Pad with a few random bytes. + output.flush(); + + CodedInputStream input = rawOutput.toByteString().newCodedInput(); + assertEquals(tag, input.readTag()); + + try { + input.readBytes(); + fail("Should have thrown an exception!"); + } catch (InvalidProtocolBufferException e) { + // success. + } + } + + private TestRecursiveMessage makeRecursiveMessage(int depth) { + if (depth == 0) { + return TestRecursiveMessage.newBuilder().setI(5).build(); + } else { + return TestRecursiveMessage.newBuilder() + .setA(makeRecursiveMessage(depth - 1)).build(); + } + } + + private void assertMessageDepth(TestRecursiveMessage message, int depth) { + if (depth == 0) { + assertFalse(message.hasA()); + assertEquals(5, message.getI()); + } else { + assertTrue(message.hasA()); + assertMessageDepth(message.getA(), depth - 1); + } + } + + public void testMaliciousRecursion() throws Exception { + ByteString data100 = makeRecursiveMessage(100).toByteString(); + ByteString data101 = makeRecursiveMessage(101).toByteString(); + + assertMessageDepth(TestRecursiveMessage.parseFrom(data100), 100); + + try { + TestRecursiveMessage.parseFrom(data101); + fail("Should have thrown an exception!"); + } catch (InvalidProtocolBufferException e) { + // success. + } + + CodedInputStream input = data100.newCodedInput(); + input.setRecursionLimit(8); + try { + TestRecursiveMessage.parseFrom(input); + fail("Should have thrown an exception!"); + } catch (InvalidProtocolBufferException e) { + // success. + } + } + + private void checkSizeLimitExceeded(InvalidProtocolBufferException e) { + assertEquals( + InvalidProtocolBufferException.sizeLimitExceeded().getMessage(), + e.getMessage()); + } + + public void testSizeLimit() throws Exception { + CodedInputStream input = CodedInputStream.newInstance( + new SmallBlockInputStream( + TestUtil.getAllSet().toByteString().newInput(), 16)); + input.setSizeLimit(16); + + try { + TestAllTypes.parseFrom(input); + fail("Should have thrown an exception!"); + } catch (InvalidProtocolBufferException expected) { + checkSizeLimitExceeded(expected); + } + } + + public void testResetSizeCounter() throws Exception { + CodedInputStream input = CodedInputStream.newInstance( + new SmallBlockInputStream(new byte[256], 8)); + input.setSizeLimit(16); + input.readRawBytes(16); + assertEquals(16, input.getTotalBytesRead()); + + try { + input.readRawByte(); + fail("Should have thrown an exception!"); + } catch (InvalidProtocolBufferException expected) { + checkSizeLimitExceeded(expected); + } + + input.resetSizeCounter(); + assertEquals(0, input.getTotalBytesRead()); + input.readRawByte(); // No exception thrown. + input.resetSizeCounter(); + assertEquals(0, input.getTotalBytesRead()); + input.readRawBytes(16); + assertEquals(16, input.getTotalBytesRead()); + input.resetSizeCounter(); + + try { + input.readRawBytes(17); // Hits limit again. + fail("Should have thrown an exception!"); + } catch (InvalidProtocolBufferException expected) { + checkSizeLimitExceeded(expected); + } + } + + public void testSizeLimitMultipleMessages() throws Exception { + byte[] bytes = new byte[256]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = (byte) i; + } + CodedInputStream input = CodedInputStream.newInstance( + new SmallBlockInputStream(bytes, 7)); + input.setSizeLimit(16); + for (int i = 0; i < 256 / 16; i++) { + byte[] message = input.readRawBytes(16); + for (int j = 0; j < message.length; j++) { + assertEquals(i * 16 + j, message[j] & 0xff); + } + assertEquals(16, input.getTotalBytesRead()); + input.resetSizeCounter(); + assertEquals(0, input.getTotalBytesRead()); + } + } + + /** + * Tests that if we readString invalid UTF-8 bytes, no exception + * is thrown. Instead, the invalid bytes are replaced with the Unicode + * "replacement character" U+FFFD. + */ + public void testReadStringInvalidUtf8() throws Exception { + ByteString.Output rawOutput = ByteString.newOutput(); + CodedOutputStream output = CodedOutputStream.newInstance(rawOutput); + + int tag = WireFormat.makeTag(1, WireFormat.WIRETYPE_LENGTH_DELIMITED); + output.writeRawVarint32(tag); + output.writeRawVarint32(1); + output.writeRawBytes(new byte[] { (byte) 0x80 }); + output.flush(); + + CodedInputStream input = rawOutput.toByteString().newCodedInput(); + assertEquals(tag, input.readTag()); + String text = input.readString(); + assertEquals(0xfffd, text.charAt(0)); + } + + /** + * Tests that if we readStringRequireUtf8 invalid UTF-8 bytes, an + * InvalidProtocolBufferException is thrown. + */ + public void testReadStringRequireUtf8InvalidUtf8() throws Exception { + ByteString.Output rawOutput = ByteString.newOutput(); + CodedOutputStream output = CodedOutputStream.newInstance(rawOutput); + + int tag = WireFormat.makeTag(1, WireFormat.WIRETYPE_LENGTH_DELIMITED); + output.writeRawVarint32(tag); + output.writeRawVarint32(1); + output.writeRawBytes(new byte[] { (byte) 0x80 }); + output.flush(); + + CodedInputStream input = rawOutput.toByteString().newCodedInput(); + assertEquals(tag, input.readTag()); + try { + input.readStringRequireUtf8(); + fail("Expected invalid UTF-8 exception."); + } catch (InvalidProtocolBufferException exception) { + assertEquals("Protocol message had invalid UTF-8.", exception.getMessage()); + } + } + + public void testReadFromSlice() throws Exception { + byte[] bytes = bytes(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); + CodedInputStream in = CodedInputStream.newInstance(bytes, 3, 5); + assertEquals(0, in.getTotalBytesRead()); + for (int i = 3; i < 8; i++) { + assertEquals(i, in.readRawByte()); + assertEquals(i - 2, in.getTotalBytesRead()); + } + // eof + assertEquals(0, in.readTag()); + assertEquals(5, in.getTotalBytesRead()); + } + + public void testInvalidTag() throws Exception { + // Any tag number which corresponds to field number zero is invalid and + // should throw InvalidProtocolBufferException. + for (int i = 0; i < 8; i++) { + try { + CodedInputStream.newInstance(bytes(i)).readTag(); + fail("Should have thrown an exception."); + } catch (InvalidProtocolBufferException e) { + assertEquals(InvalidProtocolBufferException.invalidTag().getMessage(), + e.getMessage()); + } + } + } + + public void testReadByteArray() throws Exception { + ByteString.Output rawOutput = ByteString.newOutput(); + CodedOutputStream output = CodedOutputStream.newInstance(rawOutput); + // Zero-sized bytes field. + output.writeRawVarint32(0); + // One one-byte bytes field + output.writeRawVarint32(1); + output.writeRawBytes(new byte[] { (byte) 23 }); + // Another one-byte bytes field + output.writeRawVarint32(1); + output.writeRawBytes(new byte[] { (byte) 45 }); + // A bytes field large enough that won't fit into the 4K buffer. + final int bytesLength = 16 * 1024; + byte[] bytes = new byte[bytesLength]; + bytes[0] = (byte) 67; + bytes[bytesLength - 1] = (byte) 89; + output.writeRawVarint32(bytesLength); + output.writeRawBytes(bytes); + + output.flush(); + CodedInputStream inputStream = rawOutput.toByteString().newCodedInput(); + + byte[] result = inputStream.readByteArray(); + assertEquals(0, result.length); + result = inputStream.readByteArray(); + assertEquals(1, result.length); + assertEquals((byte) 23, result[0]); + result = inputStream.readByteArray(); + assertEquals(1, result.length); + assertEquals((byte) 45, result[0]); + result = inputStream.readByteArray(); + assertEquals(bytesLength, result.length); + assertEquals((byte) 67, result[0]); + assertEquals((byte) 89, result[bytesLength - 1]); + } + + public void testReadByteBuffer() throws Exception { + ByteString.Output rawOutput = ByteString.newOutput(); + CodedOutputStream output = CodedOutputStream.newInstance(rawOutput); + // Zero-sized bytes field. + output.writeRawVarint32(0); + // One one-byte bytes field + output.writeRawVarint32(1); + output.writeRawBytes(new byte[]{(byte) 23}); + // Another one-byte bytes field + output.writeRawVarint32(1); + output.writeRawBytes(new byte[]{(byte) 45}); + // A bytes field large enough that won't fit into the 4K buffer. + final int bytesLength = 16 * 1024; + byte[] bytes = new byte[bytesLength]; + bytes[0] = (byte) 67; + bytes[bytesLength - 1] = (byte) 89; + output.writeRawVarint32(bytesLength); + output.writeRawBytes(bytes); + + output.flush(); + CodedInputStream inputStream = rawOutput.toByteString().newCodedInput(); + + ByteBuffer result = inputStream.readByteBuffer(); + assertEquals(0, result.capacity()); + result = inputStream.readByteBuffer(); + assertEquals(1, result.capacity()); + assertEquals((byte) 23, result.get()); + result = inputStream.readByteBuffer(); + assertEquals(1, result.capacity()); + assertEquals((byte) 45, result.get()); + result = inputStream.readByteBuffer(); + assertEquals(bytesLength, result.capacity()); + assertEquals((byte) 67, result.get()); + result.position(bytesLength - 1); + assertEquals((byte) 89, result.get()); + } + + public void testReadByteBufferAliasing() throws Exception { + ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream(); + CodedOutputStream output = CodedOutputStream.newInstance(byteArrayStream); + // Zero-sized bytes field. + output.writeRawVarint32(0); + // One one-byte bytes field + output.writeRawVarint32(1); + output.writeRawBytes(new byte[]{(byte) 23}); + // Another one-byte bytes field + output.writeRawVarint32(1); + output.writeRawBytes(new byte[]{(byte) 45}); + // A bytes field large enough that won't fit into the 4K buffer. + final int bytesLength = 16 * 1024; + byte[] bytes = new byte[bytesLength]; + bytes[0] = (byte) 67; + bytes[bytesLength - 1] = (byte) 89; + output.writeRawVarint32(bytesLength); + output.writeRawBytes(bytes); + output.flush(); + byte[] data = byteArrayStream.toByteArray(); + + // Without aliasing + CodedInputStream inputStream = CodedInputStream.newInstance(data); + ByteBuffer result = inputStream.readByteBuffer(); + assertEquals(0, result.capacity()); + result = inputStream.readByteBuffer(); + assertTrue(result.array() != data); + assertEquals(1, result.capacity()); + assertEquals((byte) 23, result.get()); + result = inputStream.readByteBuffer(); + assertTrue(result.array() != data); + assertEquals(1, result.capacity()); + assertEquals((byte) 45, result.get()); + result = inputStream.readByteBuffer(); + assertTrue(result.array() != data); + assertEquals(bytesLength, result.capacity()); + assertEquals((byte) 67, result.get()); + result.position(bytesLength - 1); + assertEquals((byte) 89, result.get()); + + // Enable aliasing + inputStream = CodedInputStream.newInstance(data); + inputStream.enableAliasing(true); + result = inputStream.readByteBuffer(); + assertEquals(0, result.capacity()); + result = inputStream.readByteBuffer(); + assertTrue(result.array() == data); + assertEquals(1, result.capacity()); + assertEquals((byte) 23, result.get()); + result = inputStream.readByteBuffer(); + assertTrue(result.array() == data); + assertEquals(1, result.capacity()); + assertEquals((byte) 45, result.get()); + result = inputStream.readByteBuffer(); + assertTrue(result.array() == data); + assertEquals(bytesLength, result.capacity()); + assertEquals((byte) 67, result.get()); + result.position(bytesLength - 1); + assertEquals((byte) 89, result.get()); + } + + public void testCompatibleTypes() throws Exception { + long data = 0x100000000L; + Int64Message message = Int64Message.newBuilder().setData(data).build(); + ByteString serialized = message.toByteString(); + + // Test int64(long) is compatible with bool(boolean) + BoolMessage msg2 = BoolMessage.parseFrom(serialized); + assertTrue(msg2.getData()); + + // Test int64(long) is compatible with int32(int) + Int32Message msg3 = Int32Message.parseFrom(serialized); + assertEquals((int) data, msg3.getData()); + } +} diff --git a/java/core/src/test/java/com/google/protobuf/CodedOutputStreamTest.java b/java/core/src/test/java/com/google/protobuf/CodedOutputStreamTest.java new file mode 100644 index 00000000..6018ea55 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/CodedOutputStreamTest.java @@ -0,0 +1,546 @@ +// 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.UnittestProto.SparseEnumMessage; +import protobuf_unittest.UnittestProto.TestAllTypes; +import protobuf_unittest.UnittestProto.TestPackedTypes; +import protobuf_unittest.UnittestProto.TestSparseEnum; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Unit test for {@link CodedOutputStream}. + * + * @author kenton@google.com Kenton Varda + */ +public class CodedOutputStreamTest extends TestCase { + /** + * Helper to construct a byte array from a bunch of bytes. The inputs are + * actually ints so that I can use hex notation and not get stupid errors + * about precision. + */ + private byte[] bytes(int... bytesAsInts) { + byte[] bytes = new byte[bytesAsInts.length]; + for (int i = 0; i < bytesAsInts.length; i++) { + bytes[i] = (byte) bytesAsInts[i]; + } + return bytes; + } + + /** Arrays.asList() does not work with arrays of primitives. :( */ + private List<Byte> toList(byte[] bytes) { + List<Byte> result = new ArrayList<Byte>(); + for (byte b : bytes) { + result.add(b); + } + return result; + } + + private void assertEqualBytes(byte[] a, byte[] b) { + assertEquals(toList(a), toList(b)); + } + + /** + * Writes the given value using writeRawVarint32() and writeRawVarint64() and + * checks that the result matches the given bytes. + */ + private void assertWriteVarint(byte[] data, long value) throws Exception { + // Only test 32-bit write if the value fits into an int. + if (value == (int) value) { + ByteArrayOutputStream rawOutput = new ByteArrayOutputStream(); + CodedOutputStream output = CodedOutputStream.newInstance(rawOutput); + output.writeRawVarint32((int) value); + output.flush(); + assertEqualBytes(data, rawOutput.toByteArray()); + + // Also try computing size. + assertEquals(data.length, + CodedOutputStream.computeRawVarint32Size((int) value)); + } + + { + ByteArrayOutputStream rawOutput = new ByteArrayOutputStream(); + CodedOutputStream output = CodedOutputStream.newInstance(rawOutput); + output.writeRawVarint64(value); + output.flush(); + assertEqualBytes(data, rawOutput.toByteArray()); + + // Also try computing size. + assertEquals(data.length, + CodedOutputStream.computeRawVarint64Size(value)); + } + + // Try different block sizes. + for (int blockSize = 1; blockSize <= 16; blockSize *= 2) { + // Only test 32-bit write if the value fits into an int. + if (value == (int) value) { + ByteArrayOutputStream rawOutput = new ByteArrayOutputStream(); + CodedOutputStream output = + CodedOutputStream.newInstance(rawOutput, blockSize); + output.writeRawVarint32((int) value); + output.flush(); + assertEqualBytes(data, rawOutput.toByteArray()); + } + + { + ByteArrayOutputStream rawOutput = new ByteArrayOutputStream(); + CodedOutputStream output = + CodedOutputStream.newInstance(rawOutput, blockSize); + output.writeRawVarint64(value); + output.flush(); + assertEqualBytes(data, rawOutput.toByteArray()); + } + } + } + + private void assertVarintRoundTrip(long value) throws Exception { + { + ByteArrayOutputStream rawOutput = new ByteArrayOutputStream(); + CodedOutputStream output = CodedOutputStream.newInstance(rawOutput); + output.writeRawVarint64(value); + output.flush(); + byte[] bytes = rawOutput.toByteArray(); + assertEquals(bytes.length, CodedOutputStream.computeRawVarint64Size(value)); + CodedInputStream input = CodedInputStream.newInstance(new ByteArrayInputStream(bytes)); + assertEquals(value, input.readRawVarint64()); + } + + if (value == (int) value) { + ByteArrayOutputStream rawOutput = new ByteArrayOutputStream(); + CodedOutputStream output = CodedOutputStream.newInstance(rawOutput); + output.writeRawVarint32((int) value); + output.flush(); + byte[] bytes = rawOutput.toByteArray(); + assertEquals(bytes.length, CodedOutputStream.computeRawVarint32Size((int) value)); + CodedInputStream input = CodedInputStream.newInstance(new ByteArrayInputStream(bytes)); + assertEquals(value, input.readRawVarint32()); + } + } + + /** Checks that invariants are maintained for varint round trip input and output. */ + public void testVarintRoundTrips() throws Exception { + assertVarintRoundTrip(0L); + for (int bits = 0; bits < 64; bits++) { + long value = 1L << bits; + assertVarintRoundTrip(value); + assertVarintRoundTrip(value + 1); + assertVarintRoundTrip(value - 1); + assertVarintRoundTrip(-value); + } + } + + /** Tests writeRawVarint32() and writeRawVarint64(). */ + public void testWriteVarint() throws Exception { + assertWriteVarint(bytes(0x00), 0); + assertWriteVarint(bytes(0x01), 1); + assertWriteVarint(bytes(0x7f), 127); + // 14882 + assertWriteVarint(bytes(0xa2, 0x74), (0x22 << 0) | (0x74 << 7)); + // 2961488830 + assertWriteVarint(bytes(0xbe, 0xf7, 0x92, 0x84, 0x0b), + (0x3e << 0) | (0x77 << 7) | (0x12 << 14) | (0x04 << 21) | + (0x0bL << 28)); + + // 64-bit + // 7256456126 + assertWriteVarint(bytes(0xbe, 0xf7, 0x92, 0x84, 0x1b), + (0x3e << 0) | (0x77 << 7) | (0x12 << 14) | (0x04 << 21) | + (0x1bL << 28)); + // 41256202580718336 + assertWriteVarint( + bytes(0x80, 0xe6, 0xeb, 0x9c, 0xc3, 0xc9, 0xa4, 0x49), + (0x00 << 0) | (0x66 << 7) | (0x6b << 14) | (0x1c << 21) | + (0x43L << 28) | (0x49L << 35) | (0x24L << 42) | (0x49L << 49)); + // 11964378330978735131 + assertWriteVarint( + bytes(0x9b, 0xa8, 0xf9, 0xc2, 0xbb, 0xd6, 0x80, 0x85, 0xa6, 0x01), + (0x1b << 0) | (0x28 << 7) | (0x79 << 14) | (0x42 << 21) | + (0x3bL << 28) | (0x56L << 35) | (0x00L << 42) | + (0x05L << 49) | (0x26L << 56) | (0x01L << 63)); + } + + /** + * Parses the given bytes using writeRawLittleEndian32() and checks + * that the result matches the given value. + */ + private void assertWriteLittleEndian32(byte[] data, int value) + throws Exception { + ByteArrayOutputStream rawOutput = new ByteArrayOutputStream(); + CodedOutputStream output = CodedOutputStream.newInstance(rawOutput); + output.writeRawLittleEndian32(value); + output.flush(); + assertEqualBytes(data, rawOutput.toByteArray()); + + // Try different block sizes. + for (int blockSize = 1; blockSize <= 16; blockSize *= 2) { + rawOutput = new ByteArrayOutputStream(); + output = CodedOutputStream.newInstance(rawOutput, blockSize); + output.writeRawLittleEndian32(value); + output.flush(); + assertEqualBytes(data, rawOutput.toByteArray()); + } + } + + /** + * Parses the given bytes using writeRawLittleEndian64() and checks + * that the result matches the given value. + */ + private void assertWriteLittleEndian64(byte[] data, long value) + throws Exception { + ByteArrayOutputStream rawOutput = new ByteArrayOutputStream(); + CodedOutputStream output = CodedOutputStream.newInstance(rawOutput); + output.writeRawLittleEndian64(value); + output.flush(); + assertEqualBytes(data, rawOutput.toByteArray()); + + // Try different block sizes. + for (int blockSize = 1; blockSize <= 16; blockSize *= 2) { + rawOutput = new ByteArrayOutputStream(); + output = CodedOutputStream.newInstance(rawOutput, blockSize); + output.writeRawLittleEndian64(value); + output.flush(); + assertEqualBytes(data, rawOutput.toByteArray()); + } + } + + /** Tests writeRawLittleEndian32() and writeRawLittleEndian64(). */ + public void testWriteLittleEndian() throws Exception { + assertWriteLittleEndian32(bytes(0x78, 0x56, 0x34, 0x12), 0x12345678); + assertWriteLittleEndian32(bytes(0xf0, 0xde, 0xbc, 0x9a), 0x9abcdef0); + + assertWriteLittleEndian64( + bytes(0xf0, 0xde, 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12), + 0x123456789abcdef0L); + assertWriteLittleEndian64( + bytes(0x78, 0x56, 0x34, 0x12, 0xf0, 0xde, 0xbc, 0x9a), + 0x9abcdef012345678L); + } + + /** Test encodeZigZag32() and encodeZigZag64(). */ + public void testEncodeZigZag() throws Exception { + assertEquals(0, CodedOutputStream.encodeZigZag32( 0)); + assertEquals(1, CodedOutputStream.encodeZigZag32(-1)); + assertEquals(2, CodedOutputStream.encodeZigZag32( 1)); + assertEquals(3, CodedOutputStream.encodeZigZag32(-2)); + assertEquals(0x7FFFFFFE, CodedOutputStream.encodeZigZag32(0x3FFFFFFF)); + assertEquals(0x7FFFFFFF, CodedOutputStream.encodeZigZag32(0xC0000000)); + assertEquals(0xFFFFFFFE, CodedOutputStream.encodeZigZag32(0x7FFFFFFF)); + assertEquals(0xFFFFFFFF, CodedOutputStream.encodeZigZag32(0x80000000)); + + assertEquals(0, CodedOutputStream.encodeZigZag64( 0)); + assertEquals(1, CodedOutputStream.encodeZigZag64(-1)); + assertEquals(2, CodedOutputStream.encodeZigZag64( 1)); + assertEquals(3, CodedOutputStream.encodeZigZag64(-2)); + assertEquals(0x000000007FFFFFFEL, + CodedOutputStream.encodeZigZag64(0x000000003FFFFFFFL)); + assertEquals(0x000000007FFFFFFFL, + CodedOutputStream.encodeZigZag64(0xFFFFFFFFC0000000L)); + assertEquals(0x00000000FFFFFFFEL, + CodedOutputStream.encodeZigZag64(0x000000007FFFFFFFL)); + assertEquals(0x00000000FFFFFFFFL, + CodedOutputStream.encodeZigZag64(0xFFFFFFFF80000000L)); + assertEquals(0xFFFFFFFFFFFFFFFEL, + CodedOutputStream.encodeZigZag64(0x7FFFFFFFFFFFFFFFL)); + assertEquals(0xFFFFFFFFFFFFFFFFL, + CodedOutputStream.encodeZigZag64(0x8000000000000000L)); + + // Some easier-to-verify round-trip tests. The inputs (other than 0, 1, -1) + // were chosen semi-randomly via keyboard bashing. + assertEquals(0, + CodedOutputStream.encodeZigZag32(CodedInputStream.decodeZigZag32(0))); + assertEquals(1, + CodedOutputStream.encodeZigZag32(CodedInputStream.decodeZigZag32(1))); + assertEquals(-1, + CodedOutputStream.encodeZigZag32(CodedInputStream.decodeZigZag32(-1))); + assertEquals(14927, + CodedOutputStream.encodeZigZag32(CodedInputStream.decodeZigZag32(14927))); + assertEquals(-3612, + CodedOutputStream.encodeZigZag32(CodedInputStream.decodeZigZag32(-3612))); + + assertEquals(0, + CodedOutputStream.encodeZigZag64(CodedInputStream.decodeZigZag64(0))); + assertEquals(1, + CodedOutputStream.encodeZigZag64(CodedInputStream.decodeZigZag64(1))); + assertEquals(-1, + CodedOutputStream.encodeZigZag64(CodedInputStream.decodeZigZag64(-1))); + assertEquals(14927, + CodedOutputStream.encodeZigZag64(CodedInputStream.decodeZigZag64(14927))); + assertEquals(-3612, + CodedOutputStream.encodeZigZag64(CodedInputStream.decodeZigZag64(-3612))); + + assertEquals(856912304801416L, + CodedOutputStream.encodeZigZag64( + CodedInputStream.decodeZigZag64( + 856912304801416L))); + assertEquals(-75123905439571256L, + CodedOutputStream.encodeZigZag64( + CodedInputStream.decodeZigZag64( + -75123905439571256L))); + } + + /** Tests writing a whole message with every field type. */ + public void testWriteWholeMessage() throws Exception { + TestAllTypes message = TestUtil.getAllSet(); + + byte[] rawBytes = message.toByteArray(); + assertEqualBytes(TestUtil.getGoldenMessage().toByteArray(), rawBytes); + + // Try different block sizes. + for (int blockSize = 1; blockSize < 256; blockSize *= 2) { + ByteArrayOutputStream rawOutput = new ByteArrayOutputStream(); + CodedOutputStream output = + CodedOutputStream.newInstance(rawOutput, blockSize); + message.writeTo(output); + output.flush(); + assertEqualBytes(rawBytes, rawOutput.toByteArray()); + } + } + + /** Tests writing a whole message with every packed field type. Ensures the + * wire format of packed fields is compatible with C++. */ + public void testWriteWholePackedFieldsMessage() throws Exception { + TestPackedTypes message = TestUtil.getPackedSet(); + + byte[] rawBytes = message.toByteArray(); + assertEqualBytes(TestUtil.getGoldenPackedFieldsMessage().toByteArray(), + rawBytes); + } + + /** Test writing a message containing a negative enum value. This used to + * fail because the size was not properly computed as a sign-extended varint. + */ + public void testWriteMessageWithNegativeEnumValue() throws Exception { + SparseEnumMessage message = SparseEnumMessage.newBuilder() + .setSparseEnum(TestSparseEnum.SPARSE_E) .build(); + assertTrue(message.getSparseEnum().getNumber() < 0); + byte[] rawBytes = message.toByteArray(); + SparseEnumMessage message2 = SparseEnumMessage.parseFrom(rawBytes); + assertEquals(TestSparseEnum.SPARSE_E, message2.getSparseEnum()); + } + + /** Test getTotalBytesWritten() */ + public void testGetTotalBytesWritten() throws Exception { + final int BUFFER_SIZE = 4 * 1024; + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(BUFFER_SIZE); + CodedOutputStream codedStream = CodedOutputStream.newInstance(outputStream); + byte[] value = "abcde".getBytes(Internal.UTF_8); + for (int i = 0; i < 1024; ++i) { + codedStream.writeRawBytes(value, 0, value.length); + } + String string = + "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"; + // Ensure we take the slower fast path. + assertTrue(CodedOutputStream.computeRawVarint32Size(string.length()) + != CodedOutputStream.computeRawVarint32Size(string.length() * Utf8.MAX_BYTES_PER_CHAR)); + + codedStream.writeStringNoTag(string); + int stringSize = CodedOutputStream.computeStringSizeNoTag(string); + + // Make sure we have written more bytes than the buffer could hold. This is + // to make the test complete. + assertTrue(codedStream.getTotalBytesWritten() > BUFFER_SIZE); + + // Verify that the total bytes written is correct + assertEquals((value.length * 1024) + stringSize, codedStream.getTotalBytesWritten()); + } + + // TODO(dweis): Write a comprehensive test suite for CodedOutputStream that covers more than just + // this case. + public void testWriteStringNoTag_fastpath() throws Exception { + int bufferSize = 153; + String threeBytesPer = "\u0981"; + String string = threeBytesPer; + for (int i = 0; i < 50; i++) { + string += threeBytesPer; + } + // These checks ensure we will tickle the slower fast path. + assertEquals(1, CodedOutputStream.computeRawVarint32Size(string.length())); + assertEquals( + 2, CodedOutputStream.computeRawVarint32Size(string.length() * Utf8.MAX_BYTES_PER_CHAR)); + assertEquals(bufferSize, string.length() * Utf8.MAX_BYTES_PER_CHAR); + + CodedOutputStream output = + CodedOutputStream.newInstance(ByteBuffer.allocate(bufferSize), bufferSize); + output.writeStringNoTag(string); + } + + public void testWriteToByteBuffer() throws Exception { + final int bufferSize = 16 * 1024; + ByteBuffer buffer = ByteBuffer.allocate(bufferSize); + CodedOutputStream codedStream = CodedOutputStream.newInstance(buffer); + // Write raw bytes into the ByteBuffer. + final int length1 = 5000; + for (int i = 0; i < length1; i++) { + codedStream.writeRawByte((byte) 1); + } + final int length2 = 8 * 1024; + byte[] data = new byte[length2]; + for (int i = 0; i < length2; i++) { + data[i] = (byte) 2; + } + codedStream.writeRawBytes(data); + final int length3 = bufferSize - length1 - length2; + for (int i = 0; i < length3; i++) { + codedStream.writeRawByte((byte) 3); + } + codedStream.flush(); + + // Check that data is correctly written to the ByteBuffer. + assertEquals(0, buffer.remaining()); + buffer.flip(); + for (int i = 0; i < length1; i++) { + assertEquals((byte) 1, buffer.get()); + } + for (int i = 0; i < length2; i++) { + assertEquals((byte) 2, buffer.get()); + } + for (int i = 0; i < length3; i++) { + assertEquals((byte) 3, buffer.get()); + } + } + + public void testWriteByteBuffer() throws Exception { + byte[] value = "abcde".getBytes(Internal.UTF_8); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + CodedOutputStream codedStream = CodedOutputStream.newInstance(outputStream); + ByteBuffer byteBuffer = ByteBuffer.wrap(value, 0, 1); + // This will actually write 5 bytes into the CodedOutputStream as the + // ByteBuffer's capacity() is 5. + codedStream.writeRawBytes(byteBuffer); + // The above call shouldn't affect the ByteBuffer's state. + assertEquals(0, byteBuffer.position()); + assertEquals(1, byteBuffer.limit()); + + // The correct way to write part of an array using ByteBuffer. + codedStream.writeRawBytes(ByteBuffer.wrap(value, 2, 1).slice()); + + codedStream.flush(); + byte[] result = outputStream.toByteArray(); + assertEquals(6, result.length); + for (int i = 0; i < 5; i++) { + assertEquals(value[i], result[i]); + } + assertEquals(value[2], result[5]); + } + + public void testWriteByteArrayWithOffsets() throws Exception { + byte[] fullArray = bytes(0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88); + byte[] destination = new byte[4]; + CodedOutputStream codedStream = CodedOutputStream.newInstance(destination); + codedStream.writeByteArrayNoTag(fullArray, 2, 2); + assertEqualBytes(bytes(0x02, 0x33, 0x44, 0x00), destination); + assertEquals(3, codedStream.getTotalBytesWritten()); + } + + public void testSerializeInvalidUtf8() throws Exception { + String[] invalidStrings = new String[] { + newString(Character.MIN_HIGH_SURROGATE), + "foobar" + newString(Character.MIN_HIGH_SURROGATE), + newString(Character.MIN_LOW_SURROGATE), + "foobar" + newString(Character.MIN_LOW_SURROGATE), + newString(Character.MIN_HIGH_SURROGATE, Character.MIN_HIGH_SURROGATE) + }; + + CodedOutputStream outputWithStream = CodedOutputStream.newInstance(new ByteArrayOutputStream()); + CodedOutputStream outputWithArray = CodedOutputStream.newInstance(new byte[10000]); + for (String s : invalidStrings) { + // TODO(dweis): These should all fail; instead they are corrupting data. + CodedOutputStream.computeStringSizeNoTag(s); + outputWithStream.writeStringNoTag(s); + outputWithArray.writeStringNoTag(s); + } + } + + private static String newString(char... chars) { + return new String(chars); + } + + /** Regression test for https://github.com/google/protobuf/issues/292 */ + public void testCorrectExceptionThrowWhenEncodingStringsWithoutEnoughSpace() throws Exception { + String testCase = "Foooooooo"; + assertEquals(CodedOutputStream.computeRawVarint32Size(testCase.length()), + CodedOutputStream.computeRawVarint32Size(testCase.length() * 3)); + assertEquals(11, CodedOutputStream.computeStringSize(1, testCase)); + // Tag is one byte, varint describing string length is 1 byte, string length is 9 bytes. + // An array of size 1 will cause a failure when trying to write the varint. + for (int i = 0; i < 11; i++) { + CodedOutputStream output = CodedOutputStream.newInstance(new byte[i]); + try { + output.writeString(1, testCase); + fail("Should have thrown an out of space exception"); + } catch (CodedOutputStream.OutOfSpaceException expected) {} + } + } + + public void testDifferentStringLengths() throws Exception { + // Test string serialization roundtrip using strings of the following lengths, + // with ASCII and Unicode characters requiring different UTF-8 byte counts per + // char, hence causing the length delimiter varint to sometimes require more + // bytes for the Unicode strings than the ASCII string of the same length. + int[] lengths = new int[] { + 0, + 1, + (1 << 4) - 1, // 1 byte for ASCII and Unicode + (1 << 7) - 1, // 1 byte for ASCII, 2 bytes for Unicode + (1 << 11) - 1, // 2 bytes for ASCII and Unicode + (1 << 14) - 1, // 2 bytes for ASCII, 3 bytes for Unicode + (1 << 17) - 1, // 3 bytes for ASCII and Unicode + }; + for (int i : lengths) { + testEncodingOfString('q', i); // 1 byte per char + testEncodingOfString('\u07FF', i); // 2 bytes per char + testEncodingOfString('\u0981', i); // 3 bytes per char + } + } + + private void testEncodingOfString(char c, int length) throws Exception { + String fullString = fullString(c, length); + TestAllTypes testAllTypes = TestAllTypes.newBuilder() + .setOptionalString(fullString) + .build(); + assertEquals( + fullString, TestAllTypes.parseFrom(testAllTypes.toByteArray()).getOptionalString()); + } + + private String fullString(char c, int length) { + char[] result = new char[length]; + Arrays.fill(result, c); + return new String(result); + } +} diff --git a/java/core/src/test/java/com/google/protobuf/DeprecatedFieldTest.java b/java/core/src/test/java/com/google/protobuf/DeprecatedFieldTest.java new file mode 100644 index 00000000..e7905f79 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/DeprecatedFieldTest.java @@ -0,0 +1,80 @@ +// 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.UnittestProto.TestDeprecatedFields; + +import junit.framework.TestCase; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +/** + * Test field deprecation + * + * @author birdo@google.com (Roberto Scaramuzzi) + */ +public class DeprecatedFieldTest extends TestCase { + private String[] deprecatedGetterNames = { + "hasDeprecatedInt32", + "getDeprecatedInt32"}; + + private String[] deprecatedBuilderGetterNames = { + "hasDeprecatedInt32", + "getDeprecatedInt32", + "clearDeprecatedInt32"}; + + private String[] deprecatedBuilderSetterNames = { + "setDeprecatedInt32"}; + + public void testDeprecatedField() throws Exception { + Class<?> deprecatedFields = TestDeprecatedFields.class; + Class<?> deprecatedFieldsBuilder = TestDeprecatedFields.Builder.class; + for (String name : deprecatedGetterNames) { + Method method = deprecatedFields.getMethod(name); + assertTrue("Method " + name + " should be deprecated", + isDeprecated(method)); + } + for (String name : deprecatedBuilderGetterNames) { + Method method = deprecatedFieldsBuilder.getMethod(name); + assertTrue("Method " + name + " should be deprecated", + isDeprecated(method)); + } + for (String name : deprecatedBuilderSetterNames) { + Method method = deprecatedFieldsBuilder.getMethod(name, int.class); + assertTrue("Method " + name + " should be deprecated", + isDeprecated(method)); + } + } + + private boolean isDeprecated(AnnotatedElement annotated) { + return annotated.isAnnotationPresent(Deprecated.class); + } +} diff --git a/java/core/src/test/java/com/google/protobuf/DescriptorsTest.java b/java/core/src/test/java/com/google/protobuf/DescriptorsTest.java new file mode 100644 index 00000000..82ff34af --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/DescriptorsTest.java @@ -0,0 +1,765 @@ +// 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.DescriptorProtos.DescriptorProto; +import com.google.protobuf.DescriptorProtos.EnumDescriptorProto; +import com.google.protobuf.DescriptorProtos.EnumValueDescriptorProto; +import com.google.protobuf.DescriptorProtos.FieldDescriptorProto; +import com.google.protobuf.DescriptorProtos.FileDescriptorProto; +import com.google.protobuf.Descriptors.Descriptor; +import com.google.protobuf.Descriptors.DescriptorValidationException; +import com.google.protobuf.Descriptors.EnumDescriptor; +import com.google.protobuf.Descriptors.EnumValueDescriptor; +import com.google.protobuf.Descriptors.FieldDescriptor; +import com.google.protobuf.Descriptors.FileDescriptor; +import com.google.protobuf.Descriptors.MethodDescriptor; +import com.google.protobuf.Descriptors.OneofDescriptor; +import com.google.protobuf.Descriptors.ServiceDescriptor; +import com.google.protobuf.test.UnittestImport; +import com.google.protobuf.test.UnittestImport.ImportEnum; +import com.google.protobuf.test.UnittestImport.ImportEnumForMap; +import protobuf_unittest.TestCustomOptions; +import protobuf_unittest.UnittestCustomOptions; +import protobuf_unittest.UnittestProto; +import protobuf_unittest.UnittestProto.ForeignEnum; +import protobuf_unittest.UnittestProto.ForeignMessage; +import protobuf_unittest.UnittestProto.TestAllExtensions; +import protobuf_unittest.UnittestProto.TestAllTypes; +import protobuf_unittest.UnittestProto.TestExtremeDefaultValues; +import protobuf_unittest.UnittestProto.TestMultipleExtensionRanges; +import protobuf_unittest.UnittestProto.TestRequired; +import protobuf_unittest.UnittestProto.TestReservedFields; +import protobuf_unittest.UnittestProto.TestService; + +import junit.framework.TestCase; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Unit test for {@link Descriptors}. + * + * @author kenton@google.com Kenton Varda + */ +public class DescriptorsTest extends TestCase { + + // Regression test for bug where referencing a FieldDescriptor.Type value + // before a FieldDescriptorProto.Type value would yield a + // ExceptionInInitializerError. + @SuppressWarnings("unused") + private static final Object STATIC_INIT_TEST = FieldDescriptor.Type.BOOL; + + public void testFieldTypeEnumMapping() throws Exception { + assertEquals(FieldDescriptor.Type.values().length, + FieldDescriptorProto.Type.values().length); + for (FieldDescriptor.Type type : FieldDescriptor.Type.values()) { + FieldDescriptorProto.Type protoType = type.toProto(); + assertEquals("TYPE_" + type.name(), protoType.name()); + assertEquals(type, FieldDescriptor.Type.valueOf(protoType)); + } + } + + public void testFileDescriptor() throws Exception { + FileDescriptor file = UnittestProto.getDescriptor(); + + assertEquals("google/protobuf/unittest.proto", file.getName()); + assertEquals("protobuf_unittest", file.getPackage()); + + assertEquals("UnittestProto", file.getOptions().getJavaOuterClassname()); + assertEquals("google/protobuf/unittest.proto", + file.toProto().getName()); + + assertEquals(Arrays.asList(UnittestImport.getDescriptor()), + file.getDependencies()); + + Descriptor messageType = TestAllTypes.getDescriptor(); + assertEquals(messageType, file.getMessageTypes().get(0)); + assertEquals(messageType, file.findMessageTypeByName("TestAllTypes")); + assertNull(file.findMessageTypeByName("NoSuchType")); + assertNull(file.findMessageTypeByName("protobuf_unittest.TestAllTypes")); + for (int i = 0; i < file.getMessageTypes().size(); i++) { + assertEquals(i, file.getMessageTypes().get(i).getIndex()); + } + + EnumDescriptor enumType = ForeignEnum.getDescriptor(); + assertEquals(enumType, file.getEnumTypes().get(0)); + assertEquals(enumType, file.findEnumTypeByName("ForeignEnum")); + assertNull(file.findEnumTypeByName("NoSuchType")); + assertNull(file.findEnumTypeByName("protobuf_unittest.ForeignEnum")); + assertEquals(Arrays.asList(ImportEnum.getDescriptor(), + ImportEnumForMap.getDescriptor()), + UnittestImport.getDescriptor().getEnumTypes()); + for (int i = 0; i < file.getEnumTypes().size(); i++) { + assertEquals(i, file.getEnumTypes().get(i).getIndex()); + } + + ServiceDescriptor service = TestService.getDescriptor(); + assertEquals(service, file.getServices().get(0)); + assertEquals(service, file.findServiceByName("TestService")); + assertNull(file.findServiceByName("NoSuchType")); + assertNull(file.findServiceByName("protobuf_unittest.TestService")); + assertEquals(Collections.emptyList(), + UnittestImport.getDescriptor().getServices()); + for (int i = 0; i < file.getServices().size(); i++) { + assertEquals(i, file.getServices().get(i).getIndex()); + } + + FieldDescriptor extension = + UnittestProto.optionalInt32Extension.getDescriptor(); + assertEquals(extension, file.getExtensions().get(0)); + assertEquals(extension, + file.findExtensionByName("optional_int32_extension")); + assertNull(file.findExtensionByName("no_such_ext")); + assertNull(file.findExtensionByName( + "protobuf_unittest.optional_int32_extension")); + assertEquals(Collections.emptyList(), + UnittestImport.getDescriptor().getExtensions()); + for (int i = 0; i < file.getExtensions().size(); i++) { + assertEquals(i, file.getExtensions().get(i).getIndex()); + } + } + + public void testDescriptor() throws Exception { + Descriptor messageType = TestAllTypes.getDescriptor(); + Descriptor nestedType = TestAllTypes.NestedMessage.getDescriptor(); + + assertEquals("TestAllTypes", messageType.getName()); + assertEquals("protobuf_unittest.TestAllTypes", messageType.getFullName()); + assertEquals(UnittestProto.getDescriptor(), messageType.getFile()); + assertNull(messageType.getContainingType()); + assertEquals(DescriptorProtos.MessageOptions.getDefaultInstance(), + messageType.getOptions()); + assertEquals("TestAllTypes", messageType.toProto().getName()); + + assertEquals("NestedMessage", nestedType.getName()); + assertEquals("protobuf_unittest.TestAllTypes.NestedMessage", + nestedType.getFullName()); + assertEquals(UnittestProto.getDescriptor(), nestedType.getFile()); + assertEquals(messageType, nestedType.getContainingType()); + + FieldDescriptor field = messageType.getFields().get(0); + assertEquals("optional_int32", field.getName()); + assertEquals(field, messageType.findFieldByName("optional_int32")); + assertNull(messageType.findFieldByName("no_such_field")); + assertEquals(field, messageType.findFieldByNumber(1)); + assertNull(messageType.findFieldByNumber(571283)); + for (int i = 0; i < messageType.getFields().size(); i++) { + assertEquals(i, messageType.getFields().get(i).getIndex()); + } + + assertEquals(nestedType, messageType.getNestedTypes().get(0)); + assertEquals(nestedType, messageType.findNestedTypeByName("NestedMessage")); + assertNull(messageType.findNestedTypeByName("NoSuchType")); + for (int i = 0; i < messageType.getNestedTypes().size(); i++) { + assertEquals(i, messageType.getNestedTypes().get(i).getIndex()); + } + + EnumDescriptor enumType = TestAllTypes.NestedEnum.getDescriptor(); + assertEquals(enumType, messageType.getEnumTypes().get(0)); + assertEquals(enumType, messageType.findEnumTypeByName("NestedEnum")); + assertNull(messageType.findEnumTypeByName("NoSuchType")); + for (int i = 0; i < messageType.getEnumTypes().size(); i++) { + assertEquals(i, messageType.getEnumTypes().get(i).getIndex()); + } + } + + public void testFieldDescriptor() throws Exception { + Descriptor messageType = TestAllTypes.getDescriptor(); + FieldDescriptor primitiveField = + messageType.findFieldByName("optional_int32"); + FieldDescriptor enumField = + messageType.findFieldByName("optional_nested_enum"); + FieldDescriptor messageField = + messageType.findFieldByName("optional_foreign_message"); + FieldDescriptor cordField = + messageType.findFieldByName("optional_cord"); + FieldDescriptor extension = + UnittestProto.optionalInt32Extension.getDescriptor(); + FieldDescriptor nestedExtension = TestRequired.single.getDescriptor(); + + assertEquals("optional_int32", primitiveField.getName()); + assertEquals("protobuf_unittest.TestAllTypes.optional_int32", + primitiveField.getFullName()); + assertEquals(1, primitiveField.getNumber()); + assertEquals(messageType, primitiveField.getContainingType()); + assertEquals(UnittestProto.getDescriptor(), primitiveField.getFile()); + assertEquals(FieldDescriptor.Type.INT32, primitiveField.getType()); + assertEquals(FieldDescriptor.JavaType.INT, primitiveField.getJavaType()); + assertEquals(DescriptorProtos.FieldOptions.getDefaultInstance(), + primitiveField.getOptions()); + assertFalse(primitiveField.isExtension()); + assertEquals("optional_int32", primitiveField.toProto().getName()); + + assertEquals("optional_nested_enum", enumField.getName()); + assertEquals(FieldDescriptor.Type.ENUM, enumField.getType()); + assertEquals(FieldDescriptor.JavaType.ENUM, enumField.getJavaType()); + assertEquals(TestAllTypes.NestedEnum.getDescriptor(), + enumField.getEnumType()); + + assertEquals("optional_foreign_message", messageField.getName()); + assertEquals(FieldDescriptor.Type.MESSAGE, messageField.getType()); + assertEquals(FieldDescriptor.JavaType.MESSAGE, messageField.getJavaType()); + assertEquals(ForeignMessage.getDescriptor(), messageField.getMessageType()); + + assertEquals("optional_cord", cordField.getName()); + assertEquals(FieldDescriptor.Type.STRING, cordField.getType()); + assertEquals(FieldDescriptor.JavaType.STRING, cordField.getJavaType()); + assertEquals(DescriptorProtos.FieldOptions.CType.CORD, + cordField.getOptions().getCtype()); + + assertEquals("optional_int32_extension", extension.getName()); + assertEquals("protobuf_unittest.optional_int32_extension", + extension.getFullName()); + assertEquals(1, extension.getNumber()); + assertEquals(TestAllExtensions.getDescriptor(), + extension.getContainingType()); + assertEquals(UnittestProto.getDescriptor(), extension.getFile()); + assertEquals(FieldDescriptor.Type.INT32, extension.getType()); + assertEquals(FieldDescriptor.JavaType.INT, extension.getJavaType()); + assertEquals(DescriptorProtos.FieldOptions.getDefaultInstance(), + extension.getOptions()); + assertTrue(extension.isExtension()); + assertEquals(null, extension.getExtensionScope()); + assertEquals("optional_int32_extension", extension.toProto().getName()); + + assertEquals("single", nestedExtension.getName()); + assertEquals("protobuf_unittest.TestRequired.single", + nestedExtension.getFullName()); + assertEquals(TestRequired.getDescriptor(), + nestedExtension.getExtensionScope()); + } + + public void testFieldDescriptorLabel() throws Exception { + FieldDescriptor requiredField = + TestRequired.getDescriptor().findFieldByName("a"); + FieldDescriptor optionalField = + TestAllTypes.getDescriptor().findFieldByName("optional_int32"); + FieldDescriptor repeatedField = + TestAllTypes.getDescriptor().findFieldByName("repeated_int32"); + + assertTrue(requiredField.isRequired()); + assertFalse(requiredField.isRepeated()); + assertFalse(optionalField.isRequired()); + assertFalse(optionalField.isRepeated()); + assertFalse(repeatedField.isRequired()); + assertTrue(repeatedField.isRepeated()); + } + + public void testFieldDescriptorJsonName() throws Exception { + FieldDescriptor requiredField = TestRequired.getDescriptor().findFieldByName("a"); + FieldDescriptor optionalField = TestAllTypes.getDescriptor().findFieldByName("optional_int32"); + FieldDescriptor repeatedField = TestAllTypes.getDescriptor().findFieldByName("repeated_int32"); + assertEquals("a", requiredField.getJsonName()); + assertEquals("optionalInt32", optionalField.getJsonName()); + assertEquals("repeatedInt32", repeatedField.getJsonName()); + } + + public void testFieldDescriptorDefault() throws Exception { + Descriptor d = TestAllTypes.getDescriptor(); + assertFalse(d.findFieldByName("optional_int32").hasDefaultValue()); + assertEquals(0, d.findFieldByName("optional_int32").getDefaultValue()); + assertTrue(d.findFieldByName("default_int32").hasDefaultValue()); + assertEquals(41, d.findFieldByName("default_int32").getDefaultValue()); + + d = TestExtremeDefaultValues.getDescriptor(); + assertEquals( + ByteString.copyFrom( + "\0\001\007\b\f\n\r\t\013\\\'\"\u00fe".getBytes(Internal.ISO_8859_1)), + d.findFieldByName("escaped_bytes").getDefaultValue()); + assertEquals(-1, d.findFieldByName("large_uint32").getDefaultValue()); + assertEquals(-1L, d.findFieldByName("large_uint64").getDefaultValue()); + } + + public void testEnumDescriptor() throws Exception { + EnumDescriptor enumType = ForeignEnum.getDescriptor(); + EnumDescriptor nestedType = TestAllTypes.NestedEnum.getDescriptor(); + + assertEquals("ForeignEnum", enumType.getName()); + assertEquals("protobuf_unittest.ForeignEnum", enumType.getFullName()); + assertEquals(UnittestProto.getDescriptor(), enumType.getFile()); + assertNull(enumType.getContainingType()); + assertEquals(DescriptorProtos.EnumOptions.getDefaultInstance(), + enumType.getOptions()); + + assertEquals("NestedEnum", nestedType.getName()); + assertEquals("protobuf_unittest.TestAllTypes.NestedEnum", + nestedType.getFullName()); + assertEquals(UnittestProto.getDescriptor(), nestedType.getFile()); + assertEquals(TestAllTypes.getDescriptor(), nestedType.getContainingType()); + + EnumValueDescriptor value = ForeignEnum.FOREIGN_FOO.getValueDescriptor(); + assertEquals(value, enumType.getValues().get(0)); + assertEquals("FOREIGN_FOO", value.getName()); + assertEquals("FOREIGN_FOO", value.toString()); + assertEquals(4, value.getNumber()); + assertEquals(value, enumType.findValueByName("FOREIGN_FOO")); + assertEquals(value, enumType.findValueByNumber(4)); + assertNull(enumType.findValueByName("NO_SUCH_VALUE")); + for (int i = 0; i < enumType.getValues().size(); i++) { + assertEquals(i, enumType.getValues().get(i).getIndex()); + } + } + + public void testServiceDescriptor() throws Exception { + ServiceDescriptor service = TestService.getDescriptor(); + + assertEquals("TestService", service.getName()); + assertEquals("protobuf_unittest.TestService", service.getFullName()); + assertEquals(UnittestProto.getDescriptor(), service.getFile()); + + + MethodDescriptor fooMethod = service.getMethods().get(0); + assertEquals("Foo", fooMethod.getName()); + assertEquals(UnittestProto.FooRequest.getDescriptor(), + fooMethod.getInputType()); + assertEquals(UnittestProto.FooResponse.getDescriptor(), + fooMethod.getOutputType()); + assertEquals(fooMethod, service.findMethodByName("Foo")); + + MethodDescriptor barMethod = service.getMethods().get(1); + assertEquals("Bar", barMethod.getName()); + assertEquals(UnittestProto.BarRequest.getDescriptor(), + barMethod.getInputType()); + assertEquals(UnittestProto.BarResponse.getDescriptor(), + barMethod.getOutputType()); + assertEquals(barMethod, service.findMethodByName("Bar")); + + assertNull(service.findMethodByName("NoSuchMethod")); + + for (int i = 0; i < service.getMethods().size(); i++) { + assertEquals(i, service.getMethods().get(i).getIndex()); + } + } + + + public void testCustomOptions() throws Exception { + // Get the descriptor indirectly from a dependent proto class. This is to + // ensure that when a proto class is loaded, custom options defined in its + // dependencies are also properly initialized. + Descriptor descriptor = + TestCustomOptions.TestMessageWithCustomOptionsContainer.getDescriptor() + .findFieldByName("field").getMessageType(); + + assertTrue( + descriptor.getOptions().hasExtension(UnittestCustomOptions.messageOpt1)); + assertEquals(Integer.valueOf(-56), + descriptor.getOptions().getExtension(UnittestCustomOptions.messageOpt1)); + + FieldDescriptor field = descriptor.findFieldByName("field1"); + assertNotNull(field); + + assertTrue( + field.getOptions().hasExtension(UnittestCustomOptions.fieldOpt1)); + assertEquals(Long.valueOf(8765432109L), + field.getOptions().getExtension(UnittestCustomOptions.fieldOpt1)); + + EnumDescriptor enumType = + UnittestCustomOptions.TestMessageWithCustomOptions.AnEnum.getDescriptor(); + + assertTrue( + enumType.getOptions().hasExtension(UnittestCustomOptions.enumOpt1)); + assertEquals(Integer.valueOf(-789), + enumType.getOptions().getExtension(UnittestCustomOptions.enumOpt1)); + + ServiceDescriptor service = + UnittestCustomOptions.TestServiceWithCustomOptions.getDescriptor(); + + assertTrue( + service.getOptions().hasExtension(UnittestCustomOptions.serviceOpt1)); + assertEquals(Long.valueOf(-9876543210L), + service.getOptions().getExtension(UnittestCustomOptions.serviceOpt1)); + + MethodDescriptor method = service.findMethodByName("Foo"); + assertNotNull(method); + + assertTrue( + method.getOptions().hasExtension(UnittestCustomOptions.methodOpt1)); + assertEquals(UnittestCustomOptions.MethodOpt1.METHODOPT1_VAL2, + method.getOptions().getExtension(UnittestCustomOptions.methodOpt1)); + } + + /** + * Test that the FieldDescriptor.Type enum is the same as the + * WireFormat.FieldType enum. + */ + public void testFieldTypeTablesMatch() throws Exception { + FieldDescriptor.Type[] values1 = FieldDescriptor.Type.values(); + WireFormat.FieldType[] values2 = WireFormat.FieldType.values(); + + assertEquals(values1.length, values2.length); + + for (int i = 0; i < values1.length; i++) { + assertEquals(values1[i].toString(), values2[i].toString()); + } + } + + /** + * Test that the FieldDescriptor.JavaType enum is the same as the + * WireFormat.JavaType enum. + */ + public void testJavaTypeTablesMatch() throws Exception { + FieldDescriptor.JavaType[] values1 = FieldDescriptor.JavaType.values(); + WireFormat.JavaType[] values2 = WireFormat.JavaType.values(); + + assertEquals(values1.length, values2.length); + + for (int i = 0; i < values1.length; i++) { + assertEquals(values1[i].toString(), values2[i].toString()); + } + } + + public void testEnormousDescriptor() throws Exception { + // The descriptor for this file is larger than 64k, yet it did not cause + // a compiler error due to an over-long string literal. + assertTrue( + UnittestEnormousDescriptor.getDescriptor() + .toProto().getSerializedSize() > 65536); + } + + /** + * Tests that the DescriptorValidationException works as intended. + */ + public void testDescriptorValidatorException() throws Exception { + FileDescriptorProto fileDescriptorProto = FileDescriptorProto.newBuilder() + .setName("foo.proto") + .addMessageType(DescriptorProto.newBuilder() + .setName("Foo") + .addField(FieldDescriptorProto.newBuilder() + .setLabel(FieldDescriptorProto.Label.LABEL_OPTIONAL) + .setType(FieldDescriptorProto.Type.TYPE_INT32) + .setName("foo") + .setNumber(1) + .setDefaultValue("invalid") + .build()) + .build()) + .build(); + try { + Descriptors.FileDescriptor.buildFrom(fileDescriptorProto, + new FileDescriptor[0]); + fail("DescriptorValidationException expected"); + } catch (DescriptorValidationException e) { + // Expected; check that the error message contains some useful hints + assertTrue(e.getMessage().indexOf("foo") != -1); + assertTrue(e.getMessage().indexOf("Foo") != -1); + assertTrue(e.getMessage().indexOf("invalid") != -1); + assertTrue(e.getCause() instanceof NumberFormatException); + assertTrue(e.getCause().getMessage().indexOf("invalid") != -1); + } + } + + /** + * Tests the translate/crosslink for an example where a message field's name + * and type name are the same. + */ + public void testDescriptorComplexCrosslink() throws Exception { + FileDescriptorProto fileDescriptorProto = FileDescriptorProto.newBuilder() + .setName("foo.proto") + .addMessageType(DescriptorProto.newBuilder() + .setName("Foo") + .addField(FieldDescriptorProto.newBuilder() + .setLabel(FieldDescriptorProto.Label.LABEL_OPTIONAL) + .setType(FieldDescriptorProto.Type.TYPE_INT32) + .setName("foo") + .setNumber(1) + .build()) + .build()) + .addMessageType(DescriptorProto.newBuilder() + .setName("Bar") + .addField(FieldDescriptorProto.newBuilder() + .setLabel(FieldDescriptorProto.Label.LABEL_OPTIONAL) + .setTypeName("Foo") + .setName("Foo") + .setNumber(1) + .build()) + .build()) + .build(); + // translate and crosslink + FileDescriptor file = + Descriptors.FileDescriptor.buildFrom(fileDescriptorProto, + new FileDescriptor[0]); + // verify resulting descriptors + assertNotNull(file); + List<Descriptor> msglist = file.getMessageTypes(); + assertNotNull(msglist); + assertTrue(msglist.size() == 2); + boolean barFound = false; + for (Descriptor desc : msglist) { + if (desc.getName().equals("Bar")) { + barFound = true; + assertNotNull(desc.getFields()); + List<FieldDescriptor> fieldlist = desc.getFields(); + assertNotNull(fieldlist); + assertTrue(fieldlist.size() == 1); + assertTrue(fieldlist.get(0).getType() == FieldDescriptor.Type.MESSAGE); + assertTrue(fieldlist.get(0).getMessageType().getName().equals("Foo")); + } + } + assertTrue(barFound); + } + + public void testDependencyOrder() throws Exception { + FileDescriptorProto fooProto = FileDescriptorProto.newBuilder() + .setName("foo.proto").build(); + FileDescriptorProto barProto = FileDescriptorProto.newBuilder() + .setName("bar.proto") + .addDependency("foo.proto") + .build(); + FileDescriptorProto bazProto = FileDescriptorProto.newBuilder() + .setName("baz.proto") + .addDependency("foo.proto") + .addDependency("bar.proto") + .addPublicDependency(0) + .addPublicDependency(1) + .build(); + FileDescriptor fooFile = Descriptors.FileDescriptor.buildFrom(fooProto, + new FileDescriptor[0]); + FileDescriptor barFile = Descriptors.FileDescriptor.buildFrom(barProto, + new FileDescriptor[] {fooFile}); + + // Items in the FileDescriptor array can be in any order. + Descriptors.FileDescriptor.buildFrom(bazProto, + new FileDescriptor[] {fooFile, barFile}); + Descriptors.FileDescriptor.buildFrom(bazProto, + new FileDescriptor[] {barFile, fooFile}); + } + + public void testInvalidPublicDependency() throws Exception { + FileDescriptorProto fooProto = FileDescriptorProto.newBuilder() + .setName("foo.proto").build(); + FileDescriptorProto barProto = FileDescriptorProto.newBuilder() + .setName("boo.proto") + .addDependency("foo.proto") + .addPublicDependency(1) // Error, should be 0. + .build(); + FileDescriptor fooFile = Descriptors.FileDescriptor.buildFrom(fooProto, + new FileDescriptor[0]); + try { + Descriptors.FileDescriptor.buildFrom(barProto, + new FileDescriptor[] {fooFile}); + fail("DescriptorValidationException expected"); + } catch (DescriptorValidationException e) { + assertTrue( + e.getMessage().indexOf("Invalid public dependency index.") != -1); + } + } + + public void testHiddenDependency() throws Exception { + FileDescriptorProto barProto = FileDescriptorProto.newBuilder() + .setName("bar.proto") + .addMessageType(DescriptorProto.newBuilder().setName("Bar")) + .build(); + FileDescriptorProto forwardProto = FileDescriptorProto.newBuilder() + .setName("forward.proto") + .addDependency("bar.proto") + .build(); + FileDescriptorProto fooProto = FileDescriptorProto.newBuilder() + .setName("foo.proto") + .addDependency("forward.proto") + .addMessageType(DescriptorProto.newBuilder() + .setName("Foo") + .addField(FieldDescriptorProto.newBuilder() + .setLabel(FieldDescriptorProto.Label.LABEL_OPTIONAL) + .setTypeName("Bar") + .setName("bar") + .setNumber(1))) + .build(); + FileDescriptor barFile = Descriptors.FileDescriptor.buildFrom( + barProto, new FileDescriptor[0]); + FileDescriptor forwardFile = Descriptors.FileDescriptor.buildFrom( + forwardProto, new FileDescriptor[] {barFile}); + + try { + Descriptors.FileDescriptor.buildFrom( + fooProto, new FileDescriptor[] {forwardFile}); + fail("DescriptorValidationException expected"); + } catch (DescriptorValidationException e) { + assertTrue(e.getMessage().indexOf("Bar") != -1); + assertTrue(e.getMessage().indexOf("is not defined") != -1); + } + } + + public void testPublicDependency() throws Exception { + FileDescriptorProto barProto = FileDescriptorProto.newBuilder() + .setName("bar.proto") + .addMessageType(DescriptorProto.newBuilder().setName("Bar")) + .build(); + FileDescriptorProto forwardProto = FileDescriptorProto.newBuilder() + .setName("forward.proto") + .addDependency("bar.proto") + .addPublicDependency(0) + .build(); + FileDescriptorProto fooProto = FileDescriptorProto.newBuilder() + .setName("foo.proto") + .addDependency("forward.proto") + .addMessageType(DescriptorProto.newBuilder() + .setName("Foo") + .addField(FieldDescriptorProto.newBuilder() + .setLabel(FieldDescriptorProto.Label.LABEL_OPTIONAL) + .setTypeName("Bar") + .setName("bar") + .setNumber(1))) + .build(); + FileDescriptor barFile = Descriptors.FileDescriptor.buildFrom( + barProto, new FileDescriptor[0]); + FileDescriptor forwardFile = Descriptors.FileDescriptor.buildFrom( + forwardProto, new FileDescriptor[]{barFile}); + Descriptors.FileDescriptor.buildFrom( + fooProto, new FileDescriptor[] {forwardFile}); + } + + /** + * Tests the translate/crosslink for an example with a more complex namespace + * referencing. + */ + public void testComplexNamespacePublicDependency() throws Exception { + FileDescriptorProto fooProto = FileDescriptorProto.newBuilder() + .setName("bar.proto") + .setPackage("a.b.c.d.bar.shared") + .addEnumType(EnumDescriptorProto.newBuilder() + .setName("MyEnum") + .addValue(EnumValueDescriptorProto.newBuilder() + .setName("BLAH") + .setNumber(1))) + .build(); + FileDescriptorProto barProto = FileDescriptorProto.newBuilder() + .setName("foo.proto") + .addDependency("bar.proto") + .setPackage("a.b.c.d.foo.shared") + .addMessageType(DescriptorProto.newBuilder() + .setName("MyMessage") + .addField(FieldDescriptorProto.newBuilder() + .setLabel(FieldDescriptorProto.Label.LABEL_REPEATED) + .setTypeName("bar.shared.MyEnum") + .setName("MyField") + .setNumber(1))) + .build(); + // translate and crosslink + FileDescriptor fooFile = Descriptors.FileDescriptor.buildFrom( + fooProto, new FileDescriptor[0]); + FileDescriptor barFile = Descriptors.FileDescriptor.buildFrom( + barProto, new FileDescriptor[]{fooFile}); + // verify resulting descriptors + assertNotNull(barFile); + List<Descriptor> msglist = barFile.getMessageTypes(); + assertNotNull(msglist); + assertTrue(msglist.size() == 1); + Descriptor desc = msglist.get(0); + if (desc.getName().equals("MyMessage")) { + assertNotNull(desc.getFields()); + List<FieldDescriptor> fieldlist = desc.getFields(); + assertNotNull(fieldlist); + assertTrue(fieldlist.size() == 1); + FieldDescriptor field = fieldlist.get(0); + assertTrue(field.getType() == FieldDescriptor.Type.ENUM); + assertTrue(field.getEnumType().getName().equals("MyEnum")); + assertTrue(field.getEnumType().getFile().getName().equals("bar.proto")); + assertTrue(field.getEnumType().getFile().getPackage().equals( + "a.b.c.d.bar.shared")); + } + } + + public void testOneofDescriptor() throws Exception { + Descriptor messageType = TestAllTypes.getDescriptor(); + FieldDescriptor field = + messageType.findFieldByName("oneof_nested_message"); + OneofDescriptor oneofDescriptor = field.getContainingOneof(); + assertNotNull(oneofDescriptor); + assertSame(oneofDescriptor, messageType.getOneofs().get(0)); + assertEquals("oneof_field", oneofDescriptor.getName()); + + assertEquals(4, oneofDescriptor.getFieldCount()); + assertSame(oneofDescriptor.getField(1), field); + + assertEquals(4, oneofDescriptor.getFields().size()); + assertEquals(oneofDescriptor.getFields().get(1), field); + } + + public void testMessageDescriptorExtensions() throws Exception { + assertFalse(TestAllTypes.getDescriptor().isExtendable()); + assertTrue(TestAllExtensions.getDescriptor().isExtendable()); + assertTrue(TestMultipleExtensionRanges.getDescriptor().isExtendable()); + + assertFalse(TestAllTypes.getDescriptor().isExtensionNumber(3)); + assertTrue(TestAllExtensions.getDescriptor().isExtensionNumber(3)); + assertTrue(TestMultipleExtensionRanges.getDescriptor().isExtensionNumber(42)); + assertFalse(TestMultipleExtensionRanges.getDescriptor().isExtensionNumber(43)); + assertFalse(TestMultipleExtensionRanges.getDescriptor().isExtensionNumber(4142)); + assertTrue(TestMultipleExtensionRanges.getDescriptor().isExtensionNumber(4143)); + } + + public void testReservedFields() { + Descriptor d = TestReservedFields.getDescriptor(); + assertTrue(d.isReservedNumber(2)); + assertFalse(d.isReservedNumber(8)); + assertTrue(d.isReservedNumber(9)); + assertTrue(d.isReservedNumber(10)); + assertTrue(d.isReservedNumber(11)); + assertFalse(d.isReservedNumber(12)); + assertFalse(d.isReservedName("foo")); + assertTrue(d.isReservedName("bar")); + assertTrue(d.isReservedName("baz")); + } + + 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") + .addEnumType(EnumDescriptorProto.newBuilder() + .setName("Enum") + .addValue(EnumValueDescriptorProto.newBuilder() + .setName("FOO") + .setNumber(1) + .build()) + .build()) + .addMessageType(DescriptorProto.newBuilder() + .setName("Message") + .addField(FieldDescriptorProto.newBuilder() + .setName("foo") + .setTypeName("Enum") + .setNumber(1) + .setLabel(FieldDescriptorProto.Label.LABEL_REPEATED) + .setOptions(DescriptorProtos.FieldOptions.newBuilder() + .setPacked(true) + .build()) + .build()) + .build()) + .build(); + Descriptors.FileDescriptor.buildFrom( + fileDescriptorProto, new FileDescriptor[0]); + } +} diff --git a/java/core/src/test/java/com/google/protobuf/DoubleArrayListTest.java b/java/core/src/test/java/com/google/protobuf/DoubleArrayListTest.java new file mode 100644 index 00000000..d3deaa07 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/DoubleArrayListTest.java @@ -0,0 +1,473 @@ +// 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 static java.util.Arrays.asList; + +import junit.framework.TestCase; + +import java.util.Collections; +import java.util.ConcurrentModificationException; +import java.util.Iterator; + +/** + * Tests for {@link DoubleArrayList}. + * + * @author dweis@google.com (Daniel Weis) + */ +public class DoubleArrayListTest extends TestCase { + + private static final DoubleArrayList UNARY_LIST = newImmutableDoubleArrayList(1); + private static final DoubleArrayList TERTIARY_LIST = + newImmutableDoubleArrayList(1, 2, 3); + + private DoubleArrayList list; + + @Override + protected void setUp() throws Exception { + list = new DoubleArrayList(); + } + + public void testEmptyListReturnsSameInstance() { + assertSame(DoubleArrayList.emptyList(), DoubleArrayList.emptyList()); + } + + public void testEmptyListIsImmutable() { + assertImmutable(DoubleArrayList.emptyList()); + } + + public void testMakeImmutable() { + list.addDouble(2); + list.addDouble(4); + list.addDouble(6); + list.addDouble(8); + list.makeImmutable(); + assertImmutable(list); + } + + public void testCopyConstructor() { + DoubleArrayList copy = new DoubleArrayList(TERTIARY_LIST); + assertEquals(TERTIARY_LIST, copy); + + copy = new DoubleArrayList(DoubleArrayList.emptyList()); + assertEquals(DoubleArrayList.emptyList(), copy); + + copy = new DoubleArrayList(asList(1D, 2D, 3D)); + assertEquals(asList(1D, 2D, 3D), copy); + + copy = new DoubleArrayList(Collections.<Double>emptyList()); + assertEquals(DoubleArrayList.emptyList(), copy); + } + + public void testModificationWithIteration() { + list.addAll(asList(1D, 2D, 3D, 4D)); + Iterator<Double> iterator = list.iterator(); + assertEquals(4, list.size()); + assertEquals(1D, (double) list.get(0)); + assertEquals(1D, (double) iterator.next()); + list.set(0, 1D); + assertEquals(2D, (double) iterator.next()); + + list.remove(0); + try { + iterator.next(); + fail(); + } catch (ConcurrentModificationException e) { + // expected + } + + iterator = list.iterator(); + list.add(0, 0D); + try { + iterator.next(); + fail(); + } catch (ConcurrentModificationException e) { + // expected + } + } + + public void testGet() { + assertEquals(1D, (double) TERTIARY_LIST.get(0)); + assertEquals(2D, (double) TERTIARY_LIST.get(1)); + assertEquals(3D, (double) TERTIARY_LIST.get(2)); + + try { + TERTIARY_LIST.get(-1); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + + try { + TERTIARY_LIST.get(3); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + public void testGetInt() { + assertEquals(1D, TERTIARY_LIST.getDouble(0)); + assertEquals(2D, TERTIARY_LIST.getDouble(1)); + assertEquals(3D, TERTIARY_LIST.getDouble(2)); + + try { + TERTIARY_LIST.get(-1); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + + try { + TERTIARY_LIST.get(3); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + public void testSize() { + assertEquals(0, DoubleArrayList.emptyList().size()); + assertEquals(1, UNARY_LIST.size()); + assertEquals(3, TERTIARY_LIST.size()); + + list.addDouble(2); + list.addDouble(4); + list.addDouble(6); + list.addDouble(8); + assertEquals(4, list.size()); + + list.remove(0); + assertEquals(3, list.size()); + + list.add(16D); + assertEquals(4, list.size()); + } + + public void testSet() { + list.addDouble(2); + list.addDouble(4); + + assertEquals(2D, (double) list.set(0, 0D)); + assertEquals(0D, list.getDouble(0)); + + assertEquals(4D, (double) list.set(1, 0D)); + assertEquals(0D, list.getDouble(1)); + + try { + list.set(-1, 0D); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + + try { + list.set(2, 0D); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + public void testSetInt() { + list.addDouble(2); + list.addDouble(4); + + assertEquals(2D, list.setDouble(0, 0)); + assertEquals(0D, list.getDouble(0)); + + assertEquals(4D, list.setDouble(1, 0)); + assertEquals(0D, list.getDouble(1)); + + try { + list.setDouble(-1, 0); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + + try { + list.setDouble(2, 0); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + public void testAdd() { + assertEquals(0, list.size()); + + assertTrue(list.add(2D)); + assertEquals(asList(2D), list); + + assertTrue(list.add(3D)); + list.add(0, 4D); + assertEquals(asList(4D, 2D, 3D), list); + + list.add(0, 1D); + list.add(0, 0D); + // Force a resize by getting up to 11 elements. + for (int i = 0; i < 6; i++) { + list.add(Double.valueOf(5 + i)); + } + assertEquals(asList(0D, 1D, 4D, 2D, 3D, 5D, 6D, 7D, 8D, 9D, 10D), list); + + try { + list.add(-1, 5D); + } catch (IndexOutOfBoundsException e) { + // expected + } + + try { + list.add(4, 5D); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + public void testAddInt() { + assertEquals(0, list.size()); + + list.addDouble(2); + assertEquals(asList(2D), list); + + list.addDouble(3); + assertEquals(asList(2D, 3D), list); + } + + public void testAddAll() { + assertEquals(0, list.size()); + + assertTrue(list.addAll(Collections.singleton(1D))); + assertEquals(1, list.size()); + assertEquals(1D, (double) list.get(0)); + assertEquals(1D, list.getDouble(0)); + + assertTrue(list.addAll(asList(2D, 3D, 4D, 5D, 6D))); + assertEquals(asList(1D, 2D, 3D, 4D, 5D, 6D), list); + + assertTrue(list.addAll(TERTIARY_LIST)); + assertEquals(asList(1D, 2D, 3D, 4D, 5D, 6D, 1D, 2D, 3D), list); + + assertFalse(list.addAll(Collections.<Double>emptyList())); + assertFalse(list.addAll(DoubleArrayList.emptyList())); + } + + public void testRemove() { + list.addAll(TERTIARY_LIST); + assertEquals(1D, (double) list.remove(0)); + assertEquals(asList(2D, 3D), list); + + assertTrue(list.remove(Double.valueOf(3))); + assertEquals(asList(2D), list); + + assertFalse(list.remove(Double.valueOf(3))); + assertEquals(asList(2D), list); + + assertEquals(2D, (double) list.remove(0)); + assertEquals(asList(), list); + + try { + list.remove(-1); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + + try { + list.remove(0); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + private void assertImmutable(DoubleArrayList list) { + if (list.contains(1D)) { + throw new RuntimeException("Cannot test the immutability of lists that contain 1."); + } + + try { + list.add(1D); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.add(0, 1D); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(Collections.<Double>emptyList()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(Collections.singletonList(1D)); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(new DoubleArrayList()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(UNARY_LIST); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(0, Collections.singleton(1D)); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(0, UNARY_LIST); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(0, Collections.<Double>emptyList()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addDouble(0); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.clear(); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.remove(1); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.remove(new Object()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.removeAll(Collections.<Double>emptyList()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.removeAll(Collections.singleton(1D)); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.removeAll(UNARY_LIST); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.retainAll(Collections.<Double>emptyList()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.retainAll(Collections.singleton(1D)); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.retainAll(UNARY_LIST); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.set(0, 0D); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.setDouble(0, 0); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + } + + private static DoubleArrayList newImmutableDoubleArrayList(double... elements) { + DoubleArrayList list = new DoubleArrayList(); + for (double element : elements) { + list.addDouble(element); + } + list.makeImmutable(); + return list; + } +} diff --git a/java/core/src/test/java/com/google/protobuf/DynamicMessageTest.java b/java/core/src/test/java/com/google/protobuf/DynamicMessageTest.java new file mode 100644 index 00000000..55144e7c --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/DynamicMessageTest.java @@ -0,0 +1,326 @@ +// 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.EnumDescriptor; +import com.google.protobuf.Descriptors.FieldDescriptor; +import com.google.protobuf.Descriptors.OneofDescriptor; + +import protobuf_unittest.UnittestProto.TestAllExtensions; +import protobuf_unittest.UnittestProto.TestAllTypes; +import protobuf_unittest.UnittestProto.TestEmptyMessage; +import protobuf_unittest.UnittestProto.TestPackedTypes; + +import junit.framework.TestCase; +import java.util.Arrays; + +/** + * Unit test for {@link DynamicMessage}. See also {@link MessageTest}, which + * tests some {@link DynamicMessage} functionality. + * + * @author kenton@google.com Kenton Varda + */ +public class DynamicMessageTest extends TestCase { + TestUtil.ReflectionTester reflectionTester = + new TestUtil.ReflectionTester(TestAllTypes.getDescriptor(), null); + + TestUtil.ReflectionTester extensionsReflectionTester = + new TestUtil.ReflectionTester(TestAllExtensions.getDescriptor(), + TestUtil.getExtensionRegistry()); + TestUtil.ReflectionTester packedReflectionTester = + new TestUtil.ReflectionTester(TestPackedTypes.getDescriptor(), null); + + public void testDynamicMessageAccessors() throws Exception { + Message.Builder builder = + DynamicMessage.newBuilder(TestAllTypes.getDescriptor()); + reflectionTester.setAllFieldsViaReflection(builder); + Message message = builder.build(); + reflectionTester.assertAllFieldsSetViaReflection(message); + } + + public void testSettersAfterBuild() throws Exception { + Message.Builder builder = + DynamicMessage.newBuilder(TestAllTypes.getDescriptor()); + Message firstMessage = builder.build(); + // double build() + builder.build(); + // clear() after build() + builder.clear(); + // setters after build() + reflectionTester.setAllFieldsViaReflection(builder); + Message message = builder.build(); + reflectionTester.assertAllFieldsSetViaReflection(message); + // repeated setters after build() + reflectionTester.modifyRepeatedFieldsViaReflection(builder); + message = builder.build(); + reflectionTester.assertRepeatedFieldsModifiedViaReflection(message); + // firstMessage shouldn't have been modified. + reflectionTester.assertClearViaReflection(firstMessage); + } + + public void testUnknownFields() throws Exception { + Message.Builder builder = + DynamicMessage.newBuilder(TestEmptyMessage.getDescriptor()); + builder.setUnknownFields(UnknownFieldSet.newBuilder() + .addField(1, UnknownFieldSet.Field.newBuilder().addVarint(1).build()) + .addField(2, UnknownFieldSet.Field.newBuilder().addFixed32(1).build()) + .build()); + Message message = builder.build(); + assertEquals(2, message.getUnknownFields().asMap().size()); + // clone() with unknown fields + Message.Builder newBuilder = builder.clone(); + assertEquals(2, newBuilder.getUnknownFields().asMap().size()); + // clear() with unknown fields + newBuilder.clear(); + assertTrue(newBuilder.getUnknownFields().asMap().isEmpty()); + // serialize/parse with unknown fields + newBuilder.mergeFrom(message.toByteString()); + assertEquals(2, newBuilder.getUnknownFields().asMap().size()); + } + + public void testDynamicMessageSettersRejectNull() throws Exception { + Message.Builder builder = + DynamicMessage.newBuilder(TestAllTypes.getDescriptor()); + reflectionTester.assertReflectionSettersRejectNull(builder); + } + + public void testDynamicMessageExtensionAccessors() throws Exception { + // We don't need to extensively test DynamicMessage's handling of + // extensions because, frankly, it doesn't do anything special with them. + // It treats them just like any other fields. + Message.Builder builder = + DynamicMessage.newBuilder(TestAllExtensions.getDescriptor()); + extensionsReflectionTester.setAllFieldsViaReflection(builder); + Message message = builder.build(); + extensionsReflectionTester.assertAllFieldsSetViaReflection(message); + } + + public void testDynamicMessageExtensionSettersRejectNull() throws Exception { + Message.Builder builder = + DynamicMessage.newBuilder(TestAllExtensions.getDescriptor()); + extensionsReflectionTester.assertReflectionSettersRejectNull(builder); + } + + public void testDynamicMessageRepeatedSetters() throws Exception { + Message.Builder builder = + DynamicMessage.newBuilder(TestAllTypes.getDescriptor()); + reflectionTester.setAllFieldsViaReflection(builder); + reflectionTester.modifyRepeatedFieldsViaReflection(builder); + Message message = builder.build(); + reflectionTester.assertRepeatedFieldsModifiedViaReflection(message); + } + + public void testDynamicMessageRepeatedSettersRejectNull() throws Exception { + Message.Builder builder = + DynamicMessage.newBuilder(TestAllTypes.getDescriptor()); + reflectionTester.assertReflectionRepeatedSettersRejectNull(builder); + } + + public void testDynamicMessageDefaults() throws Exception { + reflectionTester.assertClearViaReflection( + DynamicMessage.getDefaultInstance(TestAllTypes.getDescriptor())); + reflectionTester.assertClearViaReflection( + DynamicMessage.newBuilder(TestAllTypes.getDescriptor()).build()); + } + + public void testDynamicMessageSerializedSize() throws Exception { + TestAllTypes message = TestUtil.getAllSet(); + + Message.Builder dynamicBuilder = + DynamicMessage.newBuilder(TestAllTypes.getDescriptor()); + reflectionTester.setAllFieldsViaReflection(dynamicBuilder); + Message dynamicMessage = dynamicBuilder.build(); + + assertEquals(message.getSerializedSize(), + dynamicMessage.getSerializedSize()); + } + + public void testDynamicMessageSerialization() throws Exception { + Message.Builder builder = + DynamicMessage.newBuilder(TestAllTypes.getDescriptor()); + reflectionTester.setAllFieldsViaReflection(builder); + Message message = builder.build(); + + ByteString rawBytes = message.toByteString(); + TestAllTypes message2 = TestAllTypes.parseFrom(rawBytes); + + TestUtil.assertAllFieldsSet(message2); + + // In fact, the serialized forms should be exactly the same, byte-for-byte. + assertEquals(TestUtil.getAllSet().toByteString(), rawBytes); + } + + public void testDynamicMessageParsing() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TestUtil.setAllFields(builder); + TestAllTypes message = builder.build(); + + ByteString rawBytes = message.toByteString(); + + Message message2 = + DynamicMessage.parseFrom(TestAllTypes.getDescriptor(), rawBytes); + reflectionTester.assertAllFieldsSetViaReflection(message2); + + // Test Parser interface. + Message message3 = message2.getParserForType().parseFrom(rawBytes); + reflectionTester.assertAllFieldsSetViaReflection(message3); + } + + public void testDynamicMessageExtensionParsing() throws Exception { + ByteString rawBytes = TestUtil.getAllExtensionsSet().toByteString(); + Message message = DynamicMessage.parseFrom( + TestAllExtensions.getDescriptor(), rawBytes, + TestUtil.getExtensionRegistry()); + extensionsReflectionTester.assertAllFieldsSetViaReflection(message); + + // Test Parser interface. + Message message2 = message.getParserForType().parseFrom( + rawBytes, TestUtil.getExtensionRegistry()); + extensionsReflectionTester.assertAllFieldsSetViaReflection(message2); + } + + public void testDynamicMessagePackedSerialization() throws Exception { + Message.Builder builder = + DynamicMessage.newBuilder(TestPackedTypes.getDescriptor()); + packedReflectionTester.setPackedFieldsViaReflection(builder); + Message message = builder.build(); + + ByteString rawBytes = message.toByteString(); + TestPackedTypes message2 = TestPackedTypes.parseFrom(rawBytes); + + TestUtil.assertPackedFieldsSet(message2); + + // In fact, the serialized forms should be exactly the same, byte-for-byte. + assertEquals(TestUtil.getPackedSet().toByteString(), rawBytes); + } + + public void testDynamicMessagePackedParsing() throws Exception { + TestPackedTypes.Builder builder = TestPackedTypes.newBuilder(); + TestUtil.setPackedFields(builder); + TestPackedTypes message = builder.build(); + + ByteString rawBytes = message.toByteString(); + + Message message2 = + DynamicMessage.parseFrom(TestPackedTypes.getDescriptor(), rawBytes); + packedReflectionTester.assertPackedFieldsSetViaReflection(message2); + + // Test Parser interface. + Message message3 = message2.getParserForType().parseFrom(rawBytes); + packedReflectionTester.assertPackedFieldsSetViaReflection(message3); + } + + public void testDynamicMessageCopy() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TestUtil.setAllFields(builder); + TestAllTypes message = builder.build(); + + DynamicMessage copy = DynamicMessage.newBuilder(message).build(); + reflectionTester.assertAllFieldsSetViaReflection(copy); + + // Test oneof behavior + FieldDescriptor bytesField = + TestAllTypes.getDescriptor().findFieldByName("oneof_bytes"); + FieldDescriptor uint32Field = + TestAllTypes.getDescriptor().findFieldByName("oneof_uint32"); + assertTrue(copy.hasField(bytesField)); + assertFalse(copy.hasField(uint32Field)); + DynamicMessage copy2 = + DynamicMessage.newBuilder(message).setField(uint32Field, 123).build(); + assertFalse(copy2.hasField(bytesField)); + assertTrue(copy2.hasField(uint32Field)); + assertEquals(123, copy2.getField(uint32Field)); + } + + public void testToBuilder() throws Exception { + DynamicMessage.Builder builder = + DynamicMessage.newBuilder(TestAllTypes.getDescriptor()); + reflectionTester.setAllFieldsViaReflection(builder); + int unknownFieldNum = 9; + long unknownFieldVal = 90; + builder.setUnknownFields(UnknownFieldSet.newBuilder() + .addField(unknownFieldNum, + UnknownFieldSet.Field.newBuilder() + .addVarint(unknownFieldVal).build()) + .build()); + DynamicMessage message = builder.build(); + + DynamicMessage derived = message.toBuilder().build(); + reflectionTester.assertAllFieldsSetViaReflection(derived); + assertEquals(Arrays.asList(unknownFieldVal), + derived.getUnknownFields().getField(unknownFieldNum).getVarintList()); + } + + public void testDynamicOneofMessage() throws Exception { + DynamicMessage.Builder builder = + DynamicMessage.newBuilder(TestAllTypes.getDescriptor()); + OneofDescriptor oneof = TestAllTypes.getDescriptor().getOneofs().get(0); + assertFalse(builder.hasOneof(oneof)); + assertSame(null, builder.getOneofFieldDescriptor(oneof)); + + reflectionTester.setAllFieldsViaReflection(builder); + assertTrue(builder.hasOneof(oneof)); + FieldDescriptor field = oneof.getField(3); + assertSame(field, builder.getOneofFieldDescriptor(oneof)); + + DynamicMessage message = builder.buildPartial(); + assertTrue(message.hasOneof(oneof)); + + DynamicMessage.Builder mergedBuilder = + DynamicMessage.newBuilder(TestAllTypes.getDescriptor()); + FieldDescriptor mergedField = oneof.getField(0); + mergedBuilder.setField(mergedField, 123); + assertTrue(mergedBuilder.hasField(mergedField)); + mergedBuilder.mergeFrom(message); + assertTrue(mergedBuilder.hasField(field)); + assertFalse(mergedBuilder.hasField(mergedField)); + + builder.clearOneof(oneof); + assertSame(null, builder.getOneofFieldDescriptor(oneof)); + message = builder.build(); + assertSame(null, message.getOneofFieldDescriptor(oneof)); + } + + // Regression test for a bug that makes setField() not work for repeated + // enum fields. + public void testSettersForRepeatedEnumField() throws Exception { + DynamicMessage.Builder builder = + DynamicMessage.newBuilder(TestAllTypes.getDescriptor()); + FieldDescriptor repeatedEnumField = + TestAllTypes.getDescriptor().findFieldByName( + "repeated_nested_enum"); + EnumDescriptor enumDescriptor = TestAllTypes.NestedEnum.getDescriptor(); + builder.setField(repeatedEnumField, enumDescriptor.getValues()); + DynamicMessage message = builder.build(); + assertEquals( + enumDescriptor.getValues(), message.getField(repeatedEnumField)); + } +} diff --git a/java/core/src/test/java/com/google/protobuf/FieldPresenceTest.java b/java/core/src/test/java/com/google/protobuf/FieldPresenceTest.java new file mode 100644 index 00000000..eaeec0b8 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/FieldPresenceTest.java @@ -0,0 +1,373 @@ +// 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 testOneofEquals() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TestAllTypes message1 = builder.build(); + // Set message2's oneof_uint32 field to defalut value. The two + // messages should be different when check with oneof case. + builder.setOneofUint32(0); + TestAllTypes message2 = builder.build(); + assertFalse(message1.equals(message2)); + } + + 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/core/src/test/java/com/google/protobuf/FloatArrayListTest.java b/java/core/src/test/java/com/google/protobuf/FloatArrayListTest.java new file mode 100644 index 00000000..a5e65424 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/FloatArrayListTest.java @@ -0,0 +1,473 @@ +// 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 static java.util.Arrays.asList; + +import junit.framework.TestCase; + +import java.util.Collections; +import java.util.ConcurrentModificationException; +import java.util.Iterator; + +/** + * Tests for {@link FloatArrayList}. + * + * @author dweis@google.com (Daniel Weis) + */ +public class FloatArrayListTest extends TestCase { + + private static final FloatArrayList UNARY_LIST = newImmutableFloatArrayList(1); + private static final FloatArrayList TERTIARY_LIST = + newImmutableFloatArrayList(1, 2, 3); + + private FloatArrayList list; + + @Override + protected void setUp() throws Exception { + list = new FloatArrayList(); + } + + public void testEmptyListReturnsSameInstance() { + assertSame(FloatArrayList.emptyList(), FloatArrayList.emptyList()); + } + + public void testEmptyListIsImmutable() { + assertImmutable(FloatArrayList.emptyList()); + } + + public void testMakeImmutable() { + list.addFloat(2); + list.addFloat(4); + list.addFloat(6); + list.addFloat(8); + list.makeImmutable(); + assertImmutable(list); + } + + public void testCopyConstructor() { + FloatArrayList copy = new FloatArrayList(TERTIARY_LIST); + assertEquals(TERTIARY_LIST, copy); + + copy = new FloatArrayList(FloatArrayList.emptyList()); + assertEquals(FloatArrayList.emptyList(), copy); + + copy = new FloatArrayList(asList(1F, 2F, 3F)); + assertEquals(asList(1F, 2F, 3F), copy); + + copy = new FloatArrayList(Collections.<Float>emptyList()); + assertEquals(FloatArrayList.emptyList(), copy); + } + + public void testModificationWithIteration() { + list.addAll(asList(1F, 2F, 3F, 4F)); + Iterator<Float> iterator = list.iterator(); + assertEquals(4, list.size()); + assertEquals(1F, (float) list.get(0)); + assertEquals(1F, (float) iterator.next()); + list.set(0, 1F); + assertEquals(2F, (float) iterator.next()); + + list.remove(0); + try { + iterator.next(); + fail(); + } catch (ConcurrentModificationException e) { + // expected + } + + iterator = list.iterator(); + list.add(0, 0F); + try { + iterator.next(); + fail(); + } catch (ConcurrentModificationException e) { + // expected + } + } + + public void testGet() { + assertEquals(1F, (float) TERTIARY_LIST.get(0)); + assertEquals(2F, (float) TERTIARY_LIST.get(1)); + assertEquals(3F, (float) TERTIARY_LIST.get(2)); + + try { + TERTIARY_LIST.get(-1); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + + try { + TERTIARY_LIST.get(3); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + public void testGetFloat() { + assertEquals(1F, TERTIARY_LIST.getFloat(0)); + assertEquals(2F, TERTIARY_LIST.getFloat(1)); + assertEquals(3F, TERTIARY_LIST.getFloat(2)); + + try { + TERTIARY_LIST.get(-1); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + + try { + TERTIARY_LIST.get(3); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + public void testSize() { + assertEquals(0, FloatArrayList.emptyList().size()); + assertEquals(1, UNARY_LIST.size()); + assertEquals(3, TERTIARY_LIST.size()); + + list.addFloat(2); + list.addFloat(4); + list.addFloat(6); + list.addFloat(8); + assertEquals(4, list.size()); + + list.remove(0); + assertEquals(3, list.size()); + + list.add(16F); + assertEquals(4, list.size()); + } + + public void testSet() { + list.addFloat(2); + list.addFloat(4); + + assertEquals(2F, (float) list.set(0, 0F)); + assertEquals(0F, list.getFloat(0)); + + assertEquals(4F, (float) list.set(1, 0F)); + assertEquals(0F, list.getFloat(1)); + + try { + list.set(-1, 0F); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + + try { + list.set(2, 0F); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + public void testSetFloat() { + list.addFloat(2); + list.addFloat(4); + + assertEquals(2F, list.setFloat(0, 0)); + assertEquals(0F, list.getFloat(0)); + + assertEquals(4F, list.setFloat(1, 0)); + assertEquals(0F, list.getFloat(1)); + + try { + list.setFloat(-1, 0); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + + try { + list.setFloat(2, 0); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + public void testAdd() { + assertEquals(0, list.size()); + + assertTrue(list.add(2F)); + assertEquals(asList(2F), list); + + assertTrue(list.add(3F)); + list.add(0, 4F); + assertEquals(asList(4F, 2F, 3F), list); + + list.add(0, 1F); + list.add(0, 0F); + // Force a resize by getting up to 11 elements. + for (int i = 0; i < 6; i++) { + list.add(Float.valueOf(5 + i)); + } + assertEquals(asList(0F, 1F, 4F, 2F, 3F, 5F, 6F, 7F, 8F, 9F, 10F), list); + + try { + list.add(-1, 5F); + } catch (IndexOutOfBoundsException e) { + // expected + } + + try { + list.add(4, 5F); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + public void testAddFloat() { + assertEquals(0, list.size()); + + list.addFloat(2); + assertEquals(asList(2F), list); + + list.addFloat(3); + assertEquals(asList(2F, 3F), list); + } + + public void testAddAll() { + assertEquals(0, list.size()); + + assertTrue(list.addAll(Collections.singleton(1F))); + assertEquals(1, list.size()); + assertEquals(1F, (float) list.get(0)); + assertEquals(1F, list.getFloat(0)); + + assertTrue(list.addAll(asList(2F, 3F, 4F, 5F, 6F))); + assertEquals(asList(1F, 2F, 3F, 4F, 5F, 6F), list); + + assertTrue(list.addAll(TERTIARY_LIST)); + assertEquals(asList(1F, 2F, 3F, 4F, 5F, 6F, 1F, 2F, 3F), list); + + assertFalse(list.addAll(Collections.<Float>emptyList())); + assertFalse(list.addAll(FloatArrayList.emptyList())); + } + + public void testRemove() { + list.addAll(TERTIARY_LIST); + assertEquals(1F, (float) list.remove(0)); + assertEquals(asList(2F, 3F), list); + + assertTrue(list.remove(Float.valueOf(3))); + assertEquals(asList(2F), list); + + assertFalse(list.remove(Float.valueOf(3))); + assertEquals(asList(2F), list); + + assertEquals(2F, (float) list.remove(0)); + assertEquals(asList(), list); + + try { + list.remove(-1); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + + try { + list.remove(0); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + private void assertImmutable(FloatArrayList list) { + if (list.contains(1F)) { + throw new RuntimeException("Cannot test the immutability of lists that contain 1."); + } + + try { + list.add(1F); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.add(0, 1F); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(Collections.<Float>emptyList()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(Collections.singletonList(1F)); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(new FloatArrayList()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(UNARY_LIST); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(0, Collections.singleton(1F)); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(0, UNARY_LIST); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(0, Collections.<Float>emptyList()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addFloat(0); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.clear(); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.remove(1); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.remove(new Object()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.removeAll(Collections.<Float>emptyList()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.removeAll(Collections.singleton(1F)); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.removeAll(UNARY_LIST); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.retainAll(Collections.<Float>emptyList()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.retainAll(Collections.singleton(1F)); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.retainAll(UNARY_LIST); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.set(0, 0F); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.setFloat(0, 0); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + } + + private static FloatArrayList newImmutableFloatArrayList(int... elements) { + FloatArrayList list = new FloatArrayList(); + for (int element : elements) { + list.addFloat(element); + } + list.makeImmutable(); + return list; + } +} diff --git a/java/core/src/test/java/com/google/protobuf/ForceFieldBuildersPreRun.java b/java/core/src/test/java/com/google/protobuf/ForceFieldBuildersPreRun.java new file mode 100644 index 00000000..a92ba374 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/ForceFieldBuildersPreRun.java @@ -0,0 +1,48 @@ +// 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; + +/** + * A prerun for a test suite that allows running the full protocol buffer + * tests in a mode that disables the optimization for not using + * {@link RepeatedFieldBuilder} and {@link SingleFieldBuilder} until they are + * requested. This allows us to run all the tests through both code paths + * and ensures that both code paths produce identical results. + * + * @author jonp@google.com (Jon Perlow) + */ +public class ForceFieldBuildersPreRun implements Runnable { + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public void run() { + GeneratedMessage.enableAlwaysUseFieldBuildersForTesting(); + } +} diff --git a/java/core/src/test/java/com/google/protobuf/GeneratedMessageTest.java b/java/core/src/test/java/com/google/protobuf/GeneratedMessageTest.java new file mode 100644 index 00000000..70812b95 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/GeneratedMessageTest.java @@ -0,0 +1,1661 @@ +// 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.UnittestLite.TestAllExtensionsLite; +import com.google.protobuf.test.UnittestImport; +import protobuf_unittest.EnumWithNoOuter; +import protobuf_unittest.MessageWithNoOuter; +import protobuf_unittest.MultipleFilesTestProto; +import protobuf_unittest.NestedExtension.MyNestedExtension; +import protobuf_unittest.NestedExtensionLite.MyNestedExtensionLite; +import protobuf_unittest.NonNestedExtension; +import protobuf_unittest.NonNestedExtension.MessageToBeExtended; +import protobuf_unittest.NonNestedExtension.MyNonNestedExtension; +import protobuf_unittest.NonNestedExtensionLite; +import protobuf_unittest.NonNestedExtensionLite.MessageLiteToBeExtended; +import protobuf_unittest.NonNestedExtensionLite.MyNonNestedExtensionLite; +import protobuf_unittest.OuterClassNameTest2OuterClass; +import protobuf_unittest.OuterClassNameTest3OuterClass; +import protobuf_unittest.OuterClassNameTestOuterClass; +import protobuf_unittest.ServiceWithNoOuter; +import protobuf_unittest.UnittestOptimizeFor.TestOptimizedForSize; +import protobuf_unittest.UnittestOptimizeFor.TestOptionalOptimizedForSize; +import protobuf_unittest.UnittestOptimizeFor.TestRequiredOptimizedForSize; +import protobuf_unittest.UnittestProto; +import protobuf_unittest.UnittestProto.ForeignEnum; +import protobuf_unittest.UnittestProto.ForeignMessage; +import protobuf_unittest.UnittestProto.ForeignMessageOrBuilder; +import protobuf_unittest.UnittestProto.NestedTestAllTypes; +import protobuf_unittest.UnittestProto.TestAllExtensions; +import protobuf_unittest.UnittestProto.TestAllTypes; +import protobuf_unittest.UnittestProto.TestAllTypes.NestedMessage; +import protobuf_unittest.UnittestProto.TestAllTypesOrBuilder; +import protobuf_unittest.UnittestProto.TestExtremeDefaultValues; +import protobuf_unittest.UnittestProto.TestOneof2; +import protobuf_unittest.UnittestProto.TestPackedTypes; +import protobuf_unittest.UnittestProto.TestUnpackedTypes; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * Unit test for generated messages and generated code. See also + * {@link MessageTest}, which tests some generated message functionality. + * + * @author kenton@google.com Kenton Varda + */ +public class GeneratedMessageTest extends TestCase { + TestUtil.ReflectionTester reflectionTester = + new TestUtil.ReflectionTester(TestAllTypes.getDescriptor(), null); + + public void testDefaultInstance() throws Exception { + assertSame(TestAllTypes.getDefaultInstance(), + TestAllTypes.getDefaultInstance().getDefaultInstanceForType()); + assertSame(TestAllTypes.getDefaultInstance(), + TestAllTypes.newBuilder().getDefaultInstanceForType()); + } + + public void testMessageOrBuilder() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TestUtil.setAllFields(builder); + TestAllTypes message = builder.build(); + TestUtil.assertAllFieldsSet(message); + } + + public void testUsingBuilderMultipleTimes() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + // primitive field scalar and repeated + builder.setOptionalSfixed64(100); + builder.addRepeatedInt32(100); + // enum field scalar and repeated + builder.setOptionalImportEnum(UnittestImport.ImportEnum.IMPORT_BAR); + builder.addRepeatedImportEnum(UnittestImport.ImportEnum.IMPORT_BAR); + // proto field scalar and repeated + builder.setOptionalForeignMessage(ForeignMessage.newBuilder().setC(1)); + builder.addRepeatedForeignMessage(ForeignMessage.newBuilder().setC(1)); + + TestAllTypes value1 = builder.build(); + + assertEquals(100, value1.getOptionalSfixed64()); + assertEquals(100, value1.getRepeatedInt32(0)); + assertEquals(UnittestImport.ImportEnum.IMPORT_BAR, + value1.getOptionalImportEnum()); + assertEquals(UnittestImport.ImportEnum.IMPORT_BAR, + value1.getRepeatedImportEnum(0)); + assertEquals(1, value1.getOptionalForeignMessage().getC()); + assertEquals(1, value1.getRepeatedForeignMessage(0).getC()); + + // Make sure that builder didn't update previously created values + builder.setOptionalSfixed64(200); + builder.setRepeatedInt32(0, 200); + builder.setOptionalImportEnum(UnittestImport.ImportEnum.IMPORT_FOO); + builder.setRepeatedImportEnum(0, UnittestImport.ImportEnum.IMPORT_FOO); + builder.setOptionalForeignMessage(ForeignMessage.newBuilder().setC(2)); + builder.setRepeatedForeignMessage(0, ForeignMessage.newBuilder().setC(2)); + + TestAllTypes value2 = builder.build(); + + // Make sure value1 didn't change. + assertEquals(100, value1.getOptionalSfixed64()); + assertEquals(100, value1.getRepeatedInt32(0)); + assertEquals(UnittestImport.ImportEnum.IMPORT_BAR, + value1.getOptionalImportEnum()); + assertEquals(UnittestImport.ImportEnum.IMPORT_BAR, + value1.getRepeatedImportEnum(0)); + assertEquals(1, value1.getOptionalForeignMessage().getC()); + assertEquals(1, value1.getRepeatedForeignMessage(0).getC()); + + // Make sure value2 is correct + assertEquals(200, value2.getOptionalSfixed64()); + assertEquals(200, value2.getRepeatedInt32(0)); + assertEquals(UnittestImport.ImportEnum.IMPORT_FOO, + value2.getOptionalImportEnum()); + assertEquals(UnittestImport.ImportEnum.IMPORT_FOO, + value2.getRepeatedImportEnum(0)); + assertEquals(2, value2.getOptionalForeignMessage().getC()); + assertEquals(2, value2.getRepeatedForeignMessage(0).getC()); + } + + public void testProtosShareRepeatedArraysIfDidntChange() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + builder.addRepeatedInt32(100); + builder.addRepeatedForeignMessage(ForeignMessage.getDefaultInstance()); + + TestAllTypes value1 = builder.build(); + TestAllTypes value2 = value1.toBuilder().build(); + + assertSame(value1.getRepeatedInt32List(), value2.getRepeatedInt32List()); + assertSame(value1.getRepeatedForeignMessageList(), + value2.getRepeatedForeignMessageList()); + } + + public void testRepeatedArraysAreImmutable() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + builder.addRepeatedInt32(100); + builder.addRepeatedImportEnum(UnittestImport.ImportEnum.IMPORT_BAR); + builder.addRepeatedForeignMessage(ForeignMessage.getDefaultInstance()); + assertIsUnmodifiable(builder.getRepeatedInt32List()); + assertIsUnmodifiable(builder.getRepeatedImportEnumList()); + assertIsUnmodifiable(builder.getRepeatedForeignMessageList()); + assertIsUnmodifiable(builder.getRepeatedFloatList()); + + + TestAllTypes value = builder.build(); + assertIsUnmodifiable(value.getRepeatedInt32List()); + assertIsUnmodifiable(value.getRepeatedImportEnumList()); + assertIsUnmodifiable(value.getRepeatedForeignMessageList()); + assertIsUnmodifiable(value.getRepeatedFloatList()); + } + + public void testParsedMessagesAreImmutable() throws Exception { + TestAllTypes value = TestAllTypes.parser().parseFrom(TestUtil.getAllSet().toByteString()); + assertIsUnmodifiable(value.getRepeatedInt32List()); + assertIsUnmodifiable(value.getRepeatedInt64List()); + assertIsUnmodifiable(value.getRepeatedUint32List()); + assertIsUnmodifiable(value.getRepeatedUint64List()); + assertIsUnmodifiable(value.getRepeatedSint32List()); + assertIsUnmodifiable(value.getRepeatedSint64List()); + assertIsUnmodifiable(value.getRepeatedFixed32List()); + assertIsUnmodifiable(value.getRepeatedFixed64List()); + assertIsUnmodifiable(value.getRepeatedSfixed32List()); + assertIsUnmodifiable(value.getRepeatedSfixed64List()); + assertIsUnmodifiable(value.getRepeatedFloatList()); + assertIsUnmodifiable(value.getRepeatedDoubleList()); + assertIsUnmodifiable(value.getRepeatedBoolList()); + assertIsUnmodifiable(value.getRepeatedStringList()); + assertIsUnmodifiable(value.getRepeatedBytesList()); + assertIsUnmodifiable(value.getRepeatedGroupList()); + assertIsUnmodifiable(value.getRepeatedNestedMessageList()); + assertIsUnmodifiable(value.getRepeatedForeignMessageList()); + assertIsUnmodifiable(value.getRepeatedImportMessageList()); + assertIsUnmodifiable(value.getRepeatedNestedEnumList()); + assertIsUnmodifiable(value.getRepeatedForeignEnumList()); + assertIsUnmodifiable(value.getRepeatedImportEnumList()); + } + + private void assertIsUnmodifiable(List<?> list) { + if (list == Collections.emptyList()) { + // OKAY -- Need to check this b/c EmptyList allows you to call clear. + } else { + try { + list.clear(); + fail("List wasn't immutable"); + } catch (UnsupportedOperationException e) { + // good + } + } + } + + public void testSettersRejectNull() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + try { + builder.setOptionalString(null); + fail("Exception was not thrown"); + } catch (NullPointerException e) { + // We expect this exception. + } + try { + builder.setOptionalBytes(null); + fail("Exception was not thrown"); + } catch (NullPointerException e) { + // We expect this exception. + } + try { + builder.setOptionalNestedMessage((TestAllTypes.NestedMessage) null); + fail("Exception was not thrown"); + } catch (NullPointerException e) { + // We expect this exception. + } + try { + builder.setOptionalNestedMessage( + (TestAllTypes.NestedMessage.Builder) null); + fail("Exception was not thrown"); + } catch (NullPointerException e) { + // We expect this exception. + } + try { + builder.setOptionalNestedEnum(null); + fail("Exception was not thrown"); + } catch (NullPointerException e) { + // We expect this exception. + } + try { + builder.addRepeatedString(null); + fail("Exception was not thrown"); + } catch (NullPointerException e) { + // We expect this exception. + } + try { + builder.addRepeatedBytes(null); + fail("Exception was not thrown"); + } catch (NullPointerException e) { + // We expect this exception. + } + try { + builder.addRepeatedNestedMessage((TestAllTypes.NestedMessage) null); + fail("Exception was not thrown"); + } catch (NullPointerException e) { + // We expect this exception. + } + try { + builder.addRepeatedNestedMessage( + (TestAllTypes.NestedMessage.Builder) null); + fail("Exception was not thrown"); + } catch (NullPointerException e) { + // We expect this exception. + } + try { + builder.addRepeatedNestedEnum(null); + fail("Exception was not thrown"); + } catch (NullPointerException e) { + // We expect this exception. + } + } + + public void testRepeatedSetters() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TestUtil.setAllFields(builder); + TestUtil.modifyRepeatedFields(builder); + TestAllTypes message = builder.build(); + TestUtil.assertRepeatedFieldsModified(message); + } + + public void testRepeatedSettersRejectNull() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + + builder.addRepeatedString("one"); + builder.addRepeatedString("two"); + try { + builder.setRepeatedString(1, null); + fail("Exception was not thrown"); + } catch (NullPointerException e) { + // We expect this exception. + } + + builder.addRepeatedBytes(TestUtil.toBytes("one")); + builder.addRepeatedBytes(TestUtil.toBytes("two")); + try { + builder.setRepeatedBytes(1, null); + fail("Exception was not thrown"); + } catch (NullPointerException e) { + // We expect this exception. + } + + builder.addRepeatedNestedMessage( + TestAllTypes.NestedMessage.newBuilder().setBb(218).build()); + builder.addRepeatedNestedMessage( + TestAllTypes.NestedMessage.newBuilder().setBb(456).build()); + try { + builder.setRepeatedNestedMessage(1, (TestAllTypes.NestedMessage) null); + fail("Exception was not thrown"); + } catch (NullPointerException e) { + // We expect this exception. + } + try { + builder.setRepeatedNestedMessage( + 1, (TestAllTypes.NestedMessage.Builder) null); + fail("Exception was not thrown"); + } catch (NullPointerException e) { + // We expect this exception. + } + + builder.addRepeatedNestedEnum(TestAllTypes.NestedEnum.FOO); + builder.addRepeatedNestedEnum(TestAllTypes.NestedEnum.BAR); + try { + builder.setRepeatedNestedEnum(1, null); + fail("Exception was not thrown"); + } catch (NullPointerException e) { + // We expect this exception. + } + } + + public void testRepeatedAppend() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + + builder.addAllRepeatedInt32(Arrays.asList(1, 2, 3, 4)); + builder.addAllRepeatedForeignEnum(Arrays.asList(ForeignEnum.FOREIGN_BAZ)); + + ForeignMessage foreignMessage = + ForeignMessage.newBuilder().setC(12).build(); + builder.addAllRepeatedForeignMessage(Arrays.asList(foreignMessage)); + + TestAllTypes message = builder.build(); + assertEquals(message.getRepeatedInt32List(), Arrays.asList(1, 2, 3, 4)); + assertEquals(message.getRepeatedForeignEnumList(), + Arrays.asList(ForeignEnum.FOREIGN_BAZ)); + assertEquals(1, message.getRepeatedForeignMessageCount()); + assertEquals(12, message.getRepeatedForeignMessage(0).getC()); + } + + public void testRepeatedAppendRejectsNull() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + + ForeignMessage foreignMessage = + ForeignMessage.newBuilder().setC(12).build(); + try { + builder.addAllRepeatedForeignMessage( + Arrays.asList(foreignMessage, (ForeignMessage) null)); + fail("Exception was not thrown"); + } catch (NullPointerException e) { + // We expect this exception. + } + + try { + builder.addAllRepeatedForeignEnum( + Arrays.asList(ForeignEnum.FOREIGN_BAZ, null)); + fail("Exception was not thrown"); + } catch (NullPointerException e) { + // We expect this exception. + } + + try { + builder.addAllRepeatedString(Arrays.asList("one", null)); + fail("Exception was not thrown"); + } catch (NullPointerException e) { + // We expect this exception. + } + + try { + builder.addAllRepeatedBytes(Arrays.asList(TestUtil.toBytes("one"), null)); + fail("Exception was not thrown"); + } catch (NullPointerException e) { + // We expect this exception. + } + } + + public void testRepeatedAppendIterateOnlyOnce() throws Exception { + // Create a Iterable that can only be iterated once. + Iterable<String> stringIterable = new Iterable<String>() { + private boolean called = false; + @Override + public Iterator<String> iterator() { + if (called) { + throw new IllegalStateException(); + } + called = true; + return Arrays.asList("one", "two", "three").iterator(); + } + }; + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + builder.addAllRepeatedString(stringIterable); + assertEquals(3, builder.getRepeatedStringCount()); + assertEquals("one", builder.getRepeatedString(0)); + assertEquals("two", builder.getRepeatedString(1)); + assertEquals("three", builder.getRepeatedString(2)); + + try { + builder.addAllRepeatedString(stringIterable); + fail("Exception was not thrown"); + } catch (IllegalStateException e) { + // We expect this exception. + } + } + + public void testMergeFromOtherRejectsNull() throws Exception { + try { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + builder.mergeFrom((TestAllTypes) null); + fail("Exception was not thrown"); + } catch (NullPointerException e) { + // We expect this exception. + } + } + + public void testSettingForeignMessageUsingBuilder() throws Exception { + TestAllTypes message = TestAllTypes.newBuilder() + // Pass builder for foreign message instance. + .setOptionalForeignMessage(ForeignMessage.newBuilder().setC(123)) + .build(); + TestAllTypes expectedMessage = TestAllTypes.newBuilder() + // Create expected version passing foreign message instance explicitly. + .setOptionalForeignMessage( + ForeignMessage.newBuilder().setC(123).build()) + .build(); + // TODO(ngd): Upgrade to using real #equals method once implemented + assertEquals(expectedMessage.toString(), message.toString()); + } + + public void testSettingRepeatedForeignMessageUsingBuilder() throws Exception { + TestAllTypes message = TestAllTypes.newBuilder() + // Pass builder for foreign message instance. + .addRepeatedForeignMessage(ForeignMessage.newBuilder().setC(456)) + .build(); + TestAllTypes expectedMessage = TestAllTypes.newBuilder() + // Create expected version passing foreign message instance explicitly. + .addRepeatedForeignMessage( + ForeignMessage.newBuilder().setC(456).build()) + .build(); + assertEquals(expectedMessage.toString(), message.toString()); + } + + public void testDefaults() throws Exception { + TestUtil.assertClear(TestAllTypes.getDefaultInstance()); + TestUtil.assertClear(TestAllTypes.newBuilder().build()); + + TestExtremeDefaultValues message = + TestExtremeDefaultValues.getDefaultInstance(); + assertEquals("\u1234", message.getUtf8String()); + assertEquals(Double.POSITIVE_INFINITY, message.getInfDouble()); + assertEquals(Double.NEGATIVE_INFINITY, message.getNegInfDouble()); + assertTrue(Double.isNaN(message.getNanDouble())); + assertEquals(Float.POSITIVE_INFINITY, message.getInfFloat()); + assertEquals(Float.NEGATIVE_INFINITY, message.getNegInfFloat()); + assertTrue(Float.isNaN(message.getNanFloat())); + assertEquals("? ? ?? ?? ??? ??/ ??-", message.getCppTrigraph()); + } + + public void testClear() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TestUtil.assertClear(builder); + TestUtil.setAllFields(builder); + builder.clear(); + TestUtil.assertClear(builder); + } + + public void testReflectionGetters() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TestUtil.setAllFields(builder); + reflectionTester.assertAllFieldsSetViaReflection(builder); + + TestAllTypes message = builder.build(); + reflectionTester.assertAllFieldsSetViaReflection(message); + } + + public void testReflectionSetters() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + reflectionTester.setAllFieldsViaReflection(builder); + TestUtil.assertAllFieldsSet(builder); + + TestAllTypes message = builder.build(); + TestUtil.assertAllFieldsSet(message); + } + + public void testReflectionSettersRejectNull() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + reflectionTester.assertReflectionSettersRejectNull(builder); + } + + public void testReflectionRepeatedSetters() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + reflectionTester.setAllFieldsViaReflection(builder); + reflectionTester.modifyRepeatedFieldsViaReflection(builder); + TestUtil.assertRepeatedFieldsModified(builder); + + TestAllTypes message = builder.build(); + TestUtil.assertRepeatedFieldsModified(message); + } + + public void testReflectionRepeatedSettersRejectNull() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + reflectionTester.assertReflectionRepeatedSettersRejectNull(builder); + } + + public void testReflectionDefaults() throws Exception { + reflectionTester.assertClearViaReflection( + TestAllTypes.getDefaultInstance()); + reflectionTester.assertClearViaReflection( + TestAllTypes.newBuilder().build()); + } + + public void testReflectionGetOneof() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + reflectionTester.setAllFieldsViaReflection(builder); + Descriptors.OneofDescriptor oneof = + TestAllTypes.getDescriptor().getOneofs().get(0); + Descriptors.FieldDescriptor field = + TestAllTypes.getDescriptor().findFieldByName("oneof_bytes"); + assertSame(field, builder.getOneofFieldDescriptor(oneof)); + + TestAllTypes message = builder.build(); + assertSame(field, message.getOneofFieldDescriptor(oneof)); + } + + public void testReflectionClearOneof() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + reflectionTester.setAllFieldsViaReflection(builder); + Descriptors.OneofDescriptor oneof = + TestAllTypes.getDescriptor().getOneofs().get(0); + Descriptors.FieldDescriptor field = + TestAllTypes.getDescriptor().findFieldByName("oneof_bytes"); + + assertTrue(builder.hasOneof(oneof)); + assertTrue(builder.hasField(field)); + builder.clearOneof(oneof); + assertFalse(builder.hasOneof(oneof)); + assertFalse(builder.hasField(field)); + } + + public void testEnumInterface() throws Exception { + assertTrue(TestAllTypes.getDefaultInstance().getDefaultNestedEnum() + instanceof ProtocolMessageEnum); + } + + public void testEnumMap() throws Exception { + Internal.EnumLiteMap<ForeignEnum> map = ForeignEnum.internalGetValueMap(); + + for (ForeignEnum value : ForeignEnum.values()) { + assertEquals(value, map.findValueByNumber(value.getNumber())); + } + + assertTrue(map.findValueByNumber(12345) == null); + } + + public void testParsePackedToUnpacked() throws Exception { + TestUnpackedTypes.Builder builder = TestUnpackedTypes.newBuilder(); + TestUnpackedTypes message = + builder.mergeFrom(TestUtil.getPackedSet().toByteString()).build(); + TestUtil.assertUnpackedFieldsSet(message); + } + + public void testParseUnpackedToPacked() throws Exception { + TestPackedTypes.Builder builder = TestPackedTypes.newBuilder(); + TestPackedTypes message = + builder.mergeFrom(TestUtil.getUnpackedSet().toByteString()).build(); + TestUtil.assertPackedFieldsSet(message); + } + + // ================================================================= + // Extensions. + + TestUtil.ReflectionTester extensionsReflectionTester = + new TestUtil.ReflectionTester(TestAllExtensions.getDescriptor(), + TestUtil.getExtensionRegistry()); + + public void testExtensionMessageOrBuilder() throws Exception { + TestAllExtensions.Builder builder = TestAllExtensions.newBuilder(); + TestUtil.setAllExtensions(builder); + TestAllExtensions message = builder.build(); + TestUtil.assertAllExtensionsSet(message); + } + + public void testExtensionRepeatedSetters() throws Exception { + TestAllExtensions.Builder builder = TestAllExtensions.newBuilder(); + TestUtil.setAllExtensions(builder); + TestUtil.modifyRepeatedExtensions(builder); + TestAllExtensions message = builder.build(); + TestUtil.assertRepeatedExtensionsModified(message); + } + + public void testExtensionDefaults() throws Exception { + TestUtil.assertExtensionsClear(TestAllExtensions.getDefaultInstance()); + TestUtil.assertExtensionsClear(TestAllExtensions.newBuilder().build()); + } + + public void testExtensionReflectionGetters() throws Exception { + TestAllExtensions.Builder builder = TestAllExtensions.newBuilder(); + TestUtil.setAllExtensions(builder); + extensionsReflectionTester.assertAllFieldsSetViaReflection(builder); + + TestAllExtensions message = builder.build(); + extensionsReflectionTester.assertAllFieldsSetViaReflection(message); + } + + public void testExtensionReflectionSetters() throws Exception { + TestAllExtensions.Builder builder = TestAllExtensions.newBuilder(); + extensionsReflectionTester.setAllFieldsViaReflection(builder); + TestUtil.assertAllExtensionsSet(builder); + + TestAllExtensions message = builder.build(); + TestUtil.assertAllExtensionsSet(message); + } + + public void testExtensionReflectionSettersRejectNull() throws Exception { + TestAllExtensions.Builder builder = TestAllExtensions.newBuilder(); + extensionsReflectionTester.assertReflectionSettersRejectNull(builder); + } + + public void testExtensionReflectionRepeatedSetters() throws Exception { + TestAllExtensions.Builder builder = TestAllExtensions.newBuilder(); + extensionsReflectionTester.setAllFieldsViaReflection(builder); + extensionsReflectionTester.modifyRepeatedFieldsViaReflection(builder); + TestUtil.assertRepeatedExtensionsModified(builder); + + TestAllExtensions message = builder.build(); + TestUtil.assertRepeatedExtensionsModified(message); + } + + public void testExtensionReflectionRepeatedSettersRejectNull() + throws Exception { + TestAllExtensions.Builder builder = TestAllExtensions.newBuilder(); + extensionsReflectionTester.assertReflectionRepeatedSettersRejectNull( + builder); + } + + public void testExtensionReflectionDefaults() throws Exception { + extensionsReflectionTester.assertClearViaReflection( + TestAllExtensions.getDefaultInstance()); + extensionsReflectionTester.assertClearViaReflection( + TestAllExtensions.newBuilder().build()); + } + + public void testClearExtension() throws Exception { + // clearExtension() is not actually used in TestUtil, so try it manually. + assertFalse( + TestAllExtensions.newBuilder() + .setExtension(UnittestProto.optionalInt32Extension, 1) + .clearExtension(UnittestProto.optionalInt32Extension) + .hasExtension(UnittestProto.optionalInt32Extension)); + assertEquals(0, + TestAllExtensions.newBuilder() + .addExtension(UnittestProto.repeatedInt32Extension, 1) + .clearExtension(UnittestProto.repeatedInt32Extension) + .getExtensionCount(UnittestProto.repeatedInt32Extension)); + } + + public void testExtensionCopy() throws Exception { + TestAllExtensions original = TestUtil.getAllExtensionsSet(); + TestAllExtensions copy = TestAllExtensions.newBuilder(original).build(); + TestUtil.assertAllExtensionsSet(copy); + } + + public void testExtensionMergeFrom() throws Exception { + TestAllExtensions original = + TestAllExtensions.newBuilder() + .setExtension(UnittestProto.optionalInt32Extension, 1).build(); + TestAllExtensions merged = + TestAllExtensions.newBuilder().mergeFrom(original).build(); + assertTrue(merged.hasExtension(UnittestProto.optionalInt32Extension)); + assertEquals( + 1, (int) merged.getExtension(UnittestProto.optionalInt32Extension)); + } + + // ================================================================= + // Lite Extensions. + + // We test lite extensions directly because they have a separate + // implementation from full extensions. In contrast, we do not test + // lite fields directly since they are implemented exactly the same as + // regular fields. + + public void testLiteExtensionMessageOrBuilder() throws Exception { + TestAllExtensionsLite.Builder builder = TestAllExtensionsLite.newBuilder(); + TestUtil.setAllExtensions(builder); + TestUtil.assertAllExtensionsSet(builder); + + TestAllExtensionsLite message = builder.build(); + TestUtil.assertAllExtensionsSet(message); + } + + public void testLiteExtensionRepeatedSetters() throws Exception { + TestAllExtensionsLite.Builder builder = TestAllExtensionsLite.newBuilder(); + TestUtil.setAllExtensions(builder); + TestUtil.modifyRepeatedExtensions(builder); + TestUtil.assertRepeatedExtensionsModified(builder); + + TestAllExtensionsLite message = builder.build(); + TestUtil.assertRepeatedExtensionsModified(message); + } + + public void testLiteExtensionDefaults() throws Exception { + TestUtil.assertExtensionsClear(TestAllExtensionsLite.getDefaultInstance()); + TestUtil.assertExtensionsClear(TestAllExtensionsLite.newBuilder().build()); + } + + public void testClearLiteExtension() throws Exception { + // clearExtension() is not actually used in TestUtil, so try it manually. + assertFalse( + TestAllExtensionsLite.newBuilder() + .setExtension(UnittestLite.optionalInt32ExtensionLite, 1) + .clearExtension(UnittestLite.optionalInt32ExtensionLite) + .hasExtension(UnittestLite.optionalInt32ExtensionLite)); + assertEquals(0, + TestAllExtensionsLite.newBuilder() + .addExtension(UnittestLite.repeatedInt32ExtensionLite, 1) + .clearExtension(UnittestLite.repeatedInt32ExtensionLite) + .getExtensionCount(UnittestLite.repeatedInt32ExtensionLite)); + } + + public void testLiteExtensionCopy() throws Exception { + TestAllExtensionsLite original = TestUtil.getAllLiteExtensionsSet(); + TestAllExtensionsLite copy = + TestAllExtensionsLite.newBuilder(original).build(); + TestUtil.assertAllExtensionsSet(copy); + } + + public void testLiteExtensionMergeFrom() throws Exception { + TestAllExtensionsLite original = + TestAllExtensionsLite.newBuilder() + .setExtension(UnittestLite.optionalInt32ExtensionLite, 1).build(); + TestAllExtensionsLite merged = + TestAllExtensionsLite.newBuilder().mergeFrom(original).build(); + assertTrue(merged.hasExtension(UnittestLite.optionalInt32ExtensionLite)); + assertEquals( + 1, (int) merged.getExtension(UnittestLite.optionalInt32ExtensionLite)); + } + + // ================================================================= + // multiple_files_test + + // Test that custom options of an file level enum are properly initialized. + // This test needs to be put before any other access to MultipleFilesTestProto + // or messages defined in multiple_files_test.proto because the class loading + // order affects initialization process of custom options. + public void testEnumValueOptionsInMultipleFilesMode() throws Exception { + assertEquals(12345, EnumWithNoOuter.FOO.getValueDescriptor().getOptions() + .getExtension(MultipleFilesTestProto.enumValueOption).intValue()); + } + + public void testMultipleFilesOption() throws Exception { + // We mostly just want to check that things compile. + MessageWithNoOuter message = + MessageWithNoOuter.newBuilder() + .setNested(MessageWithNoOuter.NestedMessage.newBuilder().setI(1)) + .addForeign(TestAllTypes.newBuilder().setOptionalInt32(1)) + .setNestedEnum(MessageWithNoOuter.NestedEnum.BAZ) + .setForeignEnum(EnumWithNoOuter.BAR) + .build(); + assertEquals(message, MessageWithNoOuter.parseFrom(message.toByteString())); + + assertEquals(MultipleFilesTestProto.getDescriptor(), + MessageWithNoOuter.getDescriptor().getFile()); + + Descriptors.FieldDescriptor field = + MessageWithNoOuter.getDescriptor().findFieldByName("foreign_enum"); + assertEquals(EnumWithNoOuter.BAR.getValueDescriptor(), + message.getField(field)); + + assertEquals(MultipleFilesTestProto.getDescriptor(), + ServiceWithNoOuter.getDescriptor().getFile()); + + assertFalse( + TestAllExtensions.getDefaultInstance().hasExtension( + MultipleFilesTestProto.extensionWithOuter)); + } + + public void testOptionalFieldWithRequiredSubfieldsOptimizedForSize() + throws Exception { + TestOptionalOptimizedForSize message = + TestOptionalOptimizedForSize.getDefaultInstance(); + assertTrue(message.isInitialized()); + + message = TestOptionalOptimizedForSize.newBuilder().setO( + TestRequiredOptimizedForSize.newBuilder().buildPartial() + ).buildPartial(); + assertFalse(message.isInitialized()); + + message = TestOptionalOptimizedForSize.newBuilder().setO( + TestRequiredOptimizedForSize.newBuilder().setX(5).buildPartial() + ).buildPartial(); + assertTrue(message.isInitialized()); + } + + public void testUninitializedExtensionInOptimizedForSize() + throws Exception { + TestOptimizedForSize.Builder builder = TestOptimizedForSize.newBuilder(); + builder.setExtension(TestOptimizedForSize.testExtension2, + TestRequiredOptimizedForSize.newBuilder().buildPartial()); + assertFalse(builder.isInitialized()); + assertFalse(builder.buildPartial().isInitialized()); + + builder = TestOptimizedForSize.newBuilder(); + builder.setExtension(TestOptimizedForSize.testExtension2, + TestRequiredOptimizedForSize.newBuilder().setX(10).buildPartial()); + assertTrue(builder.isInitialized()); + assertTrue(builder.buildPartial().isInitialized()); + } + + public void testToBuilder() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TestUtil.setAllFields(builder); + TestAllTypes message = builder.build(); + TestUtil.assertAllFieldsSet(message); + TestUtil.assertAllFieldsSet(message.toBuilder().build()); + } + + public void testFieldConstantValues() throws Exception { + assertEquals(TestAllTypes.NestedMessage.BB_FIELD_NUMBER, 1); + assertEquals(TestAllTypes.OPTIONAL_INT32_FIELD_NUMBER, 1); + assertEquals(TestAllTypes.OPTIONALGROUP_FIELD_NUMBER, 16); + assertEquals(TestAllTypes.OPTIONAL_NESTED_MESSAGE_FIELD_NUMBER, 18); + assertEquals(TestAllTypes.OPTIONAL_NESTED_ENUM_FIELD_NUMBER, 21); + assertEquals(TestAllTypes.REPEATED_INT32_FIELD_NUMBER, 31); + assertEquals(TestAllTypes.REPEATEDGROUP_FIELD_NUMBER, 46); + assertEquals(TestAllTypes.REPEATED_NESTED_MESSAGE_FIELD_NUMBER, 48); + assertEquals(TestAllTypes.REPEATED_NESTED_ENUM_FIELD_NUMBER, 51); + } + + public void testExtensionConstantValues() throws Exception { + assertEquals(UnittestProto.TestRequired.SINGLE_FIELD_NUMBER, 1000); + assertEquals(UnittestProto.TestRequired.MULTI_FIELD_NUMBER, 1001); + assertEquals(UnittestProto.OPTIONAL_INT32_EXTENSION_FIELD_NUMBER, 1); + assertEquals(UnittestProto.OPTIONALGROUP_EXTENSION_FIELD_NUMBER, 16); + assertEquals( + UnittestProto.OPTIONAL_NESTED_MESSAGE_EXTENSION_FIELD_NUMBER, 18); + assertEquals(UnittestProto.OPTIONAL_NESTED_ENUM_EXTENSION_FIELD_NUMBER, 21); + assertEquals(UnittestProto.REPEATED_INT32_EXTENSION_FIELD_NUMBER, 31); + assertEquals(UnittestProto.REPEATEDGROUP_EXTENSION_FIELD_NUMBER, 46); + assertEquals( + UnittestProto.REPEATED_NESTED_MESSAGE_EXTENSION_FIELD_NUMBER, 48); + assertEquals(UnittestProto.REPEATED_NESTED_ENUM_EXTENSION_FIELD_NUMBER, 51); + } + + public void testRecursiveMessageDefaultInstance() throws Exception { + UnittestProto.TestRecursiveMessage message = + UnittestProto.TestRecursiveMessage.getDefaultInstance(); + assertTrue(message != null); + assertNotNull(message.getA()); + assertTrue(message.getA() == message); + } + + public void testSerialize() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TestUtil.setAllFields(builder); + TestAllTypes expected = builder.build(); + ObjectOutputStream out = new ObjectOutputStream(baos); + try { + out.writeObject(expected); + } finally { + out.close(); + } + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + ObjectInputStream in = new ObjectInputStream(bais); + TestAllTypes actual = (TestAllTypes) in.readObject(); + assertEquals(expected, actual); + } + + public void testSerializePartial() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TestAllTypes expected = builder.buildPartial(); + ObjectOutputStream out = new ObjectOutputStream(baos); + try { + out.writeObject(expected); + } finally { + out.close(); + } + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + ObjectInputStream in = new ObjectInputStream(bais); + TestAllTypes actual = (TestAllTypes) in.readObject(); + assertEquals(expected, actual); + } + + public void testEnumValues() { + assertEquals( + TestAllTypes.NestedEnum.BAR.getNumber(), + TestAllTypes.NestedEnum.BAR_VALUE); + assertEquals( + TestAllTypes.NestedEnum.BAZ.getNumber(), + TestAllTypes.NestedEnum.BAZ_VALUE); + assertEquals( + TestAllTypes.NestedEnum.FOO.getNumber(), + TestAllTypes.NestedEnum.FOO_VALUE); + } + + public void testNonNestedExtensionInitialization() { + assertTrue(NonNestedExtension.nonNestedExtension + .getMessageDefaultInstance() instanceof MyNonNestedExtension); + assertEquals("nonNestedExtension", + NonNestedExtension.nonNestedExtension.getDescriptor().getName()); + } + + public void testNestedExtensionInitialization() { + assertTrue(MyNestedExtension.recursiveExtension.getMessageDefaultInstance() + instanceof MessageToBeExtended); + assertEquals("recursiveExtension", + MyNestedExtension.recursiveExtension.getDescriptor().getName()); + } + + public void testNonNestedExtensionLiteInitialization() { + assertTrue(NonNestedExtensionLite.nonNestedExtensionLite + .getMessageDefaultInstance() instanceof MyNonNestedExtensionLite); + } + + public void testNestedExtensionLiteInitialization() { + assertTrue(MyNestedExtensionLite.recursiveExtensionLite + .getMessageDefaultInstance() instanceof MessageLiteToBeExtended); + } + + public void testInvalidations() throws Exception { + GeneratedMessage.enableAlwaysUseFieldBuildersForTesting(); + TestAllTypes.NestedMessage nestedMessage1 = + TestAllTypes.NestedMessage.newBuilder().build(); + TestAllTypes.NestedMessage nestedMessage2 = + TestAllTypes.NestedMessage.newBuilder().build(); + + // Set all three flavors (enum, primitive, message and singular/repeated) + // and verify no invalidations fired + TestUtil.MockBuilderParent mockParent = new TestUtil.MockBuilderParent(); + + TestAllTypes.Builder builder = (TestAllTypes.Builder) + ((GeneratedMessage) TestAllTypes.getDefaultInstance()). + newBuilderForType(mockParent); + builder.setOptionalInt32(1); + builder.setOptionalNestedEnum(TestAllTypes.NestedEnum.BAR); + builder.setOptionalNestedMessage(nestedMessage1); + builder.addRepeatedInt32(1); + builder.addRepeatedNestedEnum(TestAllTypes.NestedEnum.BAR); + builder.addRepeatedNestedMessage(nestedMessage1); + assertEquals(0, mockParent.getInvalidationCount()); + + // Now tell it we want changes and make sure it's only fired once + // And do this for each flavor + + // primitive single + builder.buildPartial(); + builder.setOptionalInt32(2); + builder.setOptionalInt32(3); + assertEquals(1, mockParent.getInvalidationCount()); + + // enum single + builder.buildPartial(); + builder.setOptionalNestedEnum(TestAllTypes.NestedEnum.BAZ); + builder.setOptionalNestedEnum(TestAllTypes.NestedEnum.BAR); + assertEquals(2, mockParent.getInvalidationCount()); + + // message single + builder.buildPartial(); + builder.setOptionalNestedMessage(nestedMessage2); + builder.setOptionalNestedMessage(nestedMessage1); + assertEquals(3, mockParent.getInvalidationCount()); + + // primitive repeated + builder.buildPartial(); + builder.addRepeatedInt32(2); + builder.addRepeatedInt32(3); + assertEquals(4, mockParent.getInvalidationCount()); + + // enum repeated + builder.buildPartial(); + builder.addRepeatedNestedEnum(TestAllTypes.NestedEnum.BAZ); + builder.addRepeatedNestedEnum(TestAllTypes.NestedEnum.BAZ); + assertEquals(5, mockParent.getInvalidationCount()); + + // message repeated + builder.buildPartial(); + builder.addRepeatedNestedMessage(nestedMessage2); + builder.addRepeatedNestedMessage(nestedMessage1); + assertEquals(6, mockParent.getInvalidationCount()); + + } + + public void testInvalidations_Extensions() throws Exception { + TestUtil.MockBuilderParent mockParent = new TestUtil.MockBuilderParent(); + + TestAllExtensions.Builder builder = (TestAllExtensions.Builder) + ((GeneratedMessage) TestAllExtensions.getDefaultInstance()). + newBuilderForType(mockParent); + + builder.addExtension(UnittestProto.repeatedInt32Extension, 1); + builder.setExtension(UnittestProto.repeatedInt32Extension, 0, 2); + builder.clearExtension(UnittestProto.repeatedInt32Extension); + assertEquals(0, mockParent.getInvalidationCount()); + + // Now tell it we want changes and make sure it's only fired once + builder.buildPartial(); + builder.addExtension(UnittestProto.repeatedInt32Extension, 2); + builder.addExtension(UnittestProto.repeatedInt32Extension, 3); + assertEquals(1, mockParent.getInvalidationCount()); + + builder.buildPartial(); + builder.setExtension(UnittestProto.repeatedInt32Extension, 0, 4); + builder.setExtension(UnittestProto.repeatedInt32Extension, 1, 5); + assertEquals(2, mockParent.getInvalidationCount()); + + builder.buildPartial(); + builder.clearExtension(UnittestProto.repeatedInt32Extension); + builder.clearExtension(UnittestProto.repeatedInt32Extension); + assertEquals(3, mockParent.getInvalidationCount()); + } + + public void testBaseMessageOrBuilder() { + // Mostly just makes sure the base interface exists and has some methods. + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TestAllTypes message = builder.buildPartial(); + TestAllTypesOrBuilder builderAsInterface = (TestAllTypesOrBuilder) builder; + TestAllTypesOrBuilder messageAsInterface = (TestAllTypesOrBuilder) message; + + assertEquals( + messageAsInterface.getDefaultBool(), + messageAsInterface.getDefaultBool()); + assertEquals( + messageAsInterface.getOptionalDouble(), + messageAsInterface.getOptionalDouble()); + } + + public void testMessageOrBuilderGetters() { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + + // single fields + assertSame(ForeignMessage.getDefaultInstance(), + builder.getOptionalForeignMessageOrBuilder()); + ForeignMessage.Builder subBuilder = + builder.getOptionalForeignMessageBuilder(); + assertSame(subBuilder, builder.getOptionalForeignMessageOrBuilder()); + + // repeated fields + ForeignMessage m0 = ForeignMessage.newBuilder().buildPartial(); + ForeignMessage m1 = ForeignMessage.newBuilder().buildPartial(); + ForeignMessage m2 = ForeignMessage.newBuilder().buildPartial(); + builder.addRepeatedForeignMessage(m0); + builder.addRepeatedForeignMessage(m1); + builder.addRepeatedForeignMessage(m2); + assertSame(m0, builder.getRepeatedForeignMessageOrBuilder(0)); + assertSame(m1, builder.getRepeatedForeignMessageOrBuilder(1)); + assertSame(m2, builder.getRepeatedForeignMessageOrBuilder(2)); + ForeignMessage.Builder b0 = builder.getRepeatedForeignMessageBuilder(0); + ForeignMessage.Builder b1 = builder.getRepeatedForeignMessageBuilder(1); + assertSame(b0, builder.getRepeatedForeignMessageOrBuilder(0)); + assertSame(b1, builder.getRepeatedForeignMessageOrBuilder(1)); + assertSame(m2, builder.getRepeatedForeignMessageOrBuilder(2)); + + List<? extends ForeignMessageOrBuilder> messageOrBuilderList = + builder.getRepeatedForeignMessageOrBuilderList(); + assertSame(b0, messageOrBuilderList.get(0)); + assertSame(b1, messageOrBuilderList.get(1)); + assertSame(m2, messageOrBuilderList.get(2)); + } + + public void testGetFieldBuilder() { + Descriptor descriptor = TestAllTypes.getDescriptor(); + + FieldDescriptor fieldDescriptor = + descriptor.findFieldByName("optional_nested_message"); + FieldDescriptor foreignFieldDescriptor = + descriptor.findFieldByName("optional_foreign_message"); + FieldDescriptor importFieldDescriptor = + descriptor.findFieldByName("optional_import_message"); + + // Mutate the message with new field builder + // Mutate nested message + TestAllTypes.Builder builder1 = TestAllTypes.newBuilder(); + Message.Builder fieldBuilder1 = builder1.newBuilderForField(fieldDescriptor) + .mergeFrom((Message) builder1.getField(fieldDescriptor)); + FieldDescriptor subFieldDescriptor1 = + fieldBuilder1.getDescriptorForType().findFieldByName("bb"); + fieldBuilder1.setField(subFieldDescriptor1, 1); + builder1.setField(fieldDescriptor, fieldBuilder1.build()); + + // Mutate foreign message + Message.Builder foreignFieldBuilder1 = builder1.newBuilderForField( + foreignFieldDescriptor) + .mergeFrom((Message) builder1.getField(foreignFieldDescriptor)); + FieldDescriptor subForeignFieldDescriptor1 = + foreignFieldBuilder1.getDescriptorForType().findFieldByName("c"); + foreignFieldBuilder1.setField(subForeignFieldDescriptor1, 2); + builder1.setField(foreignFieldDescriptor, foreignFieldBuilder1.build()); + + // Mutate import message + Message.Builder importFieldBuilder1 = builder1.newBuilderForField( + importFieldDescriptor) + .mergeFrom((Message) builder1.getField(importFieldDescriptor)); + FieldDescriptor subImportFieldDescriptor1 = + importFieldBuilder1.getDescriptorForType().findFieldByName("d"); + importFieldBuilder1.setField(subImportFieldDescriptor1, 3); + builder1.setField(importFieldDescriptor, importFieldBuilder1.build()); + + Message newMessage1 = builder1.build(); + + // Mutate the message with existing field builder + // Mutate nested message + TestAllTypes.Builder builder2 = TestAllTypes.newBuilder(); + Message.Builder fieldBuilder2 = builder2.getFieldBuilder(fieldDescriptor); + FieldDescriptor subFieldDescriptor2 = + fieldBuilder2.getDescriptorForType().findFieldByName("bb"); + fieldBuilder2.setField(subFieldDescriptor2, 1); + builder2.setField(fieldDescriptor, fieldBuilder2.build()); + + // Mutate foreign message + Message.Builder foreignFieldBuilder2 = builder2.newBuilderForField( + foreignFieldDescriptor) + .mergeFrom((Message) builder2.getField(foreignFieldDescriptor)); + FieldDescriptor subForeignFieldDescriptor2 = + foreignFieldBuilder2.getDescriptorForType().findFieldByName("c"); + foreignFieldBuilder2.setField(subForeignFieldDescriptor2, 2); + builder2.setField(foreignFieldDescriptor, foreignFieldBuilder2.build()); + + // Mutate import message + Message.Builder importFieldBuilder2 = builder2.newBuilderForField( + importFieldDescriptor) + .mergeFrom((Message) builder2.getField(importFieldDescriptor)); + FieldDescriptor subImportFieldDescriptor2 = + importFieldBuilder2.getDescriptorForType().findFieldByName("d"); + importFieldBuilder2.setField(subImportFieldDescriptor2, 3); + builder2.setField(importFieldDescriptor, importFieldBuilder2.build()); + + Message newMessage2 = builder2.build(); + + // These two messages should be equal. + assertEquals(newMessage1, newMessage2); + } + + public void testGetFieldBuilderWithInitializedValue() { + Descriptor descriptor = TestAllTypes.getDescriptor(); + FieldDescriptor fieldDescriptor = + descriptor.findFieldByName("optional_nested_message"); + + // Before setting field, builder is initialized by default value. + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + NestedMessage.Builder fieldBuilder = + (NestedMessage.Builder) builder.getFieldBuilder(fieldDescriptor); + assertEquals(0, fieldBuilder.getBb()); + + // Setting field value with new field builder instance. + builder = TestAllTypes.newBuilder(); + NestedMessage.Builder newFieldBuilder = + builder.getOptionalNestedMessageBuilder(); + newFieldBuilder.setBb(2); + // Then get the field builder instance by getFieldBuilder(). + fieldBuilder = + (NestedMessage.Builder) builder.getFieldBuilder(fieldDescriptor); + // It should contain new value. + assertEquals(2, fieldBuilder.getBb()); + // These two builder should be equal. + assertSame(fieldBuilder, newFieldBuilder); + } + + public void testGetFieldBuilderNotSupportedException() { + Descriptor descriptor = TestAllTypes.getDescriptor(); + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + try { + builder.getFieldBuilder(descriptor.findFieldByName("optional_int32")); + fail("Exception was not thrown"); + } catch (UnsupportedOperationException e) { + // We expect this exception. + } + try { + builder.getFieldBuilder( + descriptor.findFieldByName("optional_nested_enum")); + fail("Exception was not thrown"); + } catch (UnsupportedOperationException e) { + // We expect this exception. + } + try { + builder.getFieldBuilder(descriptor.findFieldByName("repeated_int32")); + fail("Exception was not thrown"); + } catch (UnsupportedOperationException e) { + // We expect this exception. + } + try { + builder.getFieldBuilder( + descriptor.findFieldByName("repeated_nested_enum")); + fail("Exception was not thrown"); + } catch (UnsupportedOperationException e) { + // We expect this exception. + } + try { + builder.getFieldBuilder( + descriptor.findFieldByName("repeated_nested_message")); + fail("Exception was not thrown"); + } catch (UnsupportedOperationException e) { + // We expect this exception. + } + } + + // Test that when the default outer class name conflicts with another type + // defined in the proto the compiler will append a suffix to avoid the + // conflict. + public void testConflictingOuterClassName() { + // We just need to make sure we can refer to the outer class with the + // expected name. There is nothing else to test. + OuterClassNameTestOuterClass.OuterClassNameTest message = + OuterClassNameTestOuterClass.OuterClassNameTest.newBuilder().build(); + assertTrue(message.getDescriptorForType() == + OuterClassNameTestOuterClass.OuterClassNameTest.getDescriptor()); + + OuterClassNameTest2OuterClass.TestMessage2.NestedMessage.OuterClassNameTest2 + message2 = OuterClassNameTest2OuterClass.TestMessage2.NestedMessage + .OuterClassNameTest2.newBuilder().build(); + assertEquals(0, message2.getSerializedSize()); + + OuterClassNameTest3OuterClass.TestMessage3.NestedMessage.OuterClassNameTest3 + enumValue = OuterClassNameTest3OuterClass.TestMessage3.NestedMessage + .OuterClassNameTest3.DUMMY_VALUE; + assertEquals(1, enumValue.getNumber()); + } + + // ================================================================= + // oneof generated code test + public void testOneofEnumCase() throws Exception { + TestOneof2 message = TestOneof2.newBuilder() + .setFooInt(123).setFooString("foo").setFooCord("bar").build(); + TestUtil.assertAtMostOneFieldSetOneof(message); + } + + public void testClearOneof() throws Exception { + TestOneof2.Builder builder = TestOneof2.newBuilder().setFooInt(123); + assertEquals(TestOneof2.FooCase.FOO_INT, builder.getFooCase()); + builder.clearFoo(); + assertEquals(TestOneof2.FooCase.FOO_NOT_SET, builder.getFooCase()); + } + + public void testSetOneofClearsOthers() throws Exception { + TestOneof2.Builder builder = TestOneof2.newBuilder(); + TestOneof2 message = + builder.setFooInt(123).setFooString("foo").buildPartial(); + assertTrue(message.hasFooString()); + TestUtil.assertAtMostOneFieldSetOneof(message); + + message = builder.setFooCord("bar").buildPartial(); + assertTrue(message.hasFooCord()); + TestUtil.assertAtMostOneFieldSetOneof(message); + + message = builder.setFooStringPiece("baz").buildPartial(); + assertTrue(message.hasFooStringPiece()); + TestUtil.assertAtMostOneFieldSetOneof(message); + + message = builder.setFooBytes(TestUtil.toBytes("qux")).buildPartial(); + assertTrue(message.hasFooBytes()); + TestUtil.assertAtMostOneFieldSetOneof(message); + + message = builder.setFooEnum(TestOneof2.NestedEnum.FOO).buildPartial(); + assertTrue(message.hasFooEnum()); + TestUtil.assertAtMostOneFieldSetOneof(message); + + message = builder.setFooMessage( + TestOneof2.NestedMessage.newBuilder().setQuxInt(234).build()).buildPartial(); + assertTrue(message.hasFooMessage()); + TestUtil.assertAtMostOneFieldSetOneof(message); + + message = builder.setFooInt(123).buildPartial(); + assertTrue(message.hasFooInt()); + TestUtil.assertAtMostOneFieldSetOneof(message); + } + + public void testOneofTypes() throws Exception { + // Primitive + { + TestOneof2.Builder builder = TestOneof2.newBuilder(); + assertEquals(builder.getFooInt(), 0); + assertFalse(builder.hasFooInt()); + assertTrue(builder.setFooInt(123).hasFooInt()); + assertEquals(builder.getFooInt(), 123); + TestOneof2 message = builder.buildPartial(); + assertTrue(message.hasFooInt()); + assertEquals(message.getFooInt(), 123); + + assertFalse(builder.clearFooInt().hasFooInt()); + TestOneof2 message2 = builder.build(); + assertFalse(message2.hasFooInt()); + assertEquals(message2.getFooInt(), 0); + } + + // Enum + { + TestOneof2.Builder builder = TestOneof2.newBuilder(); + assertEquals(builder.getFooEnum(), TestOneof2.NestedEnum.FOO); + assertTrue(builder.setFooEnum(TestOneof2.NestedEnum.BAR).hasFooEnum()); + assertEquals(builder.getFooEnum(), TestOneof2.NestedEnum.BAR); + TestOneof2 message = builder.buildPartial(); + assertTrue(message.hasFooEnum()); + assertEquals(message.getFooEnum(), TestOneof2.NestedEnum.BAR); + + assertFalse(builder.clearFooEnum().hasFooEnum()); + TestOneof2 message2 = builder.build(); + assertFalse(message2.hasFooEnum()); + assertEquals(message2.getFooEnum(), TestOneof2.NestedEnum.FOO); + } + + // String + { + TestOneof2.Builder builder = TestOneof2.newBuilder(); + assertEquals(builder.getFooString(), ""); + builder.setFooString("foo"); + assertTrue(builder.hasFooString()); + assertEquals(builder.getFooString(), "foo"); + TestOneof2 message = builder.buildPartial(); + assertTrue(message.hasFooString()); + assertEquals(message.getFooString(), "foo"); + assertEquals(message.getFooStringBytes(), TestUtil.toBytes("foo")); + + assertFalse(builder.clearFooString().hasFooString()); + TestOneof2 message2 = builder.buildPartial(); + assertFalse(message2.hasFooString()); + assertEquals(message2.getFooString(), ""); + assertEquals(message2.getFooStringBytes(), TestUtil.toBytes("")); + + // Get method should not change the oneof value. + builder.setFooInt(123); + assertEquals(builder.getFooString(), ""); + assertEquals(builder.getFooStringBytes(), TestUtil.toBytes("")); + assertEquals(123, builder.getFooInt()); + + message = builder.build(); + assertEquals(message.getFooString(), ""); + assertEquals(message.getFooStringBytes(), TestUtil.toBytes("")); + assertEquals(123, message.getFooInt()); + } + + // Cord + { + TestOneof2.Builder builder = TestOneof2.newBuilder(); + assertEquals(builder.getFooCord(), ""); + builder.setFooCord("foo"); + assertTrue(builder.hasFooCord()); + assertEquals(builder.getFooCord(), "foo"); + TestOneof2 message = builder.buildPartial(); + assertTrue(message.hasFooCord()); + assertEquals(message.getFooCord(), "foo"); + assertEquals(message.getFooCordBytes(), TestUtil.toBytes("foo")); + + assertFalse(builder.clearFooCord().hasFooCord()); + TestOneof2 message2 = builder.build(); + assertFalse(message2.hasFooCord()); + assertEquals(message2.getFooCord(), ""); + assertEquals(message2.getFooCordBytes(), TestUtil.toBytes("")); + } + + // StringPiece + { + TestOneof2.Builder builder = TestOneof2.newBuilder(); + assertEquals(builder.getFooStringPiece(), ""); + builder.setFooStringPiece("foo"); + assertTrue(builder.hasFooStringPiece()); + assertEquals(builder.getFooStringPiece(), "foo"); + TestOneof2 message = builder.buildPartial(); + assertTrue(message.hasFooStringPiece()); + assertEquals(message.getFooStringPiece(), "foo"); + assertEquals(message.getFooStringPieceBytes(), TestUtil.toBytes("foo")); + + assertFalse(builder.clearFooStringPiece().hasFooStringPiece()); + TestOneof2 message2 = builder.build(); + assertFalse(message2.hasFooStringPiece()); + assertEquals(message2.getFooStringPiece(), ""); + assertEquals(message2.getFooStringPieceBytes(), TestUtil.toBytes("")); + } + + // Message + { + // set + TestOneof2.Builder builder = TestOneof2.newBuilder(); + assertEquals(builder.getFooMessage().getQuxInt(), 0); + builder.setFooMessage( + TestOneof2.NestedMessage.newBuilder().setQuxInt(234).build()); + assertTrue(builder.hasFooMessage()); + assertEquals(builder.getFooMessage().getQuxInt(), 234); + TestOneof2 message = builder.buildPartial(); + assertTrue(message.hasFooMessage()); + assertEquals(message.getFooMessage().getQuxInt(), 234); + + // clear + assertFalse(builder.clearFooMessage().hasFooString()); + message = builder.build(); + assertFalse(message.hasFooMessage()); + assertEquals(message.getFooMessage().getQuxInt(), 0); + + // nested builder + builder = TestOneof2.newBuilder(); + assertSame(builder.getFooMessageOrBuilder(), + TestOneof2.NestedMessage.getDefaultInstance()); + assertFalse(builder.hasFooMessage()); + builder.getFooMessageBuilder().setQuxInt(123); + assertTrue(builder.hasFooMessage()); + assertEquals(builder.getFooMessage().getQuxInt(), 123); + message = builder.build(); + assertTrue(message.hasFooMessage()); + assertEquals(message.getFooMessage().getQuxInt(), 123); + } + + // LazyMessage is tested in LazyMessageLiteTest.java + } + + public void testOneofMerge() throws Exception { + // Primitive Type + { + TestOneof2.Builder builder = TestOneof2.newBuilder(); + TestOneof2 message = builder.setFooInt(123).build(); + TestOneof2 message2 = TestOneof2.newBuilder().mergeFrom(message).build(); + assertTrue(message2.hasFooInt()); + assertEquals(message2.getFooInt(), 123); + } + + // String + { + TestOneof2.Builder builder = TestOneof2.newBuilder(); + TestOneof2 message = builder.setFooString("foo").build(); + TestOneof2 message2 = TestOneof2.newBuilder().mergeFrom(message).build(); + assertTrue(message2.hasFooString()); + assertEquals(message2.getFooString(), "foo"); + } + + // Enum + { + TestOneof2.Builder builder = TestOneof2.newBuilder(); + TestOneof2 message = builder.setFooEnum(TestOneof2.NestedEnum.BAR).build(); + TestOneof2 message2 = TestOneof2.newBuilder().mergeFrom(message).build(); + assertTrue(message2.hasFooEnum()); + assertEquals(message2.getFooEnum(), TestOneof2.NestedEnum.BAR); + } + + // Message + { + TestOneof2.Builder builder = TestOneof2.newBuilder(); + TestOneof2 message = builder.setFooMessage( + TestOneof2.NestedMessage.newBuilder().setQuxInt(234).build()).build(); + TestOneof2 message2 = TestOneof2.newBuilder().mergeFrom(message).build(); + assertTrue(message2.hasFooMessage()); + assertEquals(message2.getFooMessage().getQuxInt(), 234); + } + } + + public void testOneofSerialization() throws Exception { + // Primitive Type + { + TestOneof2.Builder builder = TestOneof2.newBuilder(); + TestOneof2 message = builder.setFooInt(123).build(); + ByteString serialized = message.toByteString(); + TestOneof2 message2 = TestOneof2.parseFrom(serialized); + assertTrue(message2.hasFooInt()); + assertEquals(message2.getFooInt(), 123); + } + + // String + { + TestOneof2.Builder builder = TestOneof2.newBuilder(); + TestOneof2 message = builder.setFooString("foo").build(); + ByteString serialized = message.toByteString(); + TestOneof2 message2 = TestOneof2.parseFrom(serialized); + assertTrue(message2.hasFooString()); + assertEquals(message2.getFooString(), "foo"); + } + + // Enum + { + TestOneof2.Builder builder = TestOneof2.newBuilder(); + TestOneof2 message = builder.setFooEnum(TestOneof2.NestedEnum.BAR).build(); + ByteString serialized = message.toByteString(); + TestOneof2 message2 = TestOneof2.parseFrom(serialized); + assertTrue(message2.hasFooEnum()); + assertEquals(message2.getFooEnum(), TestOneof2.NestedEnum.BAR); + } + + // Message + { + TestOneof2.Builder builder = TestOneof2.newBuilder(); + TestOneof2 message = builder.setFooMessage( + TestOneof2.NestedMessage.newBuilder().setQuxInt(234).build()).build(); + ByteString serialized = message.toByteString(); + TestOneof2 message2 = TestOneof2.parseFrom(serialized); + assertTrue(message2.hasFooMessage()); + assertEquals(message2.getFooMessage().getQuxInt(), 234); + } + } + + public void testOneofNestedBuilderOnChangePropagation() { + NestedTestAllTypes.Builder parentBuilder = NestedTestAllTypes.newBuilder(); + TestAllTypes.Builder builder = parentBuilder.getPayloadBuilder(); + builder.getOneofNestedMessageBuilder(); + assertTrue(builder.hasOneofNestedMessage()); + assertTrue(parentBuilder.hasPayload()); + NestedTestAllTypes message = parentBuilder.build(); + assertTrue(message.hasPayload()); + assertTrue(message.getPayload().hasOneofNestedMessage()); + } + + 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/core/src/test/java/com/google/protobuf/IntArrayListTest.java b/java/core/src/test/java/com/google/protobuf/IntArrayListTest.java new file mode 100644 index 00000000..3733eb30 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/IntArrayListTest.java @@ -0,0 +1,473 @@ +// 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 static java.util.Arrays.asList; + +import junit.framework.TestCase; + +import java.util.Collections; +import java.util.ConcurrentModificationException; +import java.util.Iterator; + +/** + * Tests for {@link IntArrayList}. + * + * @author dweis@google.com (Daniel Weis) + */ +public class IntArrayListTest extends TestCase { + + private static final IntArrayList UNARY_LIST = newImmutableIntArrayList(1); + private static final IntArrayList TERTIARY_LIST = + newImmutableIntArrayList(1, 2, 3); + + private IntArrayList list; + + @Override + protected void setUp() throws Exception { + list = new IntArrayList(); + } + + public void testEmptyListReturnsSameInstance() { + assertSame(IntArrayList.emptyList(), IntArrayList.emptyList()); + } + + public void testEmptyListIsImmutable() { + assertImmutable(IntArrayList.emptyList()); + } + + public void testMakeImmutable() { + list.addInt(2); + list.addInt(4); + list.addInt(6); + list.addInt(8); + list.makeImmutable(); + assertImmutable(list); + } + + public void testCopyConstructor() { + IntArrayList copy = new IntArrayList(TERTIARY_LIST); + assertEquals(TERTIARY_LIST, copy); + + copy = new IntArrayList(IntArrayList.emptyList()); + assertEquals(IntArrayList.emptyList(), copy); + + copy = new IntArrayList(asList(1, 2, 3)); + assertEquals(asList(1, 2, 3), copy); + + copy = new IntArrayList(Collections.<Integer>emptyList()); + assertEquals(IntArrayList.emptyList(), copy); + } + + public void testModificationWithIteration() { + list.addAll(asList(1, 2, 3, 4)); + Iterator<Integer> iterator = list.iterator(); + assertEquals(4, list.size()); + assertEquals(1, (int) list.get(0)); + assertEquals(1, (int) iterator.next()); + list.set(0, 1); + assertEquals(2, (int) iterator.next()); + + list.remove(0); + try { + iterator.next(); + fail(); + } catch (ConcurrentModificationException e) { + // expected + } + + iterator = list.iterator(); + list.add(0, 0); + try { + iterator.next(); + fail(); + } catch (ConcurrentModificationException e) { + // expected + } + } + + public void testGet() { + assertEquals(1, (int) TERTIARY_LIST.get(0)); + assertEquals(2, (int) TERTIARY_LIST.get(1)); + assertEquals(3, (int) TERTIARY_LIST.get(2)); + + try { + TERTIARY_LIST.get(-1); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + + try { + TERTIARY_LIST.get(3); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + public void testGetInt() { + assertEquals(1, TERTIARY_LIST.getInt(0)); + assertEquals(2, TERTIARY_LIST.getInt(1)); + assertEquals(3, TERTIARY_LIST.getInt(2)); + + try { + TERTIARY_LIST.get(-1); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + + try { + TERTIARY_LIST.get(3); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + public void testSize() { + assertEquals(0, IntArrayList.emptyList().size()); + assertEquals(1, UNARY_LIST.size()); + assertEquals(3, TERTIARY_LIST.size()); + + list.addInt(2); + list.addInt(4); + list.addInt(6); + list.addInt(8); + assertEquals(4, list.size()); + + list.remove(0); + assertEquals(3, list.size()); + + list.add(16); + assertEquals(4, list.size()); + } + + public void testSet() { + list.addInt(2); + list.addInt(4); + + assertEquals(2, (int) list.set(0, 0)); + assertEquals(0, list.getInt(0)); + + assertEquals(4, (int) list.set(1, 0)); + assertEquals(0, list.getInt(1)); + + try { + list.set(-1, 0); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + + try { + list.set(2, 0); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + public void testSetInt() { + list.addInt(2); + list.addInt(4); + + assertEquals(2, list.setInt(0, 0)); + assertEquals(0, list.getInt(0)); + + assertEquals(4, list.setInt(1, 0)); + assertEquals(0, list.getInt(1)); + + try { + list.setInt(-1, 0); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + + try { + list.setInt(2, 0); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + public void testAdd() { + assertEquals(0, list.size()); + + assertTrue(list.add(2)); + assertEquals(asList(2), list); + + assertTrue(list.add(3)); + list.add(0, 4); + assertEquals(asList(4, 2, 3), list); + + list.add(0, 1); + list.add(0, 0); + // Force a resize by getting up to 11 elements. + for (int i = 0; i < 6; i++) { + list.add(5 + i); + } + assertEquals(asList(0, 1, 4, 2, 3, 5, 6, 7, 8, 9, 10), list); + + try { + list.add(-1, 5); + } catch (IndexOutOfBoundsException e) { + // expected + } + + try { + list.add(4, 5); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + public void testAddInt() { + assertEquals(0, list.size()); + + list.addInt(2); + assertEquals(asList(2), list); + + list.addInt(3); + assertEquals(asList(2, 3), list); + } + + public void testAddAll() { + assertEquals(0, list.size()); + + assertTrue(list.addAll(Collections.singleton(1))); + assertEquals(1, list.size()); + assertEquals(1, (int) list.get(0)); + assertEquals(1, list.getInt(0)); + + assertTrue(list.addAll(asList(2, 3, 4, 5, 6))); + assertEquals(asList(1, 2, 3, 4, 5, 6), list); + + assertTrue(list.addAll(TERTIARY_LIST)); + assertEquals(asList(1, 2, 3, 4, 5, 6, 1, 2, 3), list); + + assertFalse(list.addAll(Collections.<Integer>emptyList())); + assertFalse(list.addAll(IntArrayList.emptyList())); + } + + public void testRemove() { + list.addAll(TERTIARY_LIST); + assertEquals(1, (int) list.remove(0)); + assertEquals(asList(2, 3), list); + + assertTrue(list.remove(Integer.valueOf(3))); + assertEquals(asList(2), list); + + assertFalse(list.remove(Integer.valueOf(3))); + assertEquals(asList(2), list); + + assertEquals(2, (int) list.remove(0)); + assertEquals(asList(), list); + + try { + list.remove(-1); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + + try { + list.remove(0); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + private void assertImmutable(IntArrayList list) { + if (list.contains(1)) { + throw new RuntimeException("Cannot test the immutability of lists that contain 1."); + } + + try { + list.add(1); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.add(0, 1); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(Collections.<Integer>emptyList()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(Collections.singletonList(1)); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(new IntArrayList()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(UNARY_LIST); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(0, Collections.singleton(1)); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(0, UNARY_LIST); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(0, Collections.<Integer>emptyList()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addInt(0); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.clear(); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.remove(1); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.remove(new Object()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.removeAll(Collections.<Integer>emptyList()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.removeAll(Collections.singleton(1)); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.removeAll(UNARY_LIST); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.retainAll(Collections.<Integer>emptyList()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.retainAll(Collections.singleton(1)); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.retainAll(UNARY_LIST); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.set(0, 0); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.setInt(0, 0); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + } + + private static IntArrayList newImmutableIntArrayList(int... elements) { + IntArrayList list = new IntArrayList(); + for (int element : elements) { + list.addInt(element); + } + list.makeImmutable(); + return list; + } +} diff --git a/java/core/src/test/java/com/google/protobuf/IsValidUtf8Test.java b/java/core/src/test/java/com/google/protobuf/IsValidUtf8Test.java new file mode 100644 index 00000000..8751baae --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/IsValidUtf8Test.java @@ -0,0 +1,183 @@ +// 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.IsValidUtf8TestUtil.Shard; + +import junit.framework.TestCase; + +import java.io.UnsupportedEncodingException; + +/** + * Tests cases for {@link ByteString#isValidUtf8()}. This includes three + * brute force tests that actually test every permutation of one byte, two byte, + * and three byte sequences to ensure that the method produces the right result + * for every possible byte encoding where "right" means it's consistent with + * java's UTF-8 string encoding/decoding such that the method returns true for + * any sequence that will round trip when converted to a String and then back to + * bytes and will return false for any sequence that will not round trip. + * See also {@link IsValidUtf8FourByteTest}. It also includes some + * other more targeted tests. + * + * @author jonp@google.com (Jon Perlow) + * @author martinrb@google.com (Martin Buchholz) + */ +public class IsValidUtf8Test extends TestCase { + + /** + * Tests that round tripping of all two byte permutations work. + */ + public void testIsValidUtf8_1Byte() throws UnsupportedEncodingException { + IsValidUtf8TestUtil.testBytes(1, + IsValidUtf8TestUtil.EXPECTED_ONE_BYTE_ROUNDTRIPPABLE_COUNT); + } + + /** + * Tests that round tripping of all two byte permutations work. + */ + public void testIsValidUtf8_2Bytes() throws UnsupportedEncodingException { + IsValidUtf8TestUtil.testBytes(2, + IsValidUtf8TestUtil.EXPECTED_TWO_BYTE_ROUNDTRIPPABLE_COUNT); + } + + /** + * Tests that round tripping of all three byte permutations work. + */ + public void testIsValidUtf8_3Bytes() throws UnsupportedEncodingException { + // Travis' OOM killer doesn't like this test + if (System.getenv("TRAVIS") == null) { + IsValidUtf8TestUtil.testBytes(3, + IsValidUtf8TestUtil.EXPECTED_THREE_BYTE_ROUNDTRIPPABLE_COUNT); + } + } + + /** + * Tests that round tripping of a sample of four byte permutations work. + * All permutations are prohibitively expensive to test for automated runs; + * {@link IsValidUtf8FourByteTest} is used for full coverage. This method + * tests specific four-byte cases. + */ + public void testIsValidUtf8_4BytesSamples() + throws UnsupportedEncodingException { + // Valid 4 byte. + assertValidUtf8(0xF0, 0xA4, 0xAD, 0xA2); + + // Bad trailing bytes + assertInvalidUtf8(0xF0, 0xA4, 0xAD, 0x7F); + assertInvalidUtf8(0xF0, 0xA4, 0xAD, 0xC0); + + // Special cases for byte2 + assertInvalidUtf8(0xF0, 0x8F, 0xAD, 0xA2); + assertInvalidUtf8(0xF4, 0x90, 0xAD, 0xA2); + } + + /** + * Tests some hard-coded test cases. + */ + public void testSomeSequences() { + // Empty + assertTrue(asBytes("").isValidUtf8()); + + // One-byte characters, including control characters + assertTrue(asBytes("\u0000abc\u007f").isValidUtf8()); + + // Two-byte characters + assertTrue(asBytes("\u00a2\u00a2").isValidUtf8()); + + // Three-byte characters + assertTrue(asBytes("\u020ac\u020ac").isValidUtf8()); + + // Four-byte characters + assertTrue(asBytes("\u024B62\u024B62").isValidUtf8()); + + // Mixed string + assertTrue( + asBytes("a\u020ac\u00a2b\\u024B62u020acc\u00a2de\u024B62") + .isValidUtf8()); + + // Not a valid string + assertInvalidUtf8(-1, 0, -1, 0); + } + + private byte[] toByteArray(int... bytes) { + byte[] realBytes = new byte[bytes.length]; + for (int i = 0; i < bytes.length; i++) { + realBytes[i] = (byte) bytes[i]; + } + return realBytes; + } + + private ByteString toByteString(int... bytes) { + return ByteString.copyFrom(toByteArray(bytes)); + } + + private void assertValidUtf8(int[] bytes, boolean not) { + byte[] realBytes = toByteArray(bytes); + assertTrue(not ^ Utf8.isValidUtf8(realBytes)); + assertTrue(not ^ Utf8.isValidUtf8(realBytes, 0, bytes.length)); + ByteString lit = ByteString.copyFrom(realBytes); + ByteString sub = lit.substring(0, bytes.length); + assertTrue(not ^ lit.isValidUtf8()); + assertTrue(not ^ sub.isValidUtf8()); + ByteString[] ropes = { + RopeByteString.newInstanceForTest(ByteString.EMPTY, lit), + RopeByteString.newInstanceForTest(ByteString.EMPTY, sub), + RopeByteString.newInstanceForTest(lit, ByteString.EMPTY), + RopeByteString.newInstanceForTest(sub, ByteString.EMPTY), + RopeByteString.newInstanceForTest(sub, lit) + }; + for (ByteString rope : ropes) { + assertTrue(not ^ rope.isValidUtf8()); + } + } + + private void assertValidUtf8(int... bytes) { + assertValidUtf8(bytes, false); + } + + private void assertInvalidUtf8(int... bytes) { + assertValidUtf8(bytes, true); + } + + private static ByteString asBytes(String s) { + return ByteString.copyFromUtf8(s); + } + + public void testShardsHaveExpectedRoundTrippables() { + // A sanity check. + int actual = 0; + for (Shard shard : IsValidUtf8TestUtil.FOUR_BYTE_SHARDS) { + actual += shard.expected; + } + assertEquals(IsValidUtf8TestUtil.EXPECTED_FOUR_BYTE_ROUNDTRIPPABLE_COUNT, + actual); + } +} diff --git a/java/core/src/test/java/com/google/protobuf/IsValidUtf8TestUtil.java b/java/core/src/test/java/com/google/protobuf/IsValidUtf8TestUtil.java new file mode 100644 index 00000000..321669f3 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/IsValidUtf8TestUtil.java @@ -0,0 +1,420 @@ +// 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 static junit.framework.Assert.*; + +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.CoderResult; +import java.nio.charset.CodingErrorAction; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.logging.Logger; + +/** + * Shared testing code for {@link IsValidUtf8Test} and + * {@link IsValidUtf8FourByteTest}. + * + * @author jonp@google.com (Jon Perlow) + * @author martinrb@google.com (Martin Buchholz) + */ +class IsValidUtf8TestUtil { + private static Logger logger = Logger.getLogger( + IsValidUtf8TestUtil.class.getName()); + + // 128 - [chars 0x0000 to 0x007f] + static long ONE_BYTE_ROUNDTRIPPABLE_CHARACTERS = 0x007f - 0x0000 + 1; + + // 128 + static long EXPECTED_ONE_BYTE_ROUNDTRIPPABLE_COUNT = + ONE_BYTE_ROUNDTRIPPABLE_CHARACTERS; + + // 1920 [chars 0x0080 to 0x07FF] + static long TWO_BYTE_ROUNDTRIPPABLE_CHARACTERS = 0x07FF - 0x0080 + 1; + + // 18,304 + static long EXPECTED_TWO_BYTE_ROUNDTRIPPABLE_COUNT = + // Both bytes are one byte characters + (long) Math.pow(EXPECTED_ONE_BYTE_ROUNDTRIPPABLE_COUNT, 2) + + // The possible number of two byte characters + TWO_BYTE_ROUNDTRIPPABLE_CHARACTERS; + + // 2048 + static long THREE_BYTE_SURROGATES = 2 * 1024; + + // 61,440 [chars 0x0800 to 0xFFFF, minus surrogates] + static long THREE_BYTE_ROUNDTRIPPABLE_CHARACTERS = + 0xFFFF - 0x0800 + 1 - THREE_BYTE_SURROGATES; + + // 2,650,112 + static long EXPECTED_THREE_BYTE_ROUNDTRIPPABLE_COUNT = + // All one byte characters + (long) Math.pow(EXPECTED_ONE_BYTE_ROUNDTRIPPABLE_COUNT, 3) + + // One two byte character and a one byte character + 2 * TWO_BYTE_ROUNDTRIPPABLE_CHARACTERS * + ONE_BYTE_ROUNDTRIPPABLE_CHARACTERS + + // Three byte characters + THREE_BYTE_ROUNDTRIPPABLE_CHARACTERS; + + // 1,048,576 [chars 0x10000L to 0x10FFFF] + static long FOUR_BYTE_ROUNDTRIPPABLE_CHARACTERS = 0x10FFFF - 0x10000L + 1; + + // 289,571,839 + static long EXPECTED_FOUR_BYTE_ROUNDTRIPPABLE_COUNT = + // All one byte characters + (long) Math.pow(EXPECTED_ONE_BYTE_ROUNDTRIPPABLE_COUNT, 4) + + // One and three byte characters + 2 * THREE_BYTE_ROUNDTRIPPABLE_CHARACTERS * + ONE_BYTE_ROUNDTRIPPABLE_CHARACTERS + + // Two two byte characters + TWO_BYTE_ROUNDTRIPPABLE_CHARACTERS * TWO_BYTE_ROUNDTRIPPABLE_CHARACTERS + + // Permutations of one and two byte characters + 3 * TWO_BYTE_ROUNDTRIPPABLE_CHARACTERS * + ONE_BYTE_ROUNDTRIPPABLE_CHARACTERS * + ONE_BYTE_ROUNDTRIPPABLE_CHARACTERS + + // Four byte characters + FOUR_BYTE_ROUNDTRIPPABLE_CHARACTERS; + + static class Shard { + final long index; + final long start; + final long lim; + final long expected; + + + public Shard(long index, long start, long lim, long expected) { + assertTrue(start < lim); + this.index = index; + this.start = start; + this.lim = lim; + this.expected = expected; + } + } + + static final long[] FOUR_BYTE_SHARDS_EXPECTED_ROUNTRIPPABLES = + generateFourByteShardsExpectedRunnables(); + + private static long[] generateFourByteShardsExpectedRunnables() { + long[] expected = new long[128]; + + // 0-63 are all 5300224 + for (int i = 0; i <= 63; i++) { + expected[i] = 5300224; + } + + // 97-111 are all 2342912 + for (int i = 97; i <= 111; i++) { + expected[i] = 2342912; + } + + // 113-117 are all 1048576 + for (int i = 113; i <= 117; i++) { + expected[i] = 1048576; + } + + // One offs + expected[112] = 786432; + expected[118] = 786432; + expected[119] = 1048576; + expected[120] = 458752; + expected[121] = 524288; + expected[122] = 65536; + + // Anything not assigned was the default 0. + return expected; + } + + static final List<Shard> FOUR_BYTE_SHARDS = generateFourByteShards( + 128, FOUR_BYTE_SHARDS_EXPECTED_ROUNTRIPPABLES); + + + private static List<Shard> generateFourByteShards( + int numShards, long[] expected) { + assertEquals(numShards, expected.length); + List<Shard> shards = new ArrayList<Shard>(numShards); + long LIM = 1L << 32; + long increment = LIM / numShards; + assertTrue(LIM % numShards == 0); + for (int i = 0; i < numShards; i++) { + shards.add(new Shard(i, + increment * i, + increment * (i + 1), + expected[i])); + } + return shards; + } + + /** + * Helper to run the loop to test all the permutations for the number of bytes + * specified. + * + * @param numBytes the number of bytes in the byte array + * @param expectedCount the expected number of roundtrippable permutations + */ + static void testBytes(int numBytes, long expectedCount) + throws UnsupportedEncodingException { + testBytes(numBytes, expectedCount, 0, -1); + } + + /** + * Helper to run the loop to test all the permutations for the number of bytes + * specified. This overload is useful for debugging to get the loop to start + * at a certain character. + * + * @param numBytes the number of bytes in the byte array + * @param expectedCount the expected number of roundtrippable permutations + * @param start the starting bytes encoded as a long as big-endian + * @param lim the limit of bytes to process encoded as a long as big-endian, + * or -1 to mean the max limit for numBytes + */ + static void testBytes(int numBytes, long expectedCount, long start, long lim) + throws UnsupportedEncodingException { + Random rnd = new Random(); + byte[] bytes = new byte[numBytes]; + + if (lim == -1) { + lim = 1L << (numBytes * 8); + } + long count = 0; + long countRoundTripped = 0; + for (long byteChar = start; byteChar < lim; byteChar++) { + long tmpByteChar = byteChar; + for (int i = 0; i < numBytes; i++) { + bytes[bytes.length - i - 1] = (byte) tmpByteChar; + tmpByteChar = tmpByteChar >> 8; + } + ByteString bs = ByteString.copyFrom(bytes); + boolean isRoundTrippable = bs.isValidUtf8(); + String s = new String(bytes, Internal.UTF_8); + byte[] bytesReencoded = s.getBytes(Internal.UTF_8); + boolean bytesEqual = Arrays.equals(bytes, bytesReencoded); + + if (bytesEqual != isRoundTrippable) { + outputFailure(byteChar, bytes, bytesReencoded); + } + + // Check agreement with static Utf8 methods. + assertEquals(isRoundTrippable, Utf8.isValidUtf8(bytes)); + assertEquals(isRoundTrippable, Utf8.isValidUtf8(bytes, 0, numBytes)); + + // Test partial sequences. + // Partition numBytes into three segments (not necessarily non-empty). + int i = rnd.nextInt(numBytes); + int j = rnd.nextInt(numBytes); + if (j < i) { + int tmp = i; i = j; j = tmp; + } + int state1 = Utf8.partialIsValidUtf8(Utf8.COMPLETE, bytes, 0, i); + int state2 = Utf8.partialIsValidUtf8(state1, bytes, i, j); + int state3 = Utf8.partialIsValidUtf8(state2, bytes, j, numBytes); + if (isRoundTrippable != (state3 == Utf8.COMPLETE)) { + System.out.printf("state=%04x %04x %04x i=%d j=%d%n", + state1, state2, state3, i, j); + outputFailure(byteChar, bytes, bytesReencoded); + } + assertEquals(isRoundTrippable, (state3 == Utf8.COMPLETE)); + + // Test ropes built out of small partial sequences + ByteString rope = RopeByteString.newInstanceForTest( + bs.substring(0, i), + RopeByteString.newInstanceForTest( + bs.substring(i, j), + bs.substring(j, numBytes))); + assertSame(RopeByteString.class, rope.getClass()); + + ByteString[] byteStrings = { bs, bs.substring(0, numBytes), rope }; + for (ByteString x : byteStrings) { + assertEquals(isRoundTrippable, + x.isValidUtf8()); + assertEquals(state3, + x.partialIsValidUtf8(Utf8.COMPLETE, 0, numBytes)); + + assertEquals(state1, + x.partialIsValidUtf8(Utf8.COMPLETE, 0, i)); + assertEquals(state1, + x.substring(0, i).partialIsValidUtf8(Utf8.COMPLETE, 0, i)); + assertEquals(state2, + x.partialIsValidUtf8(state1, i, j - i)); + assertEquals(state2, + x.substring(i, j).partialIsValidUtf8(state1, 0, j - i)); + assertEquals(state3, + x.partialIsValidUtf8(state2, j, numBytes - j)); + assertEquals(state3, + x.substring(j, numBytes) + .partialIsValidUtf8(state2, 0, numBytes - j)); + } + + // ByteString reduplication should not affect its UTF-8 validity. + ByteString ropeADope = + RopeByteString.newInstanceForTest(bs, bs.substring(0, numBytes)); + assertEquals(isRoundTrippable, ropeADope.isValidUtf8()); + + if (isRoundTrippable) { + countRoundTripped++; + } + count++; + if (byteChar != 0 && byteChar % 1000000L == 0) { + logger.info("Processed " + (byteChar / 1000000L) + + " million characters"); + } + } + logger.info("Round tripped " + countRoundTripped + " of " + count); + assertEquals(expectedCount, countRoundTripped); + } + + /** + * Variation of {@link #testBytes} that does less allocation using the + * low-level encoders/decoders directly. Checked in because it's useful for + * debugging when trying to process bytes faster, but since it doesn't use the + * actual String class, it's possible for incompatibilities to develop + * (although unlikely). + * + * @param numBytes the number of bytes in the byte array + * @param expectedCount the expected number of roundtrippable permutations + * @param start the starting bytes encoded as a long as big-endian + * @param lim the limit of bytes to process encoded as a long as big-endian, + * or -1 to mean the max limit for numBytes + */ + void testBytesUsingByteBuffers( + int numBytes, long expectedCount, long start, long lim) + throws UnsupportedEncodingException { + CharsetDecoder decoder = Internal.UTF_8.newDecoder() + .onMalformedInput(CodingErrorAction.REPLACE) + .onUnmappableCharacter(CodingErrorAction.REPLACE); + CharsetEncoder encoder = Internal.UTF_8.newEncoder() + .onMalformedInput(CodingErrorAction.REPLACE) + .onUnmappableCharacter(CodingErrorAction.REPLACE); + byte[] bytes = new byte[numBytes]; + int maxChars = (int) (decoder.maxCharsPerByte() * numBytes) + 1; + char[] charsDecoded = + new char[(int) (decoder.maxCharsPerByte() * numBytes) + 1]; + int maxBytes = (int) (encoder.maxBytesPerChar() * maxChars) + 1; + byte[] bytesReencoded = new byte[maxBytes]; + + ByteBuffer bb = ByteBuffer.wrap(bytes); + CharBuffer cb = CharBuffer.wrap(charsDecoded); + ByteBuffer bbReencoded = ByteBuffer.wrap(bytesReencoded); + if (lim == -1) { + lim = 1L << (numBytes * 8); + } + long count = 0; + long countRoundTripped = 0; + for (long byteChar = start; byteChar < lim; byteChar++) { + bb.rewind(); + bb.limit(bytes.length); + cb.rewind(); + cb.limit(charsDecoded.length); + bbReencoded.rewind(); + bbReencoded.limit(bytesReencoded.length); + encoder.reset(); + decoder.reset(); + long tmpByteChar = byteChar; + for (int i = 0; i < bytes.length; i++) { + bytes[bytes.length - i - 1] = (byte) tmpByteChar; + tmpByteChar = tmpByteChar >> 8; + } + boolean isRoundTrippable = ByteString.copyFrom(bytes).isValidUtf8(); + CoderResult result = decoder.decode(bb, cb, true); + assertFalse(result.isError()); + result = decoder.flush(cb); + assertFalse(result.isError()); + + int charLen = cb.position(); + cb.rewind(); + cb.limit(charLen); + result = encoder.encode(cb, bbReencoded, true); + assertFalse(result.isError()); + result = encoder.flush(bbReencoded); + assertFalse(result.isError()); + + boolean bytesEqual = true; + int bytesLen = bbReencoded.position(); + if (bytesLen != numBytes) { + bytesEqual = false; + } else { + for (int i = 0; i < numBytes; i++) { + if (bytes[i] != bytesReencoded[i]) { + bytesEqual = false; + break; + } + } + } + if (bytesEqual != isRoundTrippable) { + outputFailure(byteChar, bytes, bytesReencoded, bytesLen); + } + + count++; + if (isRoundTrippable) { + countRoundTripped++; + } + if (byteChar != 0 && byteChar % 1000000 == 0) { + logger.info("Processed " + (byteChar / 1000000) + + " million characters"); + } + } + logger.info("Round tripped " + countRoundTripped + " of " + count); + assertEquals(expectedCount, countRoundTripped); + } + + private static void outputFailure(long byteChar, byte[] bytes, byte[] after) { + outputFailure(byteChar, bytes, after, after.length); + } + + private static void outputFailure(long byteChar, byte[] bytes, byte[] after, + int len) { + fail("Failure: (" + Long.toHexString(byteChar) + ") " + + toHexString(bytes) + " => " + toHexString(after, len)); + } + + private static String toHexString(byte[] b) { + return toHexString(b, b.length); + } + + private static String toHexString(byte[] b, int len) { + StringBuilder s = new StringBuilder(); + s.append("\""); + for (int i = 0; i < len; i++) { + if (i > 0) { + s.append(" "); + } + s.append(String.format("%02x", b[i] & 0xFF)); + } + s.append("\""); + return s.toString(); + } + +} diff --git a/java/core/src/test/java/com/google/protobuf/LazyFieldLiteTest.java b/java/core/src/test/java/com/google/protobuf/LazyFieldLiteTest.java new file mode 100644 index 00000000..211b5697 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/LazyFieldLiteTest.java @@ -0,0 +1,271 @@ +// 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 static protobuf_unittest.UnittestProto.optionalInt32Extension; +import static protobuf_unittest.UnittestProto.optionalInt64Extension; + +import protobuf_unittest.UnittestProto.TestAllExtensions; +import protobuf_unittest.UnittestProto.TestAllTypes; + +import java.io.IOException; +import junit.framework.TestCase; + +/** + * Unit test for {@link LazyFieldLite}. + * + * @author xiangl@google.com (Xiang Li) + */ +public class LazyFieldLiteTest extends TestCase { + + public void testGetValue() { + MessageLite message = TestUtil.getAllSet(); + LazyFieldLite lazyField = createLazyFieldLiteFromMessage(message); + assertEquals(message, lazyField.getValue(TestAllTypes.getDefaultInstance())); + changeValue(lazyField); + assertNotEqual(message, lazyField.getValue(TestAllTypes.getDefaultInstance())); + } + + public void testGetValueEx() throws Exception { + TestAllExtensions message = TestUtil.getAllExtensionsSet(); + LazyFieldLite lazyField = createLazyFieldLiteFromMessage(message); + assertEquals(message, lazyField.getValue(TestAllExtensions.getDefaultInstance())); + changeValue(lazyField); + assertNotEqual(message, lazyField.getValue(TestAllExtensions.getDefaultInstance())); + } + + public void testSetValue() { + MessageLite message = TestUtil.getAllSet(); + LazyFieldLite lazyField = createLazyFieldLiteFromMessage(message); + changeValue(lazyField); + assertNotEqual(message, lazyField.getValue(TestAllTypes.getDefaultInstance())); + message = lazyField.getValue(TestAllTypes.getDefaultInstance()); + changeValue(lazyField); + assertEquals(message, lazyField.getValue(TestAllTypes.getDefaultInstance())); + } + + public void testSetValueEx() throws Exception { + TestAllExtensions message = TestUtil.getAllExtensionsSet(); + LazyFieldLite lazyField = createLazyFieldLiteFromMessage(message); + changeValue(lazyField); + assertNotEqual(message, lazyField.getValue(TestAllExtensions.getDefaultInstance())); + MessageLite value = lazyField.getValue(TestAllExtensions.getDefaultInstance()); + changeValue(lazyField); + assertEquals(value, lazyField.getValue(TestAllExtensions.getDefaultInstance())); + } + + public void testGetSerializedSize() { + MessageLite message = TestUtil.getAllSet(); + LazyFieldLite lazyField = createLazyFieldLiteFromMessage(message); + assertEquals(message.getSerializedSize(), lazyField.getSerializedSize()); + changeValue(lazyField); + assertNotEqual(message.getSerializedSize(), lazyField.getSerializedSize()); + } + + public void testGetSerializedSizeEx() throws Exception { + TestAllExtensions message = TestUtil.getAllExtensionsSet(); + LazyFieldLite lazyField = createLazyFieldLiteFromMessage(message); + assertEquals(message.getSerializedSize(), lazyField.getSerializedSize()); + changeValue(lazyField); + assertNotEqual(message.getSerializedSize(), lazyField.getSerializedSize()); + } + + public void testGetByteString() { + MessageLite message = TestUtil.getAllSet(); + LazyFieldLite lazyField = createLazyFieldLiteFromMessage(message); + assertEquals(message.toByteString(), lazyField.toByteString()); + changeValue(lazyField); + assertNotEqual(message.toByteString(), lazyField.toByteString()); + } + + public void testGetByteStringEx() throws Exception { + TestAllExtensions message = TestUtil.getAllExtensionsSet(); + LazyFieldLite lazyField = createLazyFieldLiteFromMessage(message); + assertEquals(message.toByteString(), lazyField.toByteString()); + changeValue(lazyField); + assertNotEqual(message.toByteString(), lazyField.toByteString()); + } + + public void testMergeExtensions() throws Exception { + TestAllExtensions message = TestUtil.getAllExtensionsSet(); + LazyFieldLite original = createLazyFieldLiteFromMessage(message); + LazyFieldLite merged = new LazyFieldLite(); + merged.merge(original); + TestAllExtensions value = (TestAllExtensions) merged.getValue( + TestAllExtensions.getDefaultInstance()); + assertEquals(message, value); + } + + public void testEmptyLazyField() throws Exception { + LazyFieldLite field = new LazyFieldLite(); + assertEquals(0, field.getSerializedSize()); + assertEquals(ByteString.EMPTY, field.toByteString()); + } + + public void testInvalidProto() throws Exception { + // Silently fails and uses the default instance. + LazyFieldLite field = new LazyFieldLite( + TestUtil.getExtensionRegistry(), ByteString.copyFromUtf8("invalid")); + assertEquals( + TestAllTypes.getDefaultInstance(), field.getValue(TestAllTypes.getDefaultInstance())); + assertEquals(0, field.getSerializedSize()); + assertEquals(ByteString.EMPTY, field.toByteString()); + } + + public void testMergeBeforeParsing() throws Exception { + TestAllTypes message1 = TestAllTypes.newBuilder().setOptionalInt32(1).build(); + LazyFieldLite field1 = createLazyFieldLiteFromMessage(message1); + TestAllTypes message2 = TestAllTypes.newBuilder().setOptionalInt64(2).build(); + LazyFieldLite field2 = createLazyFieldLiteFromMessage(message2); + + field1.merge(field2); + TestAllTypes expected = + TestAllTypes.newBuilder().setOptionalInt32(1).setOptionalInt64(2).build(); + assertEquals(expected, field1.getValue(TestAllTypes.getDefaultInstance())); + } + + public void testMergeOneNotParsed() throws Exception { + // Test a few different paths that involve one message that was not parsed. + TestAllTypes message1 = TestAllTypes.newBuilder().setOptionalInt32(1).build(); + TestAllTypes message2 = TestAllTypes.newBuilder().setOptionalInt64(2).build(); + TestAllTypes expected = + TestAllTypes.newBuilder().setOptionalInt32(1).setOptionalInt64(2).build(); + + LazyFieldLite field1 = LazyFieldLite.fromValue(message1); + field1.getValue(TestAllTypes.getDefaultInstance()); // Force parsing. + LazyFieldLite field2 = createLazyFieldLiteFromMessage(message2); + field1.merge(field2); + assertEquals(expected, field1.getValue(TestAllTypes.getDefaultInstance())); + + // Now reverse which one is parsed first. + field1 = LazyFieldLite.fromValue(message1); + field2 = createLazyFieldLiteFromMessage(message2); + field2.getValue(TestAllTypes.getDefaultInstance()); // Force parsing. + field1.merge(field2); + assertEquals(expected, field1.getValue(TestAllTypes.getDefaultInstance())); + } + + public void testMergeInvalid() throws Exception { + // Test a few different paths that involve one message that was not parsed. + TestAllTypes message = TestAllTypes.newBuilder().setOptionalInt32(1).build(); + LazyFieldLite valid = LazyFieldLite.fromValue(message); + LazyFieldLite invalid = new LazyFieldLite( + TestUtil.getExtensionRegistry(), ByteString.copyFromUtf8("invalid")); + invalid.merge(valid); + + // We swallow the exception and just use the set field. + assertEquals(message, invalid.getValue(TestAllTypes.getDefaultInstance())); + } + + public void testMergeKeepsExtensionsWhenPossible() throws Exception { + // In this test we attempt to only use the empty registry, which will strip out all extensions + // when serializing and then parsing. We verify that each code path will attempt to not + // serialize and parse a message that was set directly without going through the + // extensionRegistry. + TestAllExtensions messageWithExtensions = + TestAllExtensions.newBuilder().setExtension(optionalInt32Extension, 42).build(); + TestAllExtensions emptyMessage = TestAllExtensions.newBuilder().build(); + + ExtensionRegistryLite emptyRegistry = ExtensionRegistryLite.getEmptyRegistry(); + + LazyFieldLite field = LazyFieldLite.fromValue(messageWithExtensions); + field.merge(createLazyFieldLiteFromMessage(emptyRegistry, emptyMessage)); + assertEquals(messageWithExtensions, field.getValue(TestAllExtensions.getDefaultInstance())); + + // Now reverse the order of the merging. + field = createLazyFieldLiteFromMessage(emptyRegistry, emptyMessage); + field.merge(LazyFieldLite.fromValue(messageWithExtensions)); + assertEquals(messageWithExtensions, field.getValue(TestAllExtensions.getDefaultInstance())); + + // Now try parsing the empty field first. + field = LazyFieldLite.fromValue(messageWithExtensions); + LazyFieldLite other = createLazyFieldLiteFromMessage(emptyRegistry, emptyMessage); + other.getValue(TestAllExtensions.getDefaultInstance()); // Force parsing. + field.merge(other); + assertEquals(messageWithExtensions, field.getValue(TestAllExtensions.getDefaultInstance())); + + // And again reverse. + field = createLazyFieldLiteFromMessage(emptyRegistry, emptyMessage); + field.getValue(TestAllExtensions.getDefaultInstance()); // Force parsing. + other = LazyFieldLite.fromValue(messageWithExtensions); + field.merge(other); + assertEquals(messageWithExtensions, field.getValue(TestAllExtensions.getDefaultInstance())); + } + + public void testMergeMightLoseExtensions() throws Exception { + // Test that we don't know about the extensions when parsing. + TestAllExtensions message1 = + TestAllExtensions.newBuilder().setExtension(optionalInt32Extension, 1).build(); + TestAllExtensions message2 = + TestAllExtensions.newBuilder().setExtension(optionalInt64Extension, 2L).build(); + + LazyFieldLite field = LazyFieldLite.fromValue(message1); + field.merge(LazyFieldLite.fromValue(message2)); + + // We lose the extensions from message 2 because we have to serialize it and then parse it + // again, using the empty registry this time. + TestAllExtensions value = + (TestAllExtensions) field.getValue(TestAllExtensions.getDefaultInstance()); + assertTrue(value.hasExtension(optionalInt32Extension)); + assertEquals(Integer.valueOf(1), value.getExtension(optionalInt32Extension)); + assertFalse(value.hasExtension(optionalInt64Extension)); + + // The field is still there, it is just unknown. + assertTrue(value.getUnknownFields() + .hasField(optionalInt64Extension.getDescriptor().getNumber())); + } + + + // Help methods. + + private LazyFieldLite createLazyFieldLiteFromMessage(MessageLite message) { + return createLazyFieldLiteFromMessage(TestUtil.getExtensionRegistry(), message); + } + + private LazyFieldLite createLazyFieldLiteFromMessage( + ExtensionRegistryLite extensionRegistry, MessageLite message) { + ByteString bytes = message.toByteString(); + return new LazyFieldLite(extensionRegistry, bytes); + } + + private void changeValue(LazyFieldLite lazyField) { + TestAllTypes.Builder builder = TestUtil.getAllSet().toBuilder(); + builder.addRepeatedBool(true); + MessageLite newMessage = builder.build(); + lazyField.setValue(newMessage); + } + + private void assertNotEqual(Object unexpected, Object actual) { + assertFalse(unexpected == actual + || (unexpected != null && unexpected.equals(actual))); + } + +} diff --git a/java/core/src/test/java/com/google/protobuf/LazyFieldTest.java b/java/core/src/test/java/com/google/protobuf/LazyFieldTest.java new file mode 100644 index 00000000..2b900065 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/LazyFieldTest.java @@ -0,0 +1,121 @@ +// 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.UnittestProto.TestAllExtensions; +import protobuf_unittest.UnittestProto.TestAllTypes; + +import java.io.IOException; +import junit.framework.TestCase; + +/** + * Unit test for {@link LazyField}. + * + * @author xiangl@google.com (Xiang Li) + */ +public class LazyFieldTest extends TestCase { + public void testHashCode() { + MessageLite message = TestUtil.getAllSet(); + LazyField lazyField = + createLazyFieldFromMessage(message); + assertEquals(message.hashCode(), lazyField.hashCode()); + lazyField.getValue(); + assertEquals(message.hashCode(), lazyField.hashCode()); + changeValue(lazyField); + // make sure two messages have different hash code + assertNotEqual(message.hashCode(), lazyField.hashCode()); + } + + public void testHashCodeEx() throws Exception { + TestAllExtensions message = TestUtil.getAllExtensionsSet(); + LazyField lazyField = createLazyFieldFromMessage(message); + assertEquals(message.hashCode(), lazyField.hashCode()); + lazyField.getValue(); + assertEquals(message.hashCode(), lazyField.hashCode()); + changeValue(lazyField); + // make sure two messages have different hash code + assertNotEqual(message.hashCode(), lazyField.hashCode()); + } + + public void testGetValue() { + MessageLite message = TestUtil.getAllSet(); + LazyField lazyField = createLazyFieldFromMessage(message); + assertEquals(message, lazyField.getValue()); + changeValue(lazyField); + assertNotEqual(message, lazyField.getValue()); + } + + public void testGetValueEx() throws Exception { + TestAllExtensions message = TestUtil.getAllExtensionsSet(); + LazyField lazyField = createLazyFieldFromMessage(message); + assertEquals(message, lazyField.getValue()); + changeValue(lazyField); + assertNotEqual(message, lazyField.getValue()); + } + + public void testEqualsObject() { + MessageLite message = TestUtil.getAllSet(); + LazyField lazyField = createLazyFieldFromMessage(message); + assertTrue(lazyField.equals(message)); + changeValue(lazyField); + assertFalse(lazyField.equals(message)); + assertFalse(message.equals(lazyField.getValue())); + } + + public void testEqualsObjectEx() throws Exception { + TestAllExtensions message = TestUtil.getAllExtensionsSet(); + LazyField lazyField = createLazyFieldFromMessage(message); + assertTrue(lazyField.equals(message)); + changeValue(lazyField); + assertFalse(lazyField.equals(message)); + assertFalse(message.equals(lazyField.getValue())); + } + + // Help methods. + + private LazyField createLazyFieldFromMessage(MessageLite message) { + ByteString bytes = message.toByteString(); + return new LazyField(message.getDefaultInstanceForType(), + TestUtil.getExtensionRegistry(), bytes); + } + + private void changeValue(LazyField lazyField) { + TestAllTypes.Builder builder = TestUtil.getAllSet().toBuilder(); + builder.addRepeatedBool(true); + MessageLite newMessage = builder.build(); + lazyField.setValue(newMessage); + } + + private void assertNotEqual(Object unexpected, Object actual) { + assertFalse(unexpected == actual + || (unexpected != null && unexpected.equals(actual))); + } +} diff --git a/java/core/src/test/java/com/google/protobuf/LazyMessageLiteTest.java b/java/core/src/test/java/com/google/protobuf/LazyMessageLiteTest.java new file mode 100644 index 00000000..afe0fffd --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/LazyMessageLiteTest.java @@ -0,0 +1,307 @@ +// 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.LazyFieldsLite.LazyExtension; +import protobuf_unittest.LazyFieldsLite.LazyInnerMessageLite; +import protobuf_unittest.LazyFieldsLite.LazyMessageLite; +import protobuf_unittest.LazyFieldsLite.LazyNestedInnerMessageLite; + +import junit.framework.TestCase; + +import java.util.ArrayList; + +/** + * Unit test for messages with lazy fields. + * + * @author niwasaki@google.com (Naoki Iwasaki) + */ +public class LazyMessageLiteTest extends TestCase { + + private Parser<LazyInnerMessageLite> originalLazyInnerMessageLiteParser; + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + public void testSetValues() { + LazyNestedInnerMessageLite nested = LazyNestedInnerMessageLite.newBuilder() + .setNum(3) + .build(); + LazyInnerMessageLite inner = LazyInnerMessageLite.newBuilder() + .setNum(2) + .setNested(nested) + .build(); + LazyMessageLite outer = LazyMessageLite.newBuilder() + .setNum(1) + .setInner(inner) + .setOneofNum(123) + .setOneofInner(inner) + .build(); + + assertEquals(1, outer.getNum()); + assertEquals(421, outer.getNumWithDefault()); + + assertEquals(2, outer.getInner().getNum()); + assertEquals(42, outer.getInner().getNumWithDefault()); + + assertEquals(3, outer.getInner().getNested().getNum()); + assertEquals(4, outer.getInner().getNested().getNumWithDefault()); + + assertFalse(outer.hasOneofNum()); + assertTrue(outer.hasOneofInner()); + + assertEquals(2, outer.getOneofInner().getNum()); + assertEquals(42, outer.getOneofInner().getNumWithDefault()); + assertEquals(3, outer.getOneofInner().getNested().getNum()); + assertEquals(4, outer.getOneofInner().getNested().getNumWithDefault()); + } + + public void testSetRepeatedValues() { + LazyMessageLite outer = LazyMessageLite.newBuilder() + .setNum(1) + .addRepeatedInner(LazyInnerMessageLite.newBuilder().setNum(119)) + .addRepeatedInner(LazyInnerMessageLite.newBuilder().setNum(122)) + .build(); + + assertEquals(1, outer.getNum()); + assertEquals(2, outer.getRepeatedInnerCount()); + assertEquals(119, outer.getRepeatedInner(0).getNum()); + assertEquals(122, outer.getRepeatedInner(1).getNum()); + } + + public void testAddAll() { + ArrayList<LazyInnerMessageLite> inners = new ArrayList<LazyInnerMessageLite>(); + int count = 4; + for (int i = 0; i < count; i++) { + LazyInnerMessageLite inner = LazyInnerMessageLite.newBuilder() + .setNum(i) + .build(); + inners.add(inner); + } + + LazyMessageLite outer = LazyMessageLite.newBuilder() + .addAllRepeatedInner(inners) + .build(); + assertEquals(count, outer.getRepeatedInnerCount()); + for (int i = 0; i < count; i++) { + assertEquals(i, outer.getRepeatedInner(i).getNum()); + } + } + + public void testGetDefaultValues() { + LazyMessageLite outer = LazyMessageLite.newBuilder() + .build(); + + assertEquals(0, outer.getNum()); + assertEquals(421, outer.getNumWithDefault()); + + assertEquals(0, outer.getInner().getNum()); + assertEquals(42, outer.getInner().getNumWithDefault()); + + assertEquals(0, outer.getInner().getNested().getNum()); + assertEquals(4, outer.getInner().getNested().getNumWithDefault()); + + assertEquals(0, outer.getOneofNum()); + + assertEquals(0, outer.getOneofInner().getNum()); + assertEquals(42, outer.getOneofInner().getNumWithDefault()); + assertEquals(0, outer.getOneofInner().getNested().getNum()); + assertEquals(4, outer.getOneofInner().getNested().getNumWithDefault()); + } + + public void testClearValues() { + LazyInnerMessageLite inner = LazyInnerMessageLite.newBuilder() + .setNum(115) + .build(); + + LazyMessageLite.Builder outerBuilder = LazyMessageLite.newBuilder(); + + assertEquals(0, outerBuilder.build().getNum()); + + + // Set/Clear num + outerBuilder.setNum(100); + + assertEquals(100, outerBuilder.build().getNum()); + assertEquals(421, outerBuilder.build().getNumWithDefault()); + assertFalse(outerBuilder.build().hasInner()); + + outerBuilder.clearNum(); + + assertEquals(0, outerBuilder.build().getNum()); + assertEquals(421, outerBuilder.build().getNumWithDefault()); + assertFalse(outerBuilder.build().hasInner()); + + + // Set/Clear all + outerBuilder.setNum(100) + .setInner(inner) + .addRepeatedInner(LazyInnerMessageLite.newBuilder().setNum(119)) + .addRepeatedInner(LazyInnerMessageLite.newBuilder().setNum(122)) + .setOneofInner(LazyInnerMessageLite.newBuilder().setNum(123)); + + LazyMessageLite outer = outerBuilder.build(); + assertEquals(100, outer.getNum()); + assertEquals(421, outer.getNumWithDefault()); + assertTrue(outer.hasInner()); + assertEquals(115, outer.getInner().getNum()); + assertEquals(2, outer.getRepeatedInnerCount()); + assertEquals(119, outer.getRepeatedInner(0).getNum()); + assertEquals(122, outer.getRepeatedInner(1).getNum()); + assertTrue(outer.hasOneofInner()); + assertEquals(123, outer.getOneofInner().getNum()); + + outerBuilder.clear(); + + outer = outerBuilder.build(); + + assertEquals(0, outer.getNum()); + assertEquals(421, outer.getNumWithDefault()); + assertFalse(outer.hasInner()); + assertEquals(0, outer.getRepeatedInnerCount()); + assertFalse(outer.hasOneofInner()); + assertEquals(0, outer.getOneofInner().getNum()); + } + + public void testMergeValues() { + LazyMessageLite outerBase = LazyMessageLite.newBuilder() + .setNumWithDefault(122) + .build(); + + LazyInnerMessageLite innerMerging = LazyInnerMessageLite.newBuilder() + .setNum(115) + .build(); + LazyMessageLite outerMerging = LazyMessageLite.newBuilder() + .setNum(119) + .setInner(innerMerging) + .setOneofInner(innerMerging) + .build(); + + LazyMessageLite merged = LazyMessageLite + .newBuilder(outerBase) + .mergeFrom(outerMerging) + .build(); + assertEquals(119, merged.getNum()); + assertEquals(122, merged.getNumWithDefault()); + assertEquals(115, merged.getInner().getNum()); + assertEquals(42, merged.getInner().getNumWithDefault()); + assertEquals(115, merged.getOneofInner().getNum()); + assertEquals(42, merged.getOneofInner().getNumWithDefault()); + } + + public void testMergeDefaultValues() { + LazyInnerMessageLite innerBase = LazyInnerMessageLite.newBuilder() + .setNum(115) + .build(); + LazyMessageLite outerBase = LazyMessageLite.newBuilder() + .setNum(119) + .setNumWithDefault(122) + .setInner(innerBase) + .setOneofInner(innerBase) + .build(); + + LazyMessageLite outerMerging = LazyMessageLite.newBuilder() + .build(); + + LazyMessageLite merged = LazyMessageLite + .newBuilder(outerBase) + .mergeFrom(outerMerging) + .build(); + // Merging default-instance shouldn't overwrite values in the base message. + assertEquals(119, merged.getNum()); + assertEquals(122, merged.getNumWithDefault()); + assertEquals(115, merged.getInner().getNum()); + assertEquals(42, merged.getInner().getNumWithDefault()); + assertEquals(115, merged.getOneofInner().getNum()); + assertEquals(42, merged.getOneofInner().getNumWithDefault()); + } + + public void testSerialize() throws InvalidProtocolBufferException { + LazyNestedInnerMessageLite nested = LazyNestedInnerMessageLite.newBuilder() + .setNum(3) + .build(); + LazyInnerMessageLite inner = LazyInnerMessageLite.newBuilder() + .setNum(2) + .setNested(nested) + .build(); + LazyMessageLite outer = LazyMessageLite.newBuilder() + .setNum(1) + .setInner(inner) + .setOneofInner(inner) + .build(); + + ByteString bytes = outer.toByteString(); + assertEquals(bytes.size(), outer.getSerializedSize()); + + LazyMessageLite deserialized = LazyMessageLite.parseFrom(bytes); + + assertEquals(1, deserialized.getNum()); + assertEquals(421, deserialized.getNumWithDefault()); + + assertEquals(2, deserialized.getInner().getNum()); + assertEquals(42, deserialized.getInner().getNumWithDefault()); + + assertEquals(3, deserialized.getInner().getNested().getNum()); + assertEquals(4, deserialized.getInner().getNested().getNumWithDefault()); + + assertEquals(2, deserialized.getOneofInner().getNum()); + assertEquals(42, deserialized.getOneofInner().getNumWithDefault()); + assertEquals(3, deserialized.getOneofInner().getNested().getNum()); + assertEquals(4, deserialized.getOneofInner().getNested().getNumWithDefault()); + + assertEquals(bytes, deserialized.toByteString()); + } + + public void testExtensions() throws Exception { + LazyInnerMessageLite.Builder innerBuilder = LazyInnerMessageLite.newBuilder(); + innerBuilder.setExtension( + LazyExtension.extension, LazyExtension.newBuilder() + .setName("name").build()); + assertTrue(innerBuilder.hasExtension(LazyExtension.extension)); + assertEquals("name", innerBuilder.getExtension(LazyExtension.extension).getName()); + + LazyInnerMessageLite innerMessage = innerBuilder.build(); + assertTrue(innerMessage.hasExtension(LazyExtension.extension)); + assertEquals("name", innerMessage.getExtension(LazyExtension.extension).getName()); + + LazyMessageLite lite = LazyMessageLite.newBuilder() + .setInner(innerMessage).build(); + assertTrue(lite.getInner().hasExtension(LazyExtension.extension)); + assertEquals("name", lite.getInner().getExtension(LazyExtension.extension).getName()); + } +} diff --git a/java/core/src/test/java/com/google/protobuf/LazyStringArrayListTest.java b/java/core/src/test/java/com/google/protobuf/LazyStringArrayListTest.java new file mode 100644 index 00000000..0f42ac50 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/LazyStringArrayListTest.java @@ -0,0 +1,362 @@ +// 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 static java.util.Arrays.asList; + +import junit.framework.TestCase; + +import java.util.ArrayList; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.List; + +/** + * Tests for {@link LazyStringArrayList}. + * + * @author jonp@google.com (Jon Perlow) + */ +public class LazyStringArrayListTest extends TestCase { + + private static String STRING_A = "A"; + private static String STRING_B = "B"; + private static String STRING_C = "C"; + + private static ByteString BYTE_STRING_A = ByteString.copyFromUtf8("A"); + private static ByteString BYTE_STRING_B = ByteString.copyFromUtf8("B"); + private static ByteString BYTE_STRING_C = ByteString.copyFromUtf8("C"); + + public void testJustStrings() { + LazyStringArrayList list = new LazyStringArrayList(); + list.add(STRING_A); + list.add(STRING_B); + list.add(STRING_C); + + assertEquals(3, list.size()); + assertSame(STRING_A, list.get(0)); + assertSame(STRING_B, list.get(1)); + assertSame(STRING_C, list.get(2)); + + list.set(1, STRING_C); + assertSame(STRING_C, list.get(1)); + + list.remove(1); + assertSame(STRING_A, list.get(0)); + assertSame(STRING_C, list.get(1)); + + List<ByteString> byteStringList = list.asByteStringList(); + assertEquals(BYTE_STRING_A, byteStringList.get(0)); + assertEquals(BYTE_STRING_C, byteStringList.get(1)); + + // Underlying list should be transformed. + assertSame(byteStringList.get(0), list.getByteString(0)); + assertSame(byteStringList.get(1), list.getByteString(1)); + } + + public void testJustByteString() { + LazyStringArrayList list = new LazyStringArrayList(); + list.add(BYTE_STRING_A); + list.add(BYTE_STRING_B); + list.add(BYTE_STRING_C); + + assertEquals(3, list.size()); + assertSame(BYTE_STRING_A, list.getByteString(0)); + assertSame(BYTE_STRING_B, list.getByteString(1)); + assertSame(BYTE_STRING_C, list.getByteString(2)); + + list.remove(1); + assertSame(BYTE_STRING_A, list.getByteString(0)); + assertSame(BYTE_STRING_C, list.getByteString(1)); + + List<ByteString> byteStringList = list.asByteStringList(); + assertSame(BYTE_STRING_A, byteStringList.get(0)); + assertSame(BYTE_STRING_C, byteStringList.get(1)); + } + + public void testConversionBackAndForth() { + LazyStringArrayList list = new LazyStringArrayList(); + list.add(STRING_A); + list.add(BYTE_STRING_B); + list.add(BYTE_STRING_C); + + // String a should be the same because it was originally a string + assertSame(STRING_A, list.get(0)); + + // String b and c should be different because the string has to be computed + // from the ByteString + String bPrime = list.get(1); + assertNotSame(STRING_B, bPrime); + assertEquals(STRING_B, bPrime); + String cPrime = list.get(2); + assertNotSame(STRING_C, cPrime); + assertEquals(STRING_C, cPrime); + + // String c and c should stay the same once cached. + assertSame(bPrime, list.get(1)); + assertSame(cPrime, list.get(2)); + + // ByteString needs to be computed from string for both a and b + ByteString aPrimeByteString = list.getByteString(0); + assertEquals(BYTE_STRING_A, aPrimeByteString); + ByteString bPrimeByteString = list.getByteString(1); + assertNotSame(BYTE_STRING_B, bPrimeByteString); + assertEquals(BYTE_STRING_B, list.getByteString(1)); + + // Once cached, ByteString should stay cached. + assertSame(aPrimeByteString, list.getByteString(0)); + assertSame(bPrimeByteString, list.getByteString(1)); + } + + public void testCopyConstructorCopiesByReference() { + LazyStringArrayList list1 = new LazyStringArrayList(); + list1.add(STRING_A); + list1.add(BYTE_STRING_B); + list1.add(BYTE_STRING_C); + + LazyStringArrayList list2 = new LazyStringArrayList(list1); + assertEquals(3, list2.size()); + assertSame(STRING_A, list2.get(0)); + assertSame(BYTE_STRING_B, list2.getByteString(1)); + assertSame(BYTE_STRING_C, list2.getByteString(2)); + } + + public void testListCopyConstructor() { + List<String> list1 = new ArrayList<String>(); + list1.add(STRING_A); + list1.add(STRING_B); + list1.add(STRING_C); + + LazyStringArrayList list2 = new LazyStringArrayList(list1); + assertEquals(3, list2.size()); + assertSame(STRING_A, list2.get(0)); + assertSame(STRING_B, list2.get(1)); + assertSame(STRING_C, list2.get(2)); + } + + public void testAddAllCopiesByReferenceIfPossible() { + LazyStringArrayList list1 = new LazyStringArrayList(); + list1.add(STRING_A); + list1.add(BYTE_STRING_B); + list1.add(BYTE_STRING_C); + + LazyStringArrayList list2 = new LazyStringArrayList(); + list2.addAll(list1); + + assertEquals(3, list2.size()); + assertSame(STRING_A, list2.get(0)); + assertSame(BYTE_STRING_B, list2.getByteString(1)); + assertSame(BYTE_STRING_C, list2.getByteString(2)); + } + + public void testModificationWithIteration() { + LazyStringArrayList list = new LazyStringArrayList(); + list.addAll(asList(STRING_A, STRING_B, STRING_C)); + Iterator<String> iterator = list.iterator(); + assertEquals(3, list.size()); + assertEquals(STRING_A, list.get(0)); + assertEquals(STRING_A, iterator.next()); + + // Does not structurally modify. + iterator = list.iterator(); + list.set(0, STRING_B); + iterator.next(); + + list.remove(0); + try { + iterator.next(); + fail(); + } catch (ConcurrentModificationException e) { + // expected + } + + iterator = list.iterator(); + list.add(0, STRING_C); + try { + iterator.next(); + fail(); + } catch (ConcurrentModificationException e) { + // expected + } + } + + public void testMakeImmutable() { + LazyStringArrayList list = new LazyStringArrayList(); + list.add(STRING_A); + list.add(STRING_B); + list.add(STRING_C); + list.makeImmutable(); + assertGenericListImmutable(list, STRING_A); + + // LazyStringArrayList has extra methods not covered in the generic + // assertion. + + try { + list.add(BYTE_STRING_A.toByteArray()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.add(BYTE_STRING_A); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAllByteArray(asList(BYTE_STRING_A.toByteArray())); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAllByteString(asList(BYTE_STRING_A)); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.mergeFrom(new LazyStringArrayList()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.set(0, BYTE_STRING_A.toByteArray()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.set(0, BYTE_STRING_A); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + } + + public void testImmutabilityPropagation() { + LazyStringArrayList list = new LazyStringArrayList(); + list.add(STRING_A); + list.makeImmutable(); + + assertGenericListImmutable(list.asByteStringList(), BYTE_STRING_A); + + // Arrays use reference equality so need to retrieve the underlying value + // to properly test deep immutability. + List<byte[]> byteArrayList = list.asByteArrayList(); + assertGenericListImmutable(byteArrayList, byteArrayList.get(0)); + } + + private static <T> void assertGenericListImmutable(List<T> list, T value) { + try { + list.add(value); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.add(0, value); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(asList(value)); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(0, asList(value)); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.clear(); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.remove(0); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.remove(value); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.removeAll(asList(value)); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.retainAll(asList()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.retainAll(asList()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.set(0, value); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + } +} diff --git a/java/core/src/test/java/com/google/protobuf/LazyStringEndToEndTest.java b/java/core/src/test/java/com/google/protobuf/LazyStringEndToEndTest.java new file mode 100644 index 00000000..0ef414aa --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/LazyStringEndToEndTest.java @@ -0,0 +1,132 @@ +// 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.UnittestProto; + +import junit.framework.TestCase; + +import java.io.IOException; + +/** + * Tests to make sure the lazy conversion of UTF8-encoded byte arrays to + * strings works correctly. + * + * @author jonp@google.com (Jon Perlow) + */ +public class LazyStringEndToEndTest extends TestCase { + + private static ByteString TEST_ALL_TYPES_SERIALIZED_WITH_ILLEGAL_UTF8 = + ByteString.copyFrom(new byte[] { + 114, 4, -1, 0, -1, 0, -30, 2, 4, -1, + 0, -1, 0, -30, 2, 4, -1, 0, -1, 0, }); + + private ByteString encodedTestAllTypes; + + @Override + protected void setUp() throws Exception { + super.setUp(); + this.encodedTestAllTypes = UnittestProto.TestAllTypes.newBuilder() + .setOptionalString("foo") + .addRepeatedString("bar") + .addRepeatedString("baz") + .build() + .toByteString(); + } + + /** + * Tests that an invalid UTF8 string will roundtrip through a parse + * and serialization. + */ + public void testParseAndSerialize() throws InvalidProtocolBufferException { + UnittestProto.TestAllTypes tV2 = UnittestProto.TestAllTypes.parseFrom( + TEST_ALL_TYPES_SERIALIZED_WITH_ILLEGAL_UTF8); + ByteString bytes = tV2.toByteString(); + assertEquals(TEST_ALL_TYPES_SERIALIZED_WITH_ILLEGAL_UTF8, bytes); + + tV2.getOptionalString(); + bytes = tV2.toByteString(); + assertEquals(TEST_ALL_TYPES_SERIALIZED_WITH_ILLEGAL_UTF8, bytes); + } + + public void testParseAndWrite() throws IOException { + UnittestProto.TestAllTypes tV2 = UnittestProto.TestAllTypes.parseFrom( + TEST_ALL_TYPES_SERIALIZED_WITH_ILLEGAL_UTF8); + byte[] sink = new byte[TEST_ALL_TYPES_SERIALIZED_WITH_ILLEGAL_UTF8.size()]; + CodedOutputStream outputStream = CodedOutputStream.newInstance(sink); + tV2.writeTo(outputStream); + outputStream.flush(); + assertEquals( + TEST_ALL_TYPES_SERIALIZED_WITH_ILLEGAL_UTF8, + ByteString.copyFrom(sink)); + } + + public void testCaching() { + String a = "a"; + String b = "b"; + String c = "c"; + UnittestProto.TestAllTypes proto = UnittestProto.TestAllTypes.newBuilder() + .setOptionalString(a) + .addRepeatedString(b) + .addRepeatedString(c) + .build(); + + // String should be the one we passed it. + assertSame(a, proto.getOptionalString()); + assertSame(b, proto.getRepeatedString(0)); + assertSame(c, proto.getRepeatedString(1)); + + + // Ensure serialization keeps strings cached. + proto.toByteString(); + + // And now the string should stay cached. + assertSame(a, proto.getOptionalString()); + assertSame(b, proto.getRepeatedString(0)); + assertSame(c, proto.getRepeatedString(1)); + } + + public void testNoStringCachingIfOnlyBytesAccessed() throws Exception { + UnittestProto.TestAllTypes proto = + UnittestProto.TestAllTypes.parseFrom(encodedTestAllTypes); + ByteString optional = proto.getOptionalStringBytes(); + assertSame(optional, proto.getOptionalStringBytes()); + assertSame(optional, proto.toBuilder().getOptionalStringBytes()); + + ByteString repeated0 = proto.getRepeatedStringBytes(0); + ByteString repeated1 = proto.getRepeatedStringBytes(1); + assertSame(repeated0, proto.getRepeatedStringBytes(0)); + assertSame(repeated1, proto.getRepeatedStringBytes(1)); + assertSame(repeated0, proto.toBuilder().getRepeatedStringBytes(0)); + assertSame(repeated1, proto.toBuilder().getRepeatedStringBytes(1)); + } +} diff --git a/java/core/src/test/java/com/google/protobuf/LiteEqualsAndHashTest.java b/java/core/src/test/java/com/google/protobuf/LiteEqualsAndHashTest.java new file mode 100644 index 00000000..035917c8 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/LiteEqualsAndHashTest.java @@ -0,0 +1,108 @@ +// 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.Bar; +import protobuf_unittest.lite_equals_and_hash.LiteEqualsAndHash.BarPrime; +import protobuf_unittest.lite_equals_and_hash.LiteEqualsAndHash.Foo; + +import junit.framework.TestCase; + +/** + * Test generate equal and hash methods for the lite runtime. + * + * @author pbogle@google.com Phil Bogle + */ +public class LiteEqualsAndHashTest extends TestCase { + + public void testEquals() throws Exception { + // Since the generated equals and hashCode methods for lite messages are a + // mostly complete subset of those for regular messages, we can mostly assume + // that the generated methods are already thoroughly tested by the regular tests. + + // This test mostly just verifies is that a proto with + // optimize_for = LITE_RUNTIME and java_generates_equals_and_hash_compiles + // correctly when linked only against the lite library. + + // We do however do some basic testing to make sure that equals is actually + // overriden to test for value equality rather than simple object equality. + + // Check that two identical objs are equal. + Foo foo1a = Foo.newBuilder() + .setValue(1) + .addBar(Bar.newBuilder().setName("foo1")) + .build(); + Foo foo1b = Foo.newBuilder() + .setValue(1) + .addBar(Bar.newBuilder().setName("foo1")) + .build(); + Foo foo2 = Foo.newBuilder() + .setValue(1) + .addBar(Bar.newBuilder().setName("foo2")) + .build(); + + // Check that equals is doing value rather than object equality. + assertEquals(foo1a, foo1b); + assertEquals(foo1a.hashCode(), foo1b.hashCode()); + + // Check that a diffeent object is not equal. + assertFalse(foo1a.equals(foo2)); + + // Check that two objects which have different types but the same field values are not + // considered to be equal. + Bar bar = Bar.newBuilder().setName("bar").build(); + 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/core/src/test/java/com/google/protobuf/LiteTest.java b/java/core/src/test/java/com/google/protobuf/LiteTest.java new file mode 100644 index 00000000..b1f298ff --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/LiteTest.java @@ -0,0 +1,1462 @@ +// 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 static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; + +import com.google.protobuf.UnittestLite; +import com.google.protobuf.UnittestLite.ForeignEnumLite; +import com.google.protobuf.UnittestLite.ForeignMessageLite; +import com.google.protobuf.UnittestLite.TestAllExtensionsLite; +import com.google.protobuf.UnittestLite.TestAllTypesLite; +import com.google.protobuf.UnittestLite.TestAllTypesLite.NestedMessage; +import com.google.protobuf.UnittestLite.TestAllTypesLite.OneofFieldCase; +import com.google.protobuf.UnittestLite.TestAllTypesLite.OptionalGroup; +import com.google.protobuf.UnittestLite.TestAllTypesLite.RepeatedGroup; +import com.google.protobuf.UnittestLite.TestAllTypesLiteOrBuilder; +import com.google.protobuf.UnittestLite.TestNestedExtensionLite; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +/** + * Test lite runtime. + * + * @author kenton@google.com Kenton Varda + */ +public class LiteTest extends TestCase { + public void setUp() throws Exception { + // Test that nested extensions are initialized correctly even if the outer + // class has not been accessed directly. This was once a bug with lite + // messages. + // + // We put this in setUp() rather than in its own test method because we + // need to make sure it runs before any actual tests. + assertTrue(TestNestedExtensionLite.nestedExtension != null); + } + + public void testLite() throws Exception { + // Since lite messages are a subset of regular messages, we can mostly + // assume that the functionality of lite messages is already thoroughly + // tested by the regular tests. All this test really verifies is that + // a proto with optimize_for = LITE_RUNTIME compiles correctly when + // linked only against the lite library. That is all tested at compile + // time, leaving not much to do in this method. Let's just do some random + // stuff to make sure the lite message is actually here and usable. + + TestAllTypesLite message = + TestAllTypesLite.newBuilder() + .setOptionalInt32(123) + .addRepeatedString("hello") + .setOptionalNestedMessage( + TestAllTypesLite.NestedMessage.newBuilder().setBb(7)) + .build(); + + ByteString data = message.toByteString(); + + TestAllTypesLite message2 = TestAllTypesLite.parseFrom(data); + + assertEquals(123, message2.getOptionalInt32()); + assertEquals(1, message2.getRepeatedStringCount()); + assertEquals("hello", message2.getRepeatedString(0)); + assertEquals(7, message2.getOptionalNestedMessage().getBb()); + } + + public void testLiteExtensions() throws Exception { + // TODO(kenton): Unlike other features of the lite library, extensions are + // implemented completely differently from the regular library. We + // should probably test them more thoroughly. + + TestAllExtensionsLite message = + TestAllExtensionsLite.newBuilder() + .setExtension(UnittestLite.optionalInt32ExtensionLite, 123) + .addExtension(UnittestLite.repeatedStringExtensionLite, "hello") + .setExtension(UnittestLite.optionalNestedEnumExtensionLite, + TestAllTypesLite.NestedEnum.BAZ) + .setExtension(UnittestLite.optionalNestedMessageExtensionLite, + TestAllTypesLite.NestedMessage.newBuilder().setBb(7).build()) + .build(); + + // Test copying a message, since coping extensions actually does use a + // different code path between lite and regular libraries, and as of this + // writing, parsing hasn't been implemented yet. + TestAllExtensionsLite message2 = message.toBuilder().build(); + + assertEquals(123, (int) message2.getExtension( + UnittestLite.optionalInt32ExtensionLite)); + assertEquals(1, message2.getExtensionCount( + UnittestLite.repeatedStringExtensionLite)); + assertEquals(1, message2.getExtension( + UnittestLite.repeatedStringExtensionLite).size()); + assertEquals("hello", message2.getExtension( + UnittestLite.repeatedStringExtensionLite, 0)); + assertEquals(TestAllTypesLite.NestedEnum.BAZ, message2.getExtension( + UnittestLite.optionalNestedEnumExtensionLite)); + assertEquals(7, message2.getExtension( + UnittestLite.optionalNestedMessageExtensionLite).getBb()); + } + + public void testSerialize() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + TestAllTypesLite expected = + TestAllTypesLite.newBuilder() + .setOptionalInt32(123) + .addRepeatedString("hello") + .setOptionalNestedMessage( + TestAllTypesLite.NestedMessage.newBuilder().setBb(7)) + .build(); + ObjectOutputStream out = new ObjectOutputStream(baos); + try { + out.writeObject(expected); + } finally { + out.close(); + } + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + ObjectInputStream in = new ObjectInputStream(bais); + TestAllTypesLite actual = (TestAllTypesLite) in.readObject(); + assertEquals(expected.getOptionalInt32(), actual.getOptionalInt32()); + assertEquals(expected.getRepeatedStringCount(), + actual.getRepeatedStringCount()); + assertEquals(expected.getRepeatedString(0), + actual.getRepeatedString(0)); + assertEquals(expected.getOptionalNestedMessage().getBb(), + actual.getOptionalNestedMessage().getBb()); + } + + public void testClone() { + TestAllTypesLite.Builder expected = TestAllTypesLite.newBuilder() + .setOptionalInt32(123); + assertEquals( + expected.getOptionalInt32(), expected.clone().getOptionalInt32()); + + TestAllExtensionsLite.Builder expected2 = TestAllExtensionsLite.newBuilder() + .setExtension(UnittestLite.optionalInt32ExtensionLite, 123); + assertEquals( + expected2.getExtension(UnittestLite.optionalInt32ExtensionLite), + expected2.clone().getExtension(UnittestLite.optionalInt32ExtensionLite)); + } + + public void testAddAll() { + try { + TestAllTypesLite.newBuilder() + .addAllRepeatedBytes(null); + fail(); + } catch (NullPointerException e) { + // expected. + } + } + + public void testSanityCopyOnWrite() throws InvalidProtocolBufferException { + // Since builders are implemented as a thin wrapper around a message + // instance, we attempt to verify that we can't cause the builder to modify + // a produced message. + + TestAllTypesLite.Builder builder = TestAllTypesLite.newBuilder(); + TestAllTypesLite message = builder.build(); + TestAllTypesLite messageAfterBuild; + builder.setOptionalBool(true); + assertEquals(false, message.getOptionalBool()); + assertEquals(true, builder.getOptionalBool()); + messageAfterBuild = builder.build(); + assertEquals(true, messageAfterBuild.getOptionalBool()); + assertEquals(false, message.getOptionalBool()); + builder.clearOptionalBool(); + assertEquals(false, builder.getOptionalBool()); + assertEquals(true, messageAfterBuild.getOptionalBool()); + + message = builder.build(); + builder.setOptionalBytes(ByteString.copyFromUtf8("hi")); + assertEquals(ByteString.EMPTY, message.getOptionalBytes()); + assertEquals(ByteString.copyFromUtf8("hi"), builder.getOptionalBytes()); + messageAfterBuild = builder.build(); + assertEquals( + ByteString.copyFromUtf8("hi"), messageAfterBuild.getOptionalBytes()); + assertEquals(ByteString.EMPTY, message.getOptionalBytes()); + builder.clearOptionalBytes(); + assertEquals(ByteString.EMPTY, builder.getOptionalBytes()); + assertEquals( + ByteString.copyFromUtf8("hi"), messageAfterBuild.getOptionalBytes()); + + message = builder.build(); + builder.setOptionalCord("hi"); + assertEquals("", message.getOptionalCord()); + assertEquals("hi", builder.getOptionalCord()); + messageAfterBuild = builder.build(); + assertEquals("hi", messageAfterBuild.getOptionalCord()); + assertEquals("", message.getOptionalCord()); + builder.clearOptionalCord(); + assertEquals("", builder.getOptionalCord()); + assertEquals("hi", messageAfterBuild.getOptionalCord()); + + message = builder.build(); + builder.setOptionalCordBytes(ByteString.copyFromUtf8("no")); + assertEquals(ByteString.EMPTY, message.getOptionalCordBytes()); + assertEquals(ByteString.copyFromUtf8("no"), builder.getOptionalCordBytes()); + messageAfterBuild = builder.build(); + assertEquals( + ByteString.copyFromUtf8("no"), + messageAfterBuild.getOptionalCordBytes()); + assertEquals(ByteString.EMPTY, message.getOptionalCordBytes()); + builder.clearOptionalCord(); + assertEquals(ByteString.EMPTY, builder.getOptionalCordBytes()); + assertEquals( + ByteString.copyFromUtf8("no"), + messageAfterBuild.getOptionalCordBytes()); + + message = builder.build(); + builder.setOptionalDouble(1); + assertEquals(0D, message.getOptionalDouble()); + assertEquals(1D, builder.getOptionalDouble()); + messageAfterBuild = builder.build(); + assertEquals(1D, messageAfterBuild.getOptionalDouble()); + assertEquals(0D, message.getOptionalDouble()); + builder.clearOptionalDouble(); + assertEquals(0D, builder.getOptionalDouble()); + assertEquals(1D, messageAfterBuild.getOptionalDouble()); + + message = builder.build(); + builder.setOptionalFixed32(1); + assertEquals(0, message.getOptionalFixed32()); + assertEquals(1, builder.getOptionalFixed32()); + messageAfterBuild = builder.build(); + assertEquals(1, messageAfterBuild.getOptionalFixed32()); + assertEquals(0, message.getOptionalFixed32()); + builder.clearOptionalFixed32(); + assertEquals(0, builder.getOptionalFixed32()); + assertEquals(1, messageAfterBuild.getOptionalFixed32()); + + message = builder.build(); + builder.setOptionalFixed64(1); + assertEquals(0L, message.getOptionalFixed64()); + assertEquals(1L, builder.getOptionalFixed64()); + messageAfterBuild = builder.build(); + assertEquals(1L, messageAfterBuild.getOptionalFixed64()); + assertEquals(0L, message.getOptionalFixed64()); + builder.clearOptionalFixed64(); + assertEquals(0L, builder.getOptionalFixed64()); + assertEquals(1L, messageAfterBuild.getOptionalFixed64()); + + message = builder.build(); + builder.setOptionalFloat(1); + assertEquals(0F, message.getOptionalFloat()); + assertEquals(1F, builder.getOptionalFloat()); + messageAfterBuild = builder.build(); + assertEquals(1F, messageAfterBuild.getOptionalFloat()); + assertEquals(0F, message.getOptionalFloat()); + builder.clearOptionalFloat(); + assertEquals(0F, builder.getOptionalFloat()); + assertEquals(1F, messageAfterBuild.getOptionalFloat()); + + message = builder.build(); + builder.setOptionalForeignEnum(ForeignEnumLite.FOREIGN_LITE_BAR); + assertEquals( + ForeignEnumLite.FOREIGN_LITE_FOO, message.getOptionalForeignEnum()); + assertEquals( + ForeignEnumLite.FOREIGN_LITE_BAR, builder.getOptionalForeignEnum()); + messageAfterBuild = builder.build(); + assertEquals( + ForeignEnumLite.FOREIGN_LITE_BAR, + messageAfterBuild.getOptionalForeignEnum()); + assertEquals( + ForeignEnumLite.FOREIGN_LITE_FOO, message.getOptionalForeignEnum()); + builder.clearOptionalForeignEnum(); + assertEquals( + ForeignEnumLite.FOREIGN_LITE_FOO, builder.getOptionalForeignEnum()); + assertEquals( + ForeignEnumLite.FOREIGN_LITE_BAR, + messageAfterBuild.getOptionalForeignEnum()); + + message = builder.build(); + ForeignMessageLite foreignMessage = ForeignMessageLite.newBuilder() + .setC(1) + .build(); + builder.setOptionalForeignMessage(foreignMessage); + assertEquals( + ForeignMessageLite.getDefaultInstance(), + message.getOptionalForeignMessage()); + assertEquals(foreignMessage, builder.getOptionalForeignMessage()); + messageAfterBuild = builder.build(); + assertEquals(foreignMessage, messageAfterBuild.getOptionalForeignMessage()); + assertEquals( + ForeignMessageLite.getDefaultInstance(), + message.getOptionalForeignMessage()); + builder.clearOptionalForeignMessage(); + assertEquals( + ForeignMessageLite.getDefaultInstance(), + builder.getOptionalForeignMessage()); + assertEquals(foreignMessage, messageAfterBuild.getOptionalForeignMessage()); + + message = builder.build(); + ForeignMessageLite.Builder foreignMessageBuilder = + ForeignMessageLite.newBuilder() + .setC(3); + builder.setOptionalForeignMessage(foreignMessageBuilder); + assertEquals( + ForeignMessageLite.getDefaultInstance(), + message.getOptionalForeignMessage()); + // LITE_RUNTIME doesn't implement equals so we compare on a property and + // ensure the property isn't set on foreignMessage. + assertEquals(3, builder.getOptionalForeignMessage().getC()); + messageAfterBuild = builder.build(); + assertEquals(3, messageAfterBuild.getOptionalForeignMessage().getC()); + assertEquals( + ForeignMessageLite.getDefaultInstance(), + message.getOptionalForeignMessage()); + builder.clearOptionalForeignMessage(); + assertEquals( + ForeignMessageLite.getDefaultInstance(), + builder.getOptionalForeignMessage()); + assertEquals(3, messageAfterBuild.getOptionalForeignMessage().getC()); + + message = builder.build(); + OptionalGroup optionalGroup = OptionalGroup.newBuilder() + .setA(1) + .build(); + builder.setOptionalGroup(optionalGroup); + assertEquals( + OptionalGroup.getDefaultInstance(), message.getOptionalGroup()); + assertEquals(optionalGroup, builder.getOptionalGroup()); + messageAfterBuild = builder.build(); + assertEquals(optionalGroup, messageAfterBuild.getOptionalGroup()); + assertEquals( + OptionalGroup.getDefaultInstance(), message.getOptionalGroup()); + builder.clearOptionalGroup(); + assertEquals( + OptionalGroup.getDefaultInstance(), builder.getOptionalGroup()); + assertEquals(optionalGroup, messageAfterBuild.getOptionalGroup()); + + message = builder.build(); + OptionalGroup.Builder optionalGroupBuilder = OptionalGroup.newBuilder() + .setA(3); + builder.setOptionalGroup(optionalGroupBuilder); + assertEquals( + OptionalGroup.getDefaultInstance(), message.getOptionalGroup()); + // LITE_RUNTIME doesn't implement equals so we compare on a property and + // ensure the property isn't set on optionalGroup. + assertEquals(3, builder.getOptionalGroup().getA()); + messageAfterBuild = builder.build(); + assertEquals(3, messageAfterBuild.getOptionalGroup().getA()); + assertEquals( + OptionalGroup.getDefaultInstance(), message.getOptionalGroup()); + builder.clearOptionalGroup(); + assertEquals( + OptionalGroup.getDefaultInstance(), builder.getOptionalGroup()); + assertEquals(3, messageAfterBuild.getOptionalGroup().getA()); + + message = builder.build(); + builder.setOptionalInt32(1); + assertEquals(0, message.getOptionalInt32()); + assertEquals(1, builder.getOptionalInt32()); + messageAfterBuild = builder.build(); + assertEquals(1, messageAfterBuild.getOptionalInt32()); + assertEquals(0, message.getOptionalInt32()); + builder.clearOptionalInt32(); + assertEquals(0, builder.getOptionalInt32()); + assertEquals(1, messageAfterBuild.getOptionalInt32()); + + message = builder.build(); + builder.setOptionalInt64(1); + assertEquals(0L, message.getOptionalInt64()); + assertEquals(1L, builder.getOptionalInt64()); + messageAfterBuild = builder.build(); + assertEquals(1L, messageAfterBuild.getOptionalInt64()); + assertEquals(0L, message.getOptionalInt64()); + builder.clearOptionalInt64(); + assertEquals(0L, builder.getOptionalInt64()); + assertEquals(1L, messageAfterBuild.getOptionalInt64()); + + message = builder.build(); + NestedMessage nestedMessage = NestedMessage.newBuilder() + .setBb(1) + .build(); + builder.setOptionalLazyMessage(nestedMessage); + assertEquals( + NestedMessage.getDefaultInstance(), + message.getOptionalLazyMessage()); + assertEquals(nestedMessage, builder.getOptionalLazyMessage()); + messageAfterBuild = builder.build(); + assertEquals(nestedMessage, messageAfterBuild.getOptionalLazyMessage()); + assertEquals( + NestedMessage.getDefaultInstance(), + message.getOptionalLazyMessage()); + builder.clearOptionalLazyMessage(); + assertEquals( + NestedMessage.getDefaultInstance(), builder.getOptionalLazyMessage()); + assertEquals(nestedMessage, messageAfterBuild.getOptionalLazyMessage()); + + message = builder.build(); + NestedMessage.Builder nestedMessageBuilder = + NestedMessage.newBuilder() + .setBb(3); + builder.setOptionalLazyMessage(nestedMessageBuilder); + assertEquals( + NestedMessage.getDefaultInstance(), + message.getOptionalLazyMessage()); + // LITE_RUNTIME doesn't implement equals so we compare on a property. + assertEquals(3, builder.getOptionalLazyMessage().getBb()); + messageAfterBuild = builder.build(); + assertEquals(3, messageAfterBuild.getOptionalLazyMessage().getBb()); + assertEquals( + NestedMessage.getDefaultInstance(), + message.getOptionalLazyMessage()); + builder.clearOptionalLazyMessage(); + assertEquals( + NestedMessage.getDefaultInstance(), builder.getOptionalLazyMessage()); + assertEquals(3, messageAfterBuild.getOptionalLazyMessage().getBb()); + + message = builder.build(); + builder.setOptionalSfixed32(1); + assertEquals(0, message.getOptionalSfixed32()); + assertEquals(1, builder.getOptionalSfixed32()); + messageAfterBuild = builder.build(); + assertEquals(1, messageAfterBuild.getOptionalSfixed32()); + assertEquals(0, message.getOptionalSfixed32()); + builder.clearOptionalSfixed32(); + assertEquals(0, builder.getOptionalSfixed32()); + assertEquals(1, messageAfterBuild.getOptionalSfixed32()); + + message = builder.build(); + builder.setOptionalSfixed64(1); + assertEquals(0L, message.getOptionalSfixed64()); + assertEquals(1L, builder.getOptionalSfixed64()); + messageAfterBuild = builder.build(); + assertEquals(1L, messageAfterBuild.getOptionalSfixed64()); + assertEquals(0L, message.getOptionalSfixed64()); + builder.clearOptionalSfixed64(); + assertEquals(0L, builder.getOptionalSfixed64()); + assertEquals(1L, messageAfterBuild.getOptionalSfixed64()); + + message = builder.build(); + builder.setOptionalSint32(1); + assertEquals(0, message.getOptionalSint32()); + assertEquals(1, builder.getOptionalSint32()); + messageAfterBuild = builder.build(); + assertEquals(1, messageAfterBuild.getOptionalSint32()); + builder.clearOptionalSint32(); + assertEquals(0, builder.getOptionalSint32()); + assertEquals(1, messageAfterBuild.getOptionalSint32()); + + message = builder.build(); + builder.setOptionalSint64(1); + assertEquals(0L, message.getOptionalSint64()); + assertEquals(1L, builder.getOptionalSint64()); + messageAfterBuild = builder.build(); + assertEquals(1L, messageAfterBuild.getOptionalSint64()); + assertEquals(0L, message.getOptionalSint64()); + builder.clearOptionalSint64(); + assertEquals(0L, builder.getOptionalSint64()); + assertEquals(1L, messageAfterBuild.getOptionalSint64()); + + message = builder.build(); + builder.setOptionalString("hi"); + assertEquals("", message.getOptionalString()); + assertEquals("hi", builder.getOptionalString()); + messageAfterBuild = builder.build(); + assertEquals("hi", messageAfterBuild.getOptionalString()); + assertEquals("", message.getOptionalString()); + builder.clearOptionalString(); + assertEquals("", builder.getOptionalString()); + assertEquals("hi", messageAfterBuild.getOptionalString()); + + message = builder.build(); + builder.setOptionalStringBytes(ByteString.copyFromUtf8("no")); + assertEquals(ByteString.EMPTY, message.getOptionalStringBytes()); + assertEquals( + ByteString.copyFromUtf8("no"), builder.getOptionalStringBytes()); + messageAfterBuild = builder.build(); + assertEquals( + ByteString.copyFromUtf8("no"), + messageAfterBuild.getOptionalStringBytes()); + assertEquals(ByteString.EMPTY, message.getOptionalStringBytes()); + builder.clearOptionalString(); + assertEquals(ByteString.EMPTY, builder.getOptionalStringBytes()); + assertEquals( + ByteString.copyFromUtf8("no"), + messageAfterBuild.getOptionalStringBytes()); + + message = builder.build(); + builder.setOptionalStringPiece("hi"); + assertEquals("", message.getOptionalStringPiece()); + assertEquals("hi", builder.getOptionalStringPiece()); + messageAfterBuild = builder.build(); + assertEquals("hi", messageAfterBuild.getOptionalStringPiece()); + assertEquals("", message.getOptionalStringPiece()); + builder.clearOptionalStringPiece(); + assertEquals("", builder.getOptionalStringPiece()); + assertEquals("hi", messageAfterBuild.getOptionalStringPiece()); + + message = builder.build(); + builder.setOptionalStringPieceBytes(ByteString.copyFromUtf8("no")); + assertEquals(ByteString.EMPTY, message.getOptionalStringPieceBytes()); + assertEquals( + ByteString.copyFromUtf8("no"), builder.getOptionalStringPieceBytes()); + messageAfterBuild = builder.build(); + assertEquals( + ByteString.copyFromUtf8("no"), + messageAfterBuild.getOptionalStringPieceBytes()); + assertEquals(ByteString.EMPTY, message.getOptionalStringPieceBytes()); + builder.clearOptionalStringPiece(); + assertEquals(ByteString.EMPTY, builder.getOptionalStringPieceBytes()); + assertEquals( + ByteString.copyFromUtf8("no"), + messageAfterBuild.getOptionalStringPieceBytes()); + + message = builder.build(); + builder.setOptionalUint32(1); + assertEquals(0, message.getOptionalUint32()); + assertEquals(1, builder.getOptionalUint32()); + messageAfterBuild = builder.build(); + assertEquals(1, messageAfterBuild.getOptionalUint32()); + assertEquals(0, message.getOptionalUint32()); + builder.clearOptionalUint32(); + assertEquals(0, builder.getOptionalUint32()); + assertEquals(1, messageAfterBuild.getOptionalUint32()); + + message = builder.build(); + builder.setOptionalUint64(1); + assertEquals(0L, message.getOptionalUint64()); + assertEquals(1L, builder.getOptionalUint64()); + messageAfterBuild = builder.build(); + assertEquals(1L, messageAfterBuild.getOptionalUint64()); + assertEquals(0L, message.getOptionalUint64()); + builder.clearOptionalUint64(); + assertEquals(0L, builder.getOptionalUint64()); + assertEquals(1L, messageAfterBuild.getOptionalUint64()); + + message = builder.build(); + builder.addAllRepeatedBool(singletonList(true)); + assertEquals(emptyList(), message.getRepeatedBoolList()); + assertEquals(singletonList(true), builder.getRepeatedBoolList()); + assertEquals(emptyList(), message.getRepeatedBoolList()); + messageAfterBuild = builder.build(); + builder.clearRepeatedBool(); + assertEquals(emptyList(), builder.getRepeatedBoolList()); + assertEquals(singletonList(true), messageAfterBuild.getRepeatedBoolList()); + + message = builder.build(); + builder.addAllRepeatedBytes(singletonList(ByteString.copyFromUtf8("hi"))); + assertEquals(emptyList(), message.getRepeatedBytesList()); + assertEquals( + singletonList(ByteString.copyFromUtf8("hi")), + builder.getRepeatedBytesList()); + assertEquals(emptyList(), message.getRepeatedBytesList()); + messageAfterBuild = builder.build(); + builder.clearRepeatedBytes(); + assertEquals(emptyList(), builder.getRepeatedBytesList()); + assertEquals( + singletonList(ByteString.copyFromUtf8("hi")), + messageAfterBuild.getRepeatedBytesList()); + + message = builder.build(); + builder.addAllRepeatedCord(singletonList("hi")); + assertEquals(emptyList(), message.getRepeatedCordList()); + assertEquals(singletonList("hi"), builder.getRepeatedCordList()); + assertEquals(emptyList(), message.getRepeatedCordList()); + messageAfterBuild = builder.build(); + builder.clearRepeatedCord(); + assertEquals(emptyList(), builder.getRepeatedCordList()); + assertEquals(singletonList("hi"), messageAfterBuild.getRepeatedCordList()); + + message = builder.build(); + builder.addAllRepeatedDouble(singletonList(1D)); + assertEquals(emptyList(), message.getRepeatedDoubleList()); + assertEquals(singletonList(1D), builder.getRepeatedDoubleList()); + assertEquals(emptyList(), message.getRepeatedDoubleList()); + messageAfterBuild = builder.build(); + builder.clearRepeatedDouble(); + assertEquals(emptyList(), builder.getRepeatedDoubleList()); + assertEquals(singletonList(1D), messageAfterBuild.getRepeatedDoubleList()); + + message = builder.build(); + builder.addAllRepeatedFixed32(singletonList(1)); + assertEquals(emptyList(), message.getRepeatedFixed32List()); + assertEquals(singletonList(1), builder.getRepeatedFixed32List()); + assertEquals(emptyList(), message.getRepeatedFixed32List()); + messageAfterBuild = builder.build(); + builder.clearRepeatedFixed32(); + assertEquals(emptyList(), builder.getRepeatedFixed32List()); + assertEquals(singletonList(1), messageAfterBuild.getRepeatedFixed32List()); + + message = builder.build(); + builder.addAllRepeatedFixed64(singletonList(1L)); + assertEquals(emptyList(), message.getRepeatedFixed64List()); + assertEquals(singletonList(1L), builder.getRepeatedFixed64List()); + assertEquals(emptyList(), message.getRepeatedFixed64List()); + messageAfterBuild = builder.build(); + builder.clearRepeatedFixed64(); + assertEquals(emptyList(), builder.getRepeatedFixed64List()); + assertEquals(singletonList(1L), messageAfterBuild.getRepeatedFixed64List()); + + message = builder.build(); + builder.addAllRepeatedFloat(singletonList(1F)); + assertEquals(emptyList(), message.getRepeatedFloatList()); + assertEquals(singletonList(1F), builder.getRepeatedFloatList()); + assertEquals(emptyList(), message.getRepeatedFloatList()); + messageAfterBuild = builder.build(); + builder.clearRepeatedFloat(); + assertEquals(emptyList(), builder.getRepeatedFloatList()); + assertEquals(singletonList(1F), messageAfterBuild.getRepeatedFloatList()); + + message = builder.build(); + builder.addAllRepeatedForeignEnum( + singletonList(ForeignEnumLite.FOREIGN_LITE_BAR)); + assertEquals(emptyList(), message.getRepeatedForeignEnumList()); + assertEquals( + singletonList(ForeignEnumLite.FOREIGN_LITE_BAR), + builder.getRepeatedForeignEnumList()); + assertEquals(emptyList(), message.getRepeatedForeignEnumList()); + messageAfterBuild = builder.build(); + builder.clearRepeatedForeignEnum(); + assertEquals(emptyList(), builder.getRepeatedForeignEnumList()); + assertEquals( + singletonList(ForeignEnumLite.FOREIGN_LITE_BAR), + messageAfterBuild.getRepeatedForeignEnumList()); + + message = builder.build(); + builder.addAllRepeatedForeignMessage(singletonList(foreignMessage)); + assertEquals(emptyList(), message.getRepeatedForeignMessageList()); + assertEquals( + singletonList(foreignMessage), builder.getRepeatedForeignMessageList()); + assertEquals(emptyList(), message.getRepeatedForeignMessageList()); + messageAfterBuild = builder.build(); + builder.clearRepeatedForeignMessage(); + assertEquals(emptyList(), builder.getRepeatedForeignMessageList()); + assertEquals( + singletonList(foreignMessage), + messageAfterBuild.getRepeatedForeignMessageList()); + + message = builder.build(); + builder.addAllRepeatedGroup( + singletonList(RepeatedGroup.getDefaultInstance())); + assertEquals(emptyList(), message.getRepeatedGroupList()); + assertEquals( + singletonList(RepeatedGroup.getDefaultInstance()), + builder.getRepeatedGroupList()); + assertEquals(emptyList(), message.getRepeatedGroupList()); + messageAfterBuild = builder.build(); + builder.clearRepeatedGroup(); + assertEquals(emptyList(), builder.getRepeatedGroupList()); + assertEquals( + singletonList(RepeatedGroup.getDefaultInstance()), + messageAfterBuild.getRepeatedGroupList()); + + message = builder.build(); + builder.addAllRepeatedInt32(singletonList(1)); + assertEquals(emptyList(), message.getRepeatedInt32List()); + assertEquals(singletonList(1), builder.getRepeatedInt32List()); + assertEquals(emptyList(), message.getRepeatedInt32List()); + messageAfterBuild = builder.build(); + builder.clearRepeatedInt32(); + assertEquals(emptyList(), builder.getRepeatedInt32List()); + assertEquals(singletonList(1), messageAfterBuild.getRepeatedInt32List()); + + message = builder.build(); + builder.addAllRepeatedInt64(singletonList(1L)); + assertEquals(emptyList(), message.getRepeatedInt64List()); + assertEquals(singletonList(1L), builder.getRepeatedInt64List()); + assertEquals(emptyList(), message.getRepeatedInt64List()); + messageAfterBuild = builder.build(); + builder.clearRepeatedInt64(); + assertEquals(emptyList(), builder.getRepeatedInt64List()); + assertEquals(singletonList(1L), messageAfterBuild.getRepeatedInt64List()); + + message = builder.build(); + builder.addAllRepeatedLazyMessage(singletonList(nestedMessage)); + assertEquals(emptyList(), message.getRepeatedLazyMessageList()); + assertEquals( + singletonList(nestedMessage), builder.getRepeatedLazyMessageList()); + assertEquals(emptyList(), message.getRepeatedLazyMessageList()); + messageAfterBuild = builder.build(); + builder.clearRepeatedLazyMessage(); + assertEquals(emptyList(), builder.getRepeatedLazyMessageList()); + assertEquals( + singletonList(nestedMessage), + messageAfterBuild.getRepeatedLazyMessageList()); + + message = builder.build(); + builder.addAllRepeatedSfixed32(singletonList(1)); + assertEquals(emptyList(), message.getRepeatedSfixed32List()); + assertEquals(singletonList(1), builder.getRepeatedSfixed32List()); + assertEquals(emptyList(), message.getRepeatedSfixed32List()); + messageAfterBuild = builder.build(); + builder.clearRepeatedSfixed32(); + assertEquals(emptyList(), builder.getRepeatedSfixed32List()); + assertEquals(singletonList(1), messageAfterBuild.getRepeatedSfixed32List()); + + message = builder.build(); + builder.addAllRepeatedSfixed64(singletonList(1L)); + assertEquals(emptyList(), message.getRepeatedSfixed64List()); + assertEquals(singletonList(1L), builder.getRepeatedSfixed64List()); + assertEquals(emptyList(), message.getRepeatedSfixed64List()); + messageAfterBuild = builder.build(); + builder.clearRepeatedSfixed64(); + assertEquals(emptyList(), builder.getRepeatedSfixed64List()); + assertEquals( + singletonList(1L), messageAfterBuild.getRepeatedSfixed64List()); + + message = builder.build(); + builder.addAllRepeatedSint32(singletonList(1)); + assertEquals(emptyList(), message.getRepeatedSint32List()); + assertEquals(singletonList(1), builder.getRepeatedSint32List()); + assertEquals(emptyList(), message.getRepeatedSint32List()); + messageAfterBuild = builder.build(); + builder.clearRepeatedSint32(); + assertEquals(emptyList(), builder.getRepeatedSint32List()); + assertEquals(singletonList(1), messageAfterBuild.getRepeatedSint32List()); + + message = builder.build(); + builder.addAllRepeatedSint64(singletonList(1L)); + assertEquals(emptyList(), message.getRepeatedSint64List()); + assertEquals(singletonList(1L), builder.getRepeatedSint64List()); + assertEquals(emptyList(), message.getRepeatedSint64List()); + messageAfterBuild = builder.build(); + builder.clearRepeatedSint64(); + assertEquals(emptyList(), builder.getRepeatedSint64List()); + assertEquals(singletonList(1L), messageAfterBuild.getRepeatedSint64List()); + + message = builder.build(); + builder.addAllRepeatedString(singletonList("hi")); + assertEquals(emptyList(), message.getRepeatedStringList()); + assertEquals(singletonList("hi"), builder.getRepeatedStringList()); + assertEquals(emptyList(), message.getRepeatedStringList()); + messageAfterBuild = builder.build(); + builder.clearRepeatedString(); + assertEquals(emptyList(), builder.getRepeatedStringList()); + assertEquals( + singletonList("hi"), messageAfterBuild.getRepeatedStringList()); + + message = builder.build(); + builder.addAllRepeatedStringPiece(singletonList("hi")); + assertEquals(emptyList(), message.getRepeatedStringPieceList()); + assertEquals(singletonList("hi"), builder.getRepeatedStringPieceList()); + assertEquals(emptyList(), message.getRepeatedStringPieceList()); + messageAfterBuild = builder.build(); + builder.clearRepeatedStringPiece(); + assertEquals(emptyList(), builder.getRepeatedStringPieceList()); + assertEquals( + singletonList("hi"), messageAfterBuild.getRepeatedStringPieceList()); + + message = builder.build(); + builder.addAllRepeatedUint32(singletonList(1)); + assertEquals(emptyList(), message.getRepeatedUint32List()); + assertEquals(singletonList(1), builder.getRepeatedUint32List()); + assertEquals(emptyList(), message.getRepeatedUint32List()); + messageAfterBuild = builder.build(); + builder.clearRepeatedUint32(); + assertEquals(emptyList(), builder.getRepeatedUint32List()); + assertEquals(singletonList(1), messageAfterBuild.getRepeatedUint32List()); + + message = builder.build(); + builder.addAllRepeatedUint64(singletonList(1L)); + assertEquals(emptyList(), message.getRepeatedUint64List()); + assertEquals(singletonList(1L), builder.getRepeatedUint64List()); + assertEquals(emptyList(), message.getRepeatedUint64List()); + messageAfterBuild = builder.build(); + builder.clearRepeatedUint64(); + assertEquals(emptyList(), builder.getRepeatedUint64List()); + assertEquals(singletonList(1L), messageAfterBuild.getRepeatedUint64List()); + + message = builder.build(); + builder.addRepeatedBool(true); + assertEquals(emptyList(), message.getRepeatedBoolList()); + assertEquals(singletonList(true), builder.getRepeatedBoolList()); + assertEquals(emptyList(), message.getRepeatedBoolList()); + messageAfterBuild = builder.build(); + builder.clearRepeatedBool(); + assertEquals(emptyList(), builder.getRepeatedBoolList()); + assertEquals(singletonList(true), messageAfterBuild.getRepeatedBoolList()); + + message = builder.build(); + builder.addRepeatedBytes(ByteString.copyFromUtf8("hi")); + assertEquals(emptyList(), message.getRepeatedBytesList()); + assertEquals( + singletonList(ByteString.copyFromUtf8("hi")), + builder.getRepeatedBytesList()); + assertEquals(emptyList(), message.getRepeatedBytesList()); + messageAfterBuild = builder.build(); + builder.clearRepeatedBytes(); + assertEquals(emptyList(), builder.getRepeatedBytesList()); + assertEquals( + singletonList(ByteString.copyFromUtf8("hi")), + messageAfterBuild.getRepeatedBytesList()); + + message = builder.build(); + builder.addRepeatedCord("hi"); + assertEquals(emptyList(), message.getRepeatedCordList()); + assertEquals(singletonList("hi"), builder.getRepeatedCordList()); + assertEquals(emptyList(), message.getRepeatedCordList()); + messageAfterBuild = builder.build(); + builder.clearRepeatedCord(); + assertEquals(emptyList(), builder.getRepeatedCordList()); + assertEquals(singletonList("hi"), messageAfterBuild.getRepeatedCordList()); + + message = builder.build(); + builder.addRepeatedDouble(1D); + assertEquals(emptyList(), message.getRepeatedDoubleList()); + assertEquals(singletonList(1D), builder.getRepeatedDoubleList()); + assertEquals(emptyList(), message.getRepeatedDoubleList()); + messageAfterBuild = builder.build(); + builder.clearRepeatedDouble(); + assertEquals(emptyList(), builder.getRepeatedDoubleList()); + assertEquals(singletonList(1D), messageAfterBuild.getRepeatedDoubleList()); + + message = builder.build(); + builder.addRepeatedFixed32(1); + assertEquals(emptyList(), message.getRepeatedFixed32List()); + assertEquals(singletonList(1), builder.getRepeatedFixed32List()); + assertEquals(emptyList(), message.getRepeatedFixed32List()); + messageAfterBuild = builder.build(); + builder.clearRepeatedFixed32(); + assertEquals(emptyList(), builder.getRepeatedFixed32List()); + assertEquals(singletonList(1), messageAfterBuild.getRepeatedFixed32List()); + + message = builder.build(); + builder.addRepeatedFixed64(1L); + assertEquals(emptyList(), message.getRepeatedFixed64List()); + assertEquals(singletonList(1L), builder.getRepeatedFixed64List()); + assertEquals(emptyList(), message.getRepeatedFixed64List()); + messageAfterBuild = builder.build(); + builder.clearRepeatedFixed64(); + assertEquals(emptyList(), builder.getRepeatedFixed64List()); + assertEquals(singletonList(1L), messageAfterBuild.getRepeatedFixed64List()); + + message = builder.build(); + builder.addRepeatedFloat(1F); + assertEquals(emptyList(), message.getRepeatedFloatList()); + assertEquals(singletonList(1F), builder.getRepeatedFloatList()); + assertEquals(emptyList(), message.getRepeatedFloatList()); + messageAfterBuild = builder.build(); + builder.clearRepeatedFloat(); + assertEquals(emptyList(), builder.getRepeatedFloatList()); + assertEquals(singletonList(1F), messageAfterBuild.getRepeatedFloatList()); + + message = builder.build(); + builder.addRepeatedForeignEnum(ForeignEnumLite.FOREIGN_LITE_BAR); + assertEquals(emptyList(), message.getRepeatedForeignEnumList()); + assertEquals( + singletonList(ForeignEnumLite.FOREIGN_LITE_BAR), + builder.getRepeatedForeignEnumList()); + assertEquals(emptyList(), message.getRepeatedForeignEnumList()); + messageAfterBuild = builder.build(); + builder.clearRepeatedForeignEnum(); + assertEquals(emptyList(), builder.getRepeatedForeignEnumList()); + assertEquals( + singletonList(ForeignEnumLite.FOREIGN_LITE_BAR), + messageAfterBuild.getRepeatedForeignEnumList()); + + message = builder.build(); + builder.addRepeatedForeignMessage(foreignMessage); + assertEquals(emptyList(), message.getRepeatedForeignMessageList()); + assertEquals( + singletonList(foreignMessage), builder.getRepeatedForeignMessageList()); + assertEquals(emptyList(), message.getRepeatedForeignMessageList()); + messageAfterBuild = builder.build(); + builder.removeRepeatedForeignMessage(0); + assertEquals(emptyList(), builder.getRepeatedForeignMessageList()); + assertEquals( + singletonList(foreignMessage), + messageAfterBuild.getRepeatedForeignMessageList()); + + message = builder.build(); + builder.addRepeatedGroup(RepeatedGroup.getDefaultInstance()); + assertEquals(emptyList(), message.getRepeatedGroupList()); + assertEquals( + singletonList(RepeatedGroup.getDefaultInstance()), + builder.getRepeatedGroupList()); + assertEquals(emptyList(), message.getRepeatedGroupList()); + messageAfterBuild = builder.build(); + builder.removeRepeatedGroup(0); + assertEquals(emptyList(), builder.getRepeatedGroupList()); + assertEquals( + singletonList(RepeatedGroup.getDefaultInstance()), + messageAfterBuild.getRepeatedGroupList()); + + message = builder.build(); + builder.addRepeatedInt32(1); + assertEquals(emptyList(), message.getRepeatedInt32List()); + assertEquals(singletonList(1), builder.getRepeatedInt32List()); + assertEquals(emptyList(), message.getRepeatedInt32List()); + messageAfterBuild = builder.build(); + builder.clearRepeatedInt32(); + assertEquals(emptyList(), builder.getRepeatedInt32List()); + assertEquals(singletonList(1), messageAfterBuild.getRepeatedInt32List()); + + message = builder.build(); + builder.addRepeatedInt64(1L); + assertEquals(emptyList(), message.getRepeatedInt64List()); + assertEquals(singletonList(1L), builder.getRepeatedInt64List()); + assertEquals(emptyList(), message.getRepeatedInt64List()); + messageAfterBuild = builder.build(); + builder.clearRepeatedInt64(); + assertEquals(emptyList(), builder.getRepeatedInt64List()); + assertEquals(singletonList(1L), messageAfterBuild.getRepeatedInt64List()); + + message = builder.build(); + builder.addRepeatedLazyMessage(nestedMessage); + assertEquals(emptyList(), message.getRepeatedLazyMessageList()); + assertEquals( + singletonList(nestedMessage), builder.getRepeatedLazyMessageList()); + assertEquals(emptyList(), message.getRepeatedLazyMessageList()); + messageAfterBuild = builder.build(); + builder.removeRepeatedLazyMessage(0); + assertEquals(emptyList(), builder.getRepeatedLazyMessageList()); + assertEquals( + singletonList(nestedMessage), + messageAfterBuild.getRepeatedLazyMessageList()); + + message = builder.build(); + builder.addRepeatedSfixed32(1); + assertEquals(emptyList(), message.getRepeatedSfixed32List()); + assertEquals(singletonList(1), builder.getRepeatedSfixed32List()); + assertEquals(emptyList(), message.getRepeatedSfixed32List()); + messageAfterBuild = builder.build(); + builder.clearRepeatedSfixed32(); + assertEquals(emptyList(), builder.getRepeatedSfixed32List()); + assertEquals(singletonList(1), messageAfterBuild.getRepeatedSfixed32List()); + + message = builder.build(); + builder.addRepeatedSfixed64(1L); + assertEquals(emptyList(), message.getRepeatedSfixed64List()); + assertEquals(singletonList(1L), builder.getRepeatedSfixed64List()); + assertEquals(emptyList(), message.getRepeatedSfixed64List()); + messageAfterBuild = builder.build(); + builder.clearRepeatedSfixed64(); + assertEquals(emptyList(), builder.getRepeatedSfixed64List()); + assertEquals( + singletonList(1L), messageAfterBuild.getRepeatedSfixed64List()); + + message = builder.build(); + builder.addRepeatedSint32(1); + assertEquals(emptyList(), message.getRepeatedSint32List()); + assertEquals(singletonList(1), builder.getRepeatedSint32List()); + assertEquals(emptyList(), message.getRepeatedSint32List()); + messageAfterBuild = builder.build(); + builder.clearRepeatedSint32(); + assertEquals(emptyList(), builder.getRepeatedSint32List()); + assertEquals(singletonList(1), messageAfterBuild.getRepeatedSint32List()); + + message = builder.build(); + builder.addRepeatedSint64(1L); + assertEquals(emptyList(), message.getRepeatedSint64List()); + assertEquals(singletonList(1L), builder.getRepeatedSint64List()); + assertEquals(emptyList(), message.getRepeatedSint64List()); + messageAfterBuild = builder.build(); + builder.clearRepeatedSint64(); + assertEquals(emptyList(), builder.getRepeatedSint64List()); + assertEquals(singletonList(1L), messageAfterBuild.getRepeatedSint64List()); + + message = builder.build(); + builder.addRepeatedString("hi"); + assertEquals(emptyList(), message.getRepeatedStringList()); + assertEquals(singletonList("hi"), builder.getRepeatedStringList()); + assertEquals(emptyList(), message.getRepeatedStringList()); + messageAfterBuild = builder.build(); + builder.clearRepeatedString(); + assertEquals(emptyList(), builder.getRepeatedStringList()); + assertEquals( + singletonList("hi"), messageAfterBuild.getRepeatedStringList()); + + message = builder.build(); + builder.addRepeatedStringPiece("hi"); + assertEquals(emptyList(), message.getRepeatedStringPieceList()); + assertEquals(singletonList("hi"), builder.getRepeatedStringPieceList()); + assertEquals(emptyList(), message.getRepeatedStringPieceList()); + messageAfterBuild = builder.build(); + builder.clearRepeatedStringPiece(); + assertEquals(emptyList(), builder.getRepeatedStringPieceList()); + assertEquals( + singletonList("hi"), messageAfterBuild.getRepeatedStringPieceList()); + + message = builder.build(); + builder.addRepeatedUint32(1); + assertEquals(emptyList(), message.getRepeatedUint32List()); + assertEquals(singletonList(1), builder.getRepeatedUint32List()); + assertEquals(emptyList(), message.getRepeatedUint32List()); + messageAfterBuild = builder.build(); + builder.clearRepeatedUint32(); + assertEquals(emptyList(), builder.getRepeatedUint32List()); + assertEquals(singletonList(1), messageAfterBuild.getRepeatedUint32List()); + + message = builder.build(); + builder.addRepeatedUint64(1L); + assertEquals(emptyList(), message.getRepeatedUint64List()); + assertEquals(singletonList(1L), builder.getRepeatedUint64List()); + assertEquals(emptyList(), message.getRepeatedUint64List()); + messageAfterBuild = builder.build(); + builder.clearRepeatedUint64(); + assertEquals(emptyList(), builder.getRepeatedUint64List()); + assertEquals(singletonList(1L), messageAfterBuild.getRepeatedUint64List()); + + message = builder.build(); + builder.addRepeatedBool(true); + messageAfterBuild = builder.build(); + assertEquals(0, message.getRepeatedBoolCount()); + builder.setRepeatedBool(0, false); + assertEquals(true, messageAfterBuild.getRepeatedBool(0)); + assertEquals(false, builder.getRepeatedBool(0)); + builder.clearRepeatedBool(); + + message = builder.build(); + builder.addRepeatedBytes(ByteString.copyFromUtf8("hi")); + messageAfterBuild = builder.build(); + assertEquals(0, message.getRepeatedBytesCount()); + builder.setRepeatedBytes(0, ByteString.EMPTY); + assertEquals( + ByteString.copyFromUtf8("hi"), messageAfterBuild.getRepeatedBytes(0)); + assertEquals(ByteString.EMPTY, builder.getRepeatedBytes(0)); + builder.clearRepeatedBytes(); + + message = builder.build(); + builder.addRepeatedCord("hi"); + messageAfterBuild = builder.build(); + assertEquals(0, message.getRepeatedCordCount()); + builder.setRepeatedCord(0, ""); + assertEquals("hi", messageAfterBuild.getRepeatedCord(0)); + assertEquals("", builder.getRepeatedCord(0)); + builder.clearRepeatedCord(); + message = builder.build(); + + builder.addRepeatedCordBytes(ByteString.copyFromUtf8("hi")); + messageAfterBuild = builder.build(); + assertEquals(0, message.getRepeatedCordCount()); + builder.setRepeatedCord(0, ""); + assertEquals( + ByteString.copyFromUtf8("hi"), messageAfterBuild.getRepeatedCordBytes(0)); + assertEquals(ByteString.EMPTY, builder.getRepeatedCordBytes(0)); + builder.clearRepeatedCord(); + + message = builder.build(); + builder.addRepeatedDouble(1D); + messageAfterBuild = builder.build(); + assertEquals(0, message.getRepeatedDoubleCount()); + builder.setRepeatedDouble(0, 0D); + assertEquals(1D, messageAfterBuild.getRepeatedDouble(0)); + assertEquals(0D, builder.getRepeatedDouble(0)); + builder.clearRepeatedDouble(); + + message = builder.build(); + builder.addRepeatedFixed32(1); + messageAfterBuild = builder.build(); + assertEquals(0, message.getRepeatedFixed32Count()); + builder.setRepeatedFixed32(0, 0); + assertEquals(1, messageAfterBuild.getRepeatedFixed32(0)); + assertEquals(0, builder.getRepeatedFixed32(0)); + builder.clearRepeatedFixed32(); + + message = builder.build(); + builder.addRepeatedFixed64(1L); + messageAfterBuild = builder.build(); + assertEquals(0, message.getRepeatedFixed64Count()); + builder.setRepeatedFixed64(0, 0L); + assertEquals(1L, messageAfterBuild.getRepeatedFixed64(0)); + assertEquals(0L, builder.getRepeatedFixed64(0)); + builder.clearRepeatedFixed64(); + + message = builder.build(); + builder.addRepeatedFloat(1F); + messageAfterBuild = builder.build(); + assertEquals(0, message.getRepeatedFloatCount()); + builder.setRepeatedFloat(0, 0F); + assertEquals(1F, messageAfterBuild.getRepeatedFloat(0)); + assertEquals(0F, builder.getRepeatedFloat(0)); + builder.clearRepeatedFloat(); + + message = builder.build(); + builder.addRepeatedForeignEnum(ForeignEnumLite.FOREIGN_LITE_BAR); + messageAfterBuild = builder.build(); + assertEquals(0, message.getRepeatedForeignEnumCount()); + builder.setRepeatedForeignEnum(0, ForeignEnumLite.FOREIGN_LITE_FOO); + assertEquals( + ForeignEnumLite.FOREIGN_LITE_BAR, + messageAfterBuild.getRepeatedForeignEnum(0)); + assertEquals( + ForeignEnumLite.FOREIGN_LITE_FOO, builder.getRepeatedForeignEnum(0)); + builder.clearRepeatedForeignEnum(); + + message = builder.build(); + builder.addRepeatedForeignMessage(foreignMessage); + messageAfterBuild = builder.build(); + assertEquals(0, message.getRepeatedForeignMessageCount()); + builder.setRepeatedForeignMessage( + 0, ForeignMessageLite.getDefaultInstance()); + assertEquals( + foreignMessage, messageAfterBuild.getRepeatedForeignMessage(0)); + assertEquals( + ForeignMessageLite.getDefaultInstance(), + builder.getRepeatedForeignMessage(0)); + builder.clearRepeatedForeignMessage(); + + message = builder.build(); + builder.addRepeatedForeignMessage(foreignMessageBuilder); + messageAfterBuild = builder.build(); + assertEquals(0, message.getRepeatedForeignMessageCount()); + builder.setRepeatedForeignMessage( + 0, ForeignMessageLite.getDefaultInstance()); + // LITE_RUNTIME doesn't implement equals so we compare on a property. + assertEquals(3, messageAfterBuild.getRepeatedForeignMessage(0).getC()); + assertEquals( + ForeignMessageLite.getDefaultInstance(), + builder.getRepeatedForeignMessage(0)); + builder.clearRepeatedForeignMessage(); + + message = builder.build(); + builder.addRepeatedForeignMessage(0, foreignMessage); + messageAfterBuild = builder.build(); + assertEquals(0, message.getRepeatedForeignMessageCount()); + builder.setRepeatedForeignMessage(0, foreignMessageBuilder); + assertEquals( + foreignMessage, messageAfterBuild.getRepeatedForeignMessage(0)); + // LITE_RUNTIME doesn't implement equals so we compare on a property. + assertEquals(3, builder.getRepeatedForeignMessage(0).getC()); + builder.clearRepeatedForeignMessage(); + + message = builder.build(); + RepeatedGroup repeatedGroup = RepeatedGroup.newBuilder() + .setA(1) + .build(); + builder.addRepeatedGroup(repeatedGroup); + messageAfterBuild = builder.build(); + assertEquals(0, message.getRepeatedGroupCount()); + builder.setRepeatedGroup(0, RepeatedGroup.getDefaultInstance()); + assertEquals(repeatedGroup, messageAfterBuild.getRepeatedGroup(0)); + assertEquals( + RepeatedGroup.getDefaultInstance(), builder.getRepeatedGroup(0)); + builder.clearRepeatedGroup(); + + message = builder.build(); + builder.addRepeatedGroup(0, repeatedGroup); + messageAfterBuild = builder.build(); + assertEquals(0, message.getRepeatedGroupCount()); + builder.setRepeatedGroup(0, RepeatedGroup.getDefaultInstance()); + assertEquals(repeatedGroup, messageAfterBuild.getRepeatedGroup(0)); + assertEquals( + RepeatedGroup.getDefaultInstance(), builder.getRepeatedGroup(0)); + builder.clearRepeatedGroup(); + + message = builder.build(); + RepeatedGroup.Builder repeatedGroupBuilder = RepeatedGroup.newBuilder() + .setA(3); + builder.addRepeatedGroup(repeatedGroupBuilder); + messageAfterBuild = builder.build(); + assertEquals(0, message.getRepeatedGroupCount()); + builder.setRepeatedGroup(0, RepeatedGroup.getDefaultInstance()); + // LITE_RUNTIME doesn't implement equals so we compare on a property and + // ensure the property isn't set on repeatedGroup. + assertEquals(3, messageAfterBuild.getRepeatedGroup(0).getA()); + assertEquals( + RepeatedGroup.getDefaultInstance(), builder.getRepeatedGroup(0)); + builder.clearRepeatedGroup(); + + message = builder.build(); + builder.addRepeatedGroup(0, repeatedGroupBuilder); + messageAfterBuild = builder.build(); + assertEquals(0, message.getRepeatedGroupCount()); + builder.setRepeatedGroup(0, RepeatedGroup.getDefaultInstance()); + // LITE_RUNTIME doesn't implement equals so we compare on a property and + // ensure the property isn't set on repeatedGroup. + assertEquals(3, messageAfterBuild.getRepeatedGroup(0).getA()); + assertEquals( + RepeatedGroup.getDefaultInstance(), builder.getRepeatedGroup(0)); + builder.clearRepeatedGroup(); + + message = builder.build(); + builder.addRepeatedInt32(1); + messageAfterBuild = builder.build(); + assertEquals(0, message.getRepeatedInt32Count()); + builder.setRepeatedInt32(0, 0); + assertEquals(1, messageAfterBuild.getRepeatedInt32(0)); + assertEquals(0, builder.getRepeatedInt32(0)); + builder.clearRepeatedInt32(); + + message = builder.build(); + builder.addRepeatedInt64(1L); + messageAfterBuild = builder.build(); + assertEquals(0L, message.getRepeatedInt64Count()); + builder.setRepeatedInt64(0, 0L); + assertEquals(1L, messageAfterBuild.getRepeatedInt64(0)); + assertEquals(0L, builder.getRepeatedInt64(0)); + builder.clearRepeatedInt64(); + + message = builder.build(); + builder.addRepeatedLazyMessage(nestedMessage); + messageAfterBuild = builder.build(); + assertEquals(0, message.getRepeatedLazyMessageCount()); + builder.setRepeatedLazyMessage(0, NestedMessage.getDefaultInstance()); + assertEquals(nestedMessage, messageAfterBuild.getRepeatedLazyMessage(0)); + assertEquals( + NestedMessage.getDefaultInstance(), builder.getRepeatedLazyMessage(0)); + builder.clearRepeatedLazyMessage(); + + message = builder.build(); + builder.addRepeatedLazyMessage(0, nestedMessage); + messageAfterBuild = builder.build(); + assertEquals(0, message.getRepeatedLazyMessageCount()); + builder.setRepeatedLazyMessage(0, NestedMessage.getDefaultInstance()); + assertEquals(nestedMessage, messageAfterBuild.getRepeatedLazyMessage(0)); + assertEquals( + NestedMessage.getDefaultInstance(), builder.getRepeatedLazyMessage(0)); + builder.clearRepeatedLazyMessage(); + + message = builder.build(); + builder.addRepeatedLazyMessage(nestedMessageBuilder); + messageAfterBuild = builder.build(); + assertEquals(0, message.getRepeatedLazyMessageCount()); + builder.setRepeatedLazyMessage(0, NestedMessage.getDefaultInstance()); + // LITE_RUNTIME doesn't implement equals so we compare on a property and + // ensure the property isn't set on repeatedGroup. + assertEquals(3, messageAfterBuild.getRepeatedLazyMessage(0).getBb()); + assertEquals( + NestedMessage.getDefaultInstance(), builder.getRepeatedLazyMessage(0)); + builder.clearRepeatedLazyMessage(); + + message = builder.build(); + builder.addRepeatedLazyMessage(0, nestedMessageBuilder); + messageAfterBuild = builder.build(); + assertEquals(0, message.getRepeatedLazyMessageCount()); + builder.setRepeatedLazyMessage(0, NestedMessage.getDefaultInstance()); + // LITE_RUNTIME doesn't implement equals so we compare on a property and + // ensure the property isn't set on repeatedGroup. + assertEquals(3, messageAfterBuild.getRepeatedLazyMessage(0).getBb()); + assertEquals( + NestedMessage.getDefaultInstance(), builder.getRepeatedLazyMessage(0)); + builder.clearRepeatedLazyMessage(); + + message = builder.build(); + builder.addRepeatedSfixed32(1); + messageAfterBuild = builder.build(); + assertEquals(0, message.getRepeatedSfixed32Count()); + builder.setRepeatedSfixed32(0, 0); + assertEquals(1, messageAfterBuild.getRepeatedSfixed32(0)); + assertEquals(0, builder.getRepeatedSfixed32(0)); + builder.clearRepeatedSfixed32(); + + message = builder.build(); + builder.addRepeatedSfixed64(1L); + messageAfterBuild = builder.build(); + assertEquals(0L, message.getRepeatedSfixed64Count()); + builder.setRepeatedSfixed64(0, 0L); + assertEquals(1L, messageAfterBuild.getRepeatedSfixed64(0)); + assertEquals(0L, builder.getRepeatedSfixed64(0)); + builder.clearRepeatedSfixed64(); + + message = builder.build(); + builder.addRepeatedSint32(1); + messageAfterBuild = builder.build(); + assertEquals(0, message.getRepeatedSint32Count()); + builder.setRepeatedSint32(0, 0); + assertEquals(1, messageAfterBuild.getRepeatedSint32(0)); + assertEquals(0, builder.getRepeatedSint32(0)); + builder.clearRepeatedSint32(); + + message = builder.build(); + builder.addRepeatedSint64(1L); + messageAfterBuild = builder.build(); + assertEquals(0L, message.getRepeatedSint64Count()); + builder.setRepeatedSint64(0, 0L); + assertEquals(1L, messageAfterBuild.getRepeatedSint64(0)); + assertEquals(0L, builder.getRepeatedSint64(0)); + builder.clearRepeatedSint64(); + + message = builder.build(); + builder.addRepeatedString("hi"); + messageAfterBuild = builder.build(); + assertEquals(0L, message.getRepeatedStringCount()); + builder.setRepeatedString(0, ""); + assertEquals("hi", messageAfterBuild.getRepeatedString(0)); + assertEquals("", builder.getRepeatedString(0)); + builder.clearRepeatedString(); + + message = builder.build(); + builder.addRepeatedStringBytes(ByteString.copyFromUtf8("hi")); + messageAfterBuild = builder.build(); + assertEquals(0L, message.getRepeatedStringCount()); + builder.setRepeatedString(0, ""); + assertEquals( + ByteString.copyFromUtf8("hi"), + messageAfterBuild.getRepeatedStringBytes(0)); + assertEquals(ByteString.EMPTY, builder.getRepeatedStringBytes(0)); + builder.clearRepeatedString(); + + message = builder.build(); + builder.addRepeatedStringPiece("hi"); + messageAfterBuild = builder.build(); + assertEquals(0L, message.getRepeatedStringPieceCount()); + builder.setRepeatedStringPiece(0, ""); + assertEquals("hi", messageAfterBuild.getRepeatedStringPiece(0)); + assertEquals("", builder.getRepeatedStringPiece(0)); + builder.clearRepeatedStringPiece(); + + message = builder.build(); + builder.addRepeatedStringPieceBytes(ByteString.copyFromUtf8("hi")); + messageAfterBuild = builder.build(); + assertEquals(0L, message.getRepeatedStringPieceCount()); + builder.setRepeatedStringPiece(0, ""); + assertEquals( + ByteString.copyFromUtf8("hi"), + messageAfterBuild.getRepeatedStringPieceBytes(0)); + assertEquals(ByteString.EMPTY, builder.getRepeatedStringPieceBytes(0)); + builder.clearRepeatedStringPiece(); + + message = builder.build(); + builder.addRepeatedUint32(1); + messageAfterBuild = builder.build(); + assertEquals(0, message.getRepeatedUint32Count()); + builder.setRepeatedUint32(0, 0); + assertEquals(1, messageAfterBuild.getRepeatedUint32(0)); + assertEquals(0, builder.getRepeatedUint32(0)); + builder.clearRepeatedUint32(); + + message = builder.build(); + builder.addRepeatedUint64(1L); + messageAfterBuild = builder.build(); + assertEquals(0L, message.getRepeatedUint64Count()); + builder.setRepeatedUint64(0, 0L); + assertEquals(1L, messageAfterBuild.getRepeatedUint64(0)); + assertEquals(0L, builder.getRepeatedUint64(0)); + builder.clearRepeatedUint64(); + + message = builder.build(); + assertEquals(0, message.getSerializedSize()); + builder.mergeFrom(TestAllTypesLite.newBuilder() + .setOptionalBool(true) + .build()); + assertEquals(0, message.getSerializedSize()); + assertEquals(true, builder.build().getOptionalBool()); + builder.clearOptionalBool(); + + message = builder.build(); + assertEquals(0, message.getSerializedSize()); + builder.mergeFrom(TestAllTypesLite.newBuilder() + .setOptionalBool(true) + .build()); + assertEquals(0, message.getSerializedSize()); + assertEquals(true, builder.build().getOptionalBool()); + builder.clear(); + assertEquals(0, builder.build().getSerializedSize()); + + message = builder.build(); + assertEquals(0, message.getSerializedSize()); + builder.mergeOptionalForeignMessage(foreignMessage); + assertEquals(0, message.getSerializedSize()); + assertEquals( + foreignMessage.getC(), + builder.build().getOptionalForeignMessage().getC()); + builder.clearOptionalForeignMessage(); + + message = builder.build(); + assertEquals(0, message.getSerializedSize()); + builder.mergeOptionalLazyMessage(nestedMessage); + assertEquals(0, message.getSerializedSize()); + assertEquals( + nestedMessage.getBb(), + builder.build().getOptionalLazyMessage().getBb()); + builder.clearOptionalLazyMessage(); + + message = builder.build(); + builder.setOneofString("hi"); + assertEquals( + OneofFieldCase.ONEOFFIELD_NOT_SET, message.getOneofFieldCase()); + assertEquals(OneofFieldCase.ONEOF_STRING, builder.getOneofFieldCase()); + assertEquals("hi", builder.getOneofString()); + messageAfterBuild = builder.build(); + assertEquals( + OneofFieldCase.ONEOF_STRING, messageAfterBuild.getOneofFieldCase()); + assertEquals("hi", messageAfterBuild.getOneofString()); + builder.setOneofUint32(1); + assertEquals( + OneofFieldCase.ONEOF_STRING, messageAfterBuild.getOneofFieldCase()); + assertEquals("hi", messageAfterBuild.getOneofString()); + assertEquals(OneofFieldCase.ONEOF_UINT32, builder.getOneofFieldCase()); + assertEquals(1, builder.getOneofUint32()); + TestAllTypesLiteOrBuilder messageOrBuilder = builder; + assertEquals(OneofFieldCase.ONEOF_UINT32, messageOrBuilder.getOneofFieldCase()); + + TestAllExtensionsLite.Builder extendableMessageBuilder = + TestAllExtensionsLite.newBuilder(); + TestAllExtensionsLite extendableMessage = extendableMessageBuilder.build(); + extendableMessageBuilder.setExtension( + UnittestLite.optionalInt32ExtensionLite, 1); + assertFalse(extendableMessage.hasExtension( + UnittestLite.optionalInt32ExtensionLite)); + extendableMessage = extendableMessageBuilder.build(); + assertEquals( + 1, (int) extendableMessageBuilder.getExtension( + UnittestLite.optionalInt32ExtensionLite)); + assertEquals( + 1, (int) extendableMessage.getExtension( + UnittestLite.optionalInt32ExtensionLite)); + extendableMessageBuilder.setExtension( + UnittestLite.optionalInt32ExtensionLite, 3); + assertEquals( + 3, (int) extendableMessageBuilder.getExtension( + UnittestLite.optionalInt32ExtensionLite)); + assertEquals( + 1, (int) extendableMessage.getExtension( + UnittestLite.optionalInt32ExtensionLite)); + extendableMessage = extendableMessageBuilder.build(); + assertEquals( + 3, (int) extendableMessageBuilder.getExtension( + UnittestLite.optionalInt32ExtensionLite)); + assertEquals( + 3, (int) extendableMessage.getExtension( + UnittestLite.optionalInt32ExtensionLite)); + + // No extension registry, so it should be in unknown fields. + extendableMessage = + TestAllExtensionsLite.parseFrom(extendableMessage.toByteArray()); + assertFalse(extendableMessage.hasExtension( + UnittestLite.optionalInt32ExtensionLite)); + + extendableMessageBuilder = extendableMessage.toBuilder(); + extendableMessageBuilder.mergeFrom(TestAllExtensionsLite.newBuilder() + .setExtension(UnittestLite.optionalFixed32ExtensionLite, 11) + .build()); + + extendableMessage = extendableMessageBuilder.build(); + ExtensionRegistryLite registry = ExtensionRegistryLite.newInstance(); + UnittestLite.registerAllExtensions(registry); + extendableMessage = TestAllExtensionsLite.parseFrom( + extendableMessage.toByteArray(), registry); + + // The unknown field was preserved. + assertEquals( + 3, (int) extendableMessage.getExtension( + UnittestLite.optionalInt32ExtensionLite)); + assertEquals( + 11, (int) extendableMessage.getExtension( + UnittestLite.optionalFixed32ExtensionLite)); + } +} diff --git a/java/core/src/test/java/com/google/protobuf/LiteralByteStringTest.java b/java/core/src/test/java/com/google/protobuf/LiteralByteStringTest.java new file mode 100644 index 00000000..c8495633 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/LiteralByteStringTest.java @@ -0,0 +1,497 @@ +// 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 junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.List; +import java.util.NoSuchElementException; + +/** + * Test {@link LiteralByteString} by setting up a reference string in {@link #setUp()}. + * This class is designed to be extended for testing extensions of {@link LiteralByteString} + * such as {@link BoundedByteString}, see {@link BoundedByteStringTest}. + * + * @author carlanton@google.com (Carl Haverl) + */ +public class LiteralByteStringTest extends TestCase { + protected static final String UTF_8 = "UTF-8"; + + protected String classUnderTest; + protected byte[] referenceBytes; + protected ByteString stringUnderTest; + protected int expectedHashCode; + + @Override + protected void setUp() throws Exception { + classUnderTest = "LiteralByteString"; + referenceBytes = ByteStringTest.getTestBytes(1234, 11337766L); + stringUnderTest = ByteString.copyFrom(referenceBytes); + expectedHashCode = 331161852; + } + + public void testExpectedType() { + String actualClassName = getActualClassName(stringUnderTest); + assertEquals(classUnderTest + " should match type exactly", classUnderTest, actualClassName); + } + + protected String getActualClassName(Object object) { + String actualClassName = object.getClass().getName(); + actualClassName = actualClassName.substring(actualClassName.lastIndexOf('.') + 1); + return actualClassName; + } + + public void testByteAt() { + boolean stillEqual = true; + for (int i = 0; stillEqual && i < referenceBytes.length; ++i) { + stillEqual = (referenceBytes[i] == stringUnderTest.byteAt(i)); + } + assertTrue(classUnderTest + " must capture the right bytes", stillEqual); + } + + public void testByteIterator() { + boolean stillEqual = true; + ByteString.ByteIterator iter = stringUnderTest.iterator(); + for (int i = 0; stillEqual && i < referenceBytes.length; ++i) { + stillEqual = (iter.hasNext() && referenceBytes[i] == iter.nextByte()); + } + assertTrue(classUnderTest + " must capture the right bytes", stillEqual); + assertFalse(classUnderTest + " must have exhausted the itertor", iter.hasNext()); + + try { + iter.nextByte(); + fail("Should have thrown an exception."); + } catch (NoSuchElementException e) { + // This is success + } + } + + public void testByteIterable() { + boolean stillEqual = true; + int j = 0; + for (byte quantum : stringUnderTest) { + stillEqual = (referenceBytes[j] == quantum); + ++j; + } + assertTrue(classUnderTest + " must capture the right bytes as Bytes", stillEqual); + assertEquals(classUnderTest + " iterable character count", referenceBytes.length, j); + } + + public void testSize() { + assertEquals(classUnderTest + " must have the expected size", referenceBytes.length, + stringUnderTest.size()); + } + + public void testGetTreeDepth() { + assertEquals(classUnderTest + " must have depth 0", 0, stringUnderTest.getTreeDepth()); + } + + public void testIsBalanced() { + assertTrue(classUnderTest + " is technically balanced", stringUnderTest.isBalanced()); + } + + public void testCopyTo_ByteArrayOffsetLength() { + int destinationOffset = 50; + int length = 100; + byte[] destination = new byte[destinationOffset + length]; + int sourceOffset = 213; + stringUnderTest.copyTo(destination, sourceOffset, destinationOffset, length); + boolean stillEqual = true; + for (int i = 0; stillEqual && i < length; ++i) { + stillEqual = referenceBytes[i + sourceOffset] == destination[i + destinationOffset]; + } + assertTrue(classUnderTest + ".copyTo(4 arg) must give the expected bytes", stillEqual); + } + + public void testCopyTo_ByteArrayOffsetLengthErrors() { + int destinationOffset = 50; + int length = 100; + byte[] destination = new byte[destinationOffset + length]; + + try { + // Copy one too many bytes + stringUnderTest.copyTo(destination, stringUnderTest.size() + 1 - length, + destinationOffset, length); + fail("Should have thrown an exception when copying too many bytes of a " + + classUnderTest); + } catch (IndexOutOfBoundsException expected) { + // This is success + } + + try { + // Copy with illegal negative sourceOffset + stringUnderTest.copyTo(destination, -1, destinationOffset, length); + fail("Should have thrown an exception when given a negative sourceOffset in " + + classUnderTest); + } catch (IndexOutOfBoundsException expected) { + // This is success + } + + try { + // Copy with illegal negative destinationOffset + stringUnderTest.copyTo(destination, 0, -1, length); + fail("Should have thrown an exception when given a negative destinationOffset in " + + classUnderTest); + } catch (IndexOutOfBoundsException expected) { + // This is success + } + + try { + // Copy with illegal negative size + stringUnderTest.copyTo(destination, 0, 0, -1); + fail("Should have thrown an exception when given a negative size in " + + classUnderTest); + } catch (IndexOutOfBoundsException expected) { + // This is success + } + + try { + // Copy with illegal too-large sourceOffset + stringUnderTest.copyTo(destination, 2 * stringUnderTest.size(), 0, length); + fail("Should have thrown an exception when the destinationOffset is too large in " + + classUnderTest); + } catch (IndexOutOfBoundsException expected) { + // This is success + } + + try { + // Copy with illegal too-large destinationOffset + stringUnderTest.copyTo(destination, 0, 2 * destination.length, length); + fail("Should have thrown an exception when the destinationOffset is too large in " + + classUnderTest); + } catch (IndexOutOfBoundsException expected) { + // This is success + } + } + + public void testCopyTo_ByteBuffer() { + ByteBuffer myBuffer = ByteBuffer.allocate(referenceBytes.length); + stringUnderTest.copyTo(myBuffer); + assertTrue(classUnderTest + ".copyTo(ByteBuffer) must give back the same bytes", + Arrays.equals(referenceBytes, myBuffer.array())); + } + + public void testMarkSupported() { + InputStream stream = stringUnderTest.newInput(); + assertTrue(classUnderTest + ".newInput() must support marking", stream.markSupported()); + } + + public void testMarkAndReset() throws IOException { + int fraction = stringUnderTest.size() / 3; + + InputStream stream = stringUnderTest.newInput(); + stream.mark(stringUnderTest.size()); // First, mark() the end. + + skipFully(stream, fraction); // Skip a large fraction, but not all. + int available = stream.available(); + assertTrue( + classUnderTest + ": after skipping to the 'middle', half the bytes are available", + (stringUnderTest.size() - fraction) == available); + stream.reset(); + + skipFully(stream, stringUnderTest.size()); // Skip to the end. + available = stream.available(); + assertTrue( + classUnderTest + ": after skipping to the end, no more bytes are available", + 0 == available); + } + + /** + * Discards {@code n} bytes of data from the input stream. This method + * will block until the full amount has been skipped. Does not close the + * stream. + * <p>Copied from com.google.common.io.ByteStreams to avoid adding dependency. + * + * @param in the input stream to read from + * @param n the number of bytes to skip + * @throws EOFException if this stream reaches the end before skipping all + * the bytes + * @throws IOException if an I/O error occurs, or the stream does not + * support skipping + */ + static void skipFully(InputStream in, long n) throws IOException { + long toSkip = n; + while (n > 0) { + long amt = in.skip(n); + if (amt == 0) { + // Force a blocking read to avoid infinite loop + if (in.read() == -1) { + long skipped = toSkip - n; + throw new EOFException("reached end of stream after skipping " + + skipped + " bytes; " + toSkip + " bytes expected"); + } + n--; + } else { + n -= amt; + } + } + } + + public void testAsReadOnlyByteBuffer() { + ByteBuffer byteBuffer = stringUnderTest.asReadOnlyByteBuffer(); + byte[] roundTripBytes = new byte[referenceBytes.length]; + assertTrue(byteBuffer.remaining() == referenceBytes.length); + assertTrue(byteBuffer.isReadOnly()); + byteBuffer.get(roundTripBytes); + assertTrue(classUnderTest + ".asReadOnlyByteBuffer() must give back the same bytes", + Arrays.equals(referenceBytes, roundTripBytes)); + } + + public void testAsReadOnlyByteBufferList() { + List<ByteBuffer> byteBuffers = stringUnderTest.asReadOnlyByteBufferList(); + int bytesSeen = 0; + byte[] roundTripBytes = new byte[referenceBytes.length]; + for (ByteBuffer byteBuffer : byteBuffers) { + int thisLength = byteBuffer.remaining(); + assertTrue(byteBuffer.isReadOnly()); + assertTrue(bytesSeen + thisLength <= referenceBytes.length); + byteBuffer.get(roundTripBytes, bytesSeen, thisLength); + bytesSeen += thisLength; + } + assertTrue(bytesSeen == referenceBytes.length); + assertTrue(classUnderTest + ".asReadOnlyByteBufferTest() must give back the same bytes", + Arrays.equals(referenceBytes, roundTripBytes)); + } + + public void testToByteArray() { + byte[] roundTripBytes = stringUnderTest.toByteArray(); + assertTrue(classUnderTest + ".toByteArray() must give back the same bytes", + Arrays.equals(referenceBytes, roundTripBytes)); + } + + public void testWriteTo() throws IOException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + stringUnderTest.writeTo(bos); + byte[] roundTripBytes = bos.toByteArray(); + assertTrue(classUnderTest + ".writeTo() must give back the same bytes", + Arrays.equals(referenceBytes, roundTripBytes)); + } + + public void testWriteTo_mutating() throws IOException { + OutputStream os = new OutputStream() { + @Override + public void write(byte[] b, int off, int len) { + for (int x = 0; x < len; ++x) { + b[off + x] = (byte) 0; + } + } + + @Override + public void write(int b) { + // Purposefully left blank. + } + }; + + stringUnderTest.writeTo(os); + byte[] newBytes = stringUnderTest.toByteArray(); + assertTrue(classUnderTest + ".writeTo() must not grant access to underlying array", + Arrays.equals(referenceBytes, newBytes)); + } + + public void testNewOutput() throws IOException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ByteString.Output output = ByteString.newOutput(); + stringUnderTest.writeTo(output); + assertEquals("Output Size returns correct result", + output.size(), stringUnderTest.size()); + output.writeTo(bos); + assertTrue("Output.writeTo() must give back the same bytes", + Arrays.equals(referenceBytes, bos.toByteArray())); + + // write the output stream to itself! This should cause it to double + output.writeTo(output); + assertEquals("Writing an output stream to itself is successful", + stringUnderTest.concat(stringUnderTest), output.toByteString()); + + output.reset(); + assertEquals("Output.reset() resets the output", 0, output.size()); + assertEquals("Output.reset() resets the output", + ByteString.EMPTY, output.toByteString()); + } + + public void testToString() throws UnsupportedEncodingException { + String testString = "I love unicode \u1234\u5678 characters"; + LiteralByteString unicode = new LiteralByteString(testString.getBytes(Internal.UTF_8)); + String roundTripString = unicode.toString(UTF_8); + assertEquals(classUnderTest + " unicode must match", testString, roundTripString); + } + + public void testCharsetToString() { + String testString = "I love unicode \u1234\u5678 characters"; + LiteralByteString unicode = new LiteralByteString(testString.getBytes(Internal.UTF_8)); + String roundTripString = unicode.toString(Internal.UTF_8); + assertEquals(classUnderTest + " unicode must match", testString, roundTripString); + } + + public void testToString_returnsCanonicalEmptyString() { + assertSame(classUnderTest + " must be the same string references", + ByteString.EMPTY.toString(Internal.UTF_8), + new LiteralByteString(new byte[]{}).toString(Internal.UTF_8)); + } + + public void testToString_raisesException() { + try { + ByteString.EMPTY.toString("invalid"); + fail("Should have thrown an exception."); + } catch (UnsupportedEncodingException expected) { + // This is success + } + + try { + new LiteralByteString(referenceBytes).toString("invalid"); + fail("Should have thrown an exception."); + } catch (UnsupportedEncodingException expected) { + // This is success + } + } + + public void testEquals() { + assertEquals(classUnderTest + " must not equal null", false, stringUnderTest.equals(null)); + assertEquals(classUnderTest + " must equal self", stringUnderTest, stringUnderTest); + assertFalse(classUnderTest + " must not equal the empty string", + stringUnderTest.equals(ByteString.EMPTY)); + assertEquals(classUnderTest + " empty strings must be equal", + new LiteralByteString(new byte[]{}), stringUnderTest.substring(55, 55)); + assertEquals(classUnderTest + " must equal another string with the same value", + stringUnderTest, new LiteralByteString(referenceBytes)); + + byte[] mungedBytes = new byte[referenceBytes.length]; + System.arraycopy(referenceBytes, 0, mungedBytes, 0, referenceBytes.length); + mungedBytes[mungedBytes.length - 5] = (byte) (mungedBytes[mungedBytes.length - 5] ^ 0xFF); + assertFalse(classUnderTest + " must not equal every string with the same length", + stringUnderTest.equals(new LiteralByteString(mungedBytes))); + } + + public void testHashCode() { + int hash = stringUnderTest.hashCode(); + assertEquals(classUnderTest + " must have expected hashCode", expectedHashCode, hash); + } + + public void testPeekCachedHashCode() { + assertEquals(classUnderTest + ".peekCachedHashCode() should return zero at first", 0, + stringUnderTest.peekCachedHashCode()); + stringUnderTest.hashCode(); + assertEquals(classUnderTest + ".peekCachedHashCode should return zero at first", + expectedHashCode, stringUnderTest.peekCachedHashCode()); + } + + public void testPartialHash() { + // partialHash() is more strenuously tested elsewhere by testing hashes of substrings. + // This test would fail if the expected hash were 1. It's not. + int hash = stringUnderTest.partialHash(stringUnderTest.size(), 0, stringUnderTest.size()); + assertEquals(classUnderTest + ".partialHash() must yield expected hashCode", + expectedHashCode, hash); + } + + public void testNewInput() throws IOException { + InputStream input = stringUnderTest.newInput(); + assertEquals("InputStream.available() returns correct value", + stringUnderTest.size(), input.available()); + boolean stillEqual = true; + for (byte referenceByte : referenceBytes) { + int expectedInt = (referenceByte & 0xFF); + stillEqual = (expectedInt == input.read()); + } + assertEquals("InputStream.available() returns correct value", + 0, input.available()); + assertTrue(classUnderTest + " must give the same bytes from the InputStream", stillEqual); + assertEquals(classUnderTest + " InputStream must now be exhausted", -1, input.read()); + } + + public void testNewInput_skip() throws IOException { + InputStream input = stringUnderTest.newInput(); + int stringSize = stringUnderTest.size(); + int nearEndIndex = stringSize * 2 / 3; + long skipped1 = input.skip(nearEndIndex); + assertEquals("InputStream.skip()", skipped1, nearEndIndex); + assertEquals("InputStream.available()", + stringSize - skipped1, input.available()); + assertTrue("InputStream.mark() is available", input.markSupported()); + input.mark(0); + assertEquals("InputStream.skip(), read()", + stringUnderTest.byteAt(nearEndIndex) & 0xFF, input.read()); + assertEquals("InputStream.available()", + stringSize - skipped1 - 1, input.available()); + long skipped2 = input.skip(stringSize); + assertEquals("InputStream.skip() incomplete", + skipped2, stringSize - skipped1 - 1); + assertEquals("InputStream.skip(), no more input", 0, input.available()); + assertEquals("InputStream.skip(), no more input", -1, input.read()); + input.reset(); + assertEquals("InputStream.reset() succeded", + stringSize - skipped1, input.available()); + assertEquals("InputStream.reset(), read()", + stringUnderTest.byteAt(nearEndIndex) & 0xFF, input.read()); + } + + public void testNewCodedInput() throws IOException { + CodedInputStream cis = stringUnderTest.newCodedInput(); + byte[] roundTripBytes = cis.readRawBytes(referenceBytes.length); + assertTrue(classUnderTest + " must give the same bytes back from the CodedInputStream", + Arrays.equals(referenceBytes, roundTripBytes)); + assertTrue(classUnderTest + " CodedInputStream must now be exhausted", cis.isAtEnd()); + } + + /** + * Make sure we keep things simple when concatenating with empty. See also + * {@link ByteStringTest#testConcat_empty()}. + */ + public void testConcat_empty() { + assertSame(classUnderTest + " concatenated with empty must give " + classUnderTest, + stringUnderTest.concat(ByteString.EMPTY), stringUnderTest); + assertSame("empty concatenated with " + classUnderTest + " must give " + classUnderTest, + ByteString.EMPTY.concat(stringUnderTest), stringUnderTest); + } + + public void testJavaSerialization() throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(out); + oos.writeObject(stringUnderTest); + oos.close(); + byte[] pickled = out.toByteArray(); + InputStream in = new ByteArrayInputStream(pickled); + ObjectInputStream ois = new ObjectInputStream(in); + Object o = ois.readObject(); + assertTrue("Didn't get a ByteString back", o instanceof ByteString); + assertEquals("Should get an equal ByteString back", stringUnderTest, o); + } +} diff --git a/java/core/src/test/java/com/google/protobuf/LongArrayListTest.java b/java/core/src/test/java/com/google/protobuf/LongArrayListTest.java new file mode 100644 index 00000000..1bd094f7 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/LongArrayListTest.java @@ -0,0 +1,473 @@ +// 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 static java.util.Arrays.asList; + +import junit.framework.TestCase; + +import java.util.Collections; +import java.util.ConcurrentModificationException; +import java.util.Iterator; + +/** + * Tests for {@link LongArrayList}. + * + * @author dweis@google.com (Daniel Weis) + */ +public class LongArrayListTest extends TestCase { + + private static final LongArrayList UNARY_LIST = newImmutableLongArrayList(1); + private static final LongArrayList TERTIARY_LIST = + newImmutableLongArrayList(1, 2, 3); + + private LongArrayList list; + + @Override + protected void setUp() throws Exception { + list = new LongArrayList(); + } + + public void testEmptyListReturnsSameInstance() { + assertSame(LongArrayList.emptyList(), LongArrayList.emptyList()); + } + + public void testEmptyListIsImmutable() { + assertImmutable(LongArrayList.emptyList()); + } + + public void testMakeImmutable() { + list.addLong(2); + list.addLong(4); + list.addLong(6); + list.addLong(8); + list.makeImmutable(); + assertImmutable(list); + } + + public void testCopyConstructor() { + LongArrayList copy = new LongArrayList(TERTIARY_LIST); + assertEquals(TERTIARY_LIST, copy); + + copy = new LongArrayList(LongArrayList.emptyList()); + assertEquals(LongArrayList.emptyList(), copy); + + copy = new LongArrayList(asList(1L, 2L, 3L)); + assertEquals(asList(1L, 2L, 3L), copy); + + copy = new LongArrayList(Collections.<Long>emptyList()); + assertEquals(LongArrayList.emptyList(), copy); + } + + public void testModificationWithIteration() { + list.addAll(asList(1L, 2L, 3L, 4L)); + Iterator<Long> iterator = list.iterator(); + assertEquals(4, list.size()); + assertEquals(1, (long) list.get(0)); + assertEquals(1, (long) iterator.next()); + list.set(0, 1L); + assertEquals(2, (long) iterator.next()); + + list.remove(0); + try { + iterator.next(); + fail(); + } catch (ConcurrentModificationException e) { + // expected + } + + iterator = list.iterator(); + list.add(0, 0L); + try { + iterator.next(); + fail(); + } catch (ConcurrentModificationException e) { + // expected + } + } + + public void testGet() { + assertEquals(1, (long) TERTIARY_LIST.get(0)); + assertEquals(2, (long) TERTIARY_LIST.get(1)); + assertEquals(3, (long) TERTIARY_LIST.get(2)); + + try { + TERTIARY_LIST.get(-1); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + + try { + TERTIARY_LIST.get(3); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + public void testGetLong() { + assertEquals(1, TERTIARY_LIST.getLong(0)); + assertEquals(2, TERTIARY_LIST.getLong(1)); + assertEquals(3, TERTIARY_LIST.getLong(2)); + + try { + TERTIARY_LIST.get(-1); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + + try { + TERTIARY_LIST.get(3); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + public void testSize() { + assertEquals(0, LongArrayList.emptyList().size()); + assertEquals(1, UNARY_LIST.size()); + assertEquals(3, TERTIARY_LIST.size()); + + list.addLong(2); + list.addLong(4); + list.addLong(6); + list.addLong(8); + assertEquals(4, list.size()); + + list.remove(0); + assertEquals(3, list.size()); + + list.add(16L); + assertEquals(4, list.size()); + } + + public void testSet() { + list.addLong(2); + list.addLong(4); + + assertEquals(2, (long) list.set(0, 0L)); + assertEquals(0, list.getLong(0)); + + assertEquals(4, (long) list.set(1, 0L)); + assertEquals(0, list.getLong(1)); + + try { + list.set(-1, 0L); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + + try { + list.set(2, 0L); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + public void testSetLong() { + list.addLong(2); + list.addLong(4); + + assertEquals(2, list.setLong(0, 0)); + assertEquals(0, list.getLong(0)); + + assertEquals(4, list.setLong(1, 0)); + assertEquals(0, list.getLong(1)); + + try { + list.setLong(-1, 0); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + + try { + list.setLong(2, 0); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + public void testAdd() { + assertEquals(0, list.size()); + + assertTrue(list.add(2L)); + assertEquals(asList(2L), list); + + assertTrue(list.add(3L)); + list.add(0, 4L); + assertEquals(asList(4L, 2L, 3L), list); + + list.add(0, 1L); + list.add(0, 0L); + // Force a resize by getting up to 11 elements. + for (int i = 0; i < 6; i++) { + list.add(Long.valueOf(5 + i)); + } + assertEquals(asList(0L, 1L, 4L, 2L, 3L, 5L, 6L, 7L, 8L, 9L, 10L), list); + + try { + list.add(-1, 5L); + } catch (IndexOutOfBoundsException e) { + // expected + } + + try { + list.add(4, 5L); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + public void testAddLong() { + assertEquals(0, list.size()); + + list.addLong(2); + assertEquals(asList(2L), list); + + list.addLong(3); + assertEquals(asList(2L, 3L), list); + } + + public void testAddAll() { + assertEquals(0, list.size()); + + assertTrue(list.addAll(Collections.singleton(1L))); + assertEquals(1, list.size()); + assertEquals(1, (long) list.get(0)); + assertEquals(1, list.getLong(0)); + + assertTrue(list.addAll(asList(2L, 3L, 4L, 5L, 6L))); + assertEquals(asList(1L, 2L, 3L, 4L, 5L, 6L), list); + + assertTrue(list.addAll(TERTIARY_LIST)); + assertEquals(asList(1L, 2L, 3L, 4L, 5L, 6L, 1L, 2L, 3L), list); + + assertFalse(list.addAll(Collections.<Long>emptyList())); + assertFalse(list.addAll(LongArrayList.emptyList())); + } + + public void testRemove() { + list.addAll(TERTIARY_LIST); + assertEquals(1, (long) list.remove(0)); + assertEquals(asList(2L, 3L), list); + + assertTrue(list.remove(3L)); + assertEquals(asList(2L), list); + + assertFalse(list.remove(3L)); + assertEquals(asList(2L), list); + + assertEquals(2, (long) list.remove(0)); + assertEquals(asList(), list); + + try { + list.remove(-1); + fail(); + } catch (IndexOutOfBoundsException e) { + // expected + } + + try { + list.remove(0); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + private void assertImmutable(LongArrayList list) { + if (list.contains(1L)) { + throw new RuntimeException("Cannot test the immutability of lists that contain 1."); + } + + try { + list.add(1L); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.add(0, 1L); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(Collections.<Long>emptyList()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(Collections.singletonList(1L)); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(new LongArrayList()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(UNARY_LIST); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(0, Collections.singleton(1L)); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(0, UNARY_LIST); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(0, Collections.<Long>emptyList()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addLong(0); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.clear(); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.remove(1); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.remove(new Object()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.removeAll(Collections.<Long>emptyList()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.removeAll(Collections.singleton(1L)); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.removeAll(UNARY_LIST); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.retainAll(Collections.<Long>emptyList()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.retainAll(Collections.singleton(1L)); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.retainAll(UNARY_LIST); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.set(0, 0L); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.setLong(0, 0); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + } + + private static LongArrayList newImmutableLongArrayList(long... elements) { + LongArrayList list = new LongArrayList(); + for (long element : elements) { + list.addLong(element); + } + list.makeImmutable(); + return list; + } +} diff --git a/java/core/src/test/java/com/google/protobuf/MapForProto2LiteTest.java b/java/core/src/test/java/com/google/protobuf/MapForProto2LiteTest.java new file mode 100644 index 00000000..3d8c9bc4 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/MapForProto2LiteTest.java @@ -0,0 +1,458 @@ +// 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; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * 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 copyMapValues(TestMap source, TestMap.Builder destination) { + destination + .putAllInt32ToInt32Field(source.getInt32ToInt32Field()) + .putAllInt32ToStringField(source.getInt32ToStringField()) + .putAllInt32ToBytesField(source.getInt32ToBytesField()) + .putAllInt32ToEnumField(source.getInt32ToEnumField()) + .putAllInt32ToMessageField(source.getInt32ToMessageField()) + .putAllStringToInt32Field(source.getStringToInt32Field()); + } + + 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 testSanityCopyOnWrite() throws InvalidProtocolBufferException { + // Since builders are implemented as a thin wrapper around a message + // instance, we attempt to verify that we can't cause the builder to modify + // a produced message. + + TestMap.Builder builder = TestMap.newBuilder(); + TestMap message = builder.build(); + Map<Integer, Integer> intMap = builder.getMutableInt32ToInt32Field(); + intMap.put(1, 2); + assertTrue(message.getInt32ToInt32Field().isEmpty()); + message = builder.build(); + try { + intMap.put(2, 3); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + assertEquals(newMap(1, 2), message.getInt32ToInt32Field()); + assertEquals(newMap(1, 2), builder.getInt32ToInt32Field()); + builder.getMutableInt32ToInt32Field().put(2, 3); + assertEquals(newMap(1, 2), message.getInt32ToInt32Field()); + assertEquals(newMap(1, 2, 2, 3), builder.getInt32ToInt32Field()); + } + + public void testMutableMapLifecycle() { + TestMap.Builder builder = TestMap.newBuilder(); + Map<Integer, Integer> intMap = builder.getMutableInt32ToInt32Field(); + intMap.put(1, 2); + assertEquals(newMap(1, 2), builder.build().getInt32ToInt32Field()); + try { + intMap.put(2, 3); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + assertEquals(newMap(1, 2), builder.getInt32ToInt32Field()); + builder.getMutableInt32ToInt32Field().put(2, 3); + assertEquals(newMap(1, 2, 2, 3), builder.getInt32ToInt32Field()); + + Map<Integer, TestMap.EnumValue> enumMap = builder.getMutableInt32ToEnumField(); + enumMap.put(1, TestMap.EnumValue.BAR); + assertEquals(newMap(1, TestMap.EnumValue.BAR), builder.build().getInt32ToEnumField()); + try { + enumMap.put(2, TestMap.EnumValue.FOO); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + assertEquals(newMap(1, TestMap.EnumValue.BAR), builder.getInt32ToEnumField()); + builder.getMutableInt32ToEnumField().put(2, TestMap.EnumValue.FOO); + assertEquals( + newMap(1, TestMap.EnumValue.BAR, 2, TestMap.EnumValue.FOO), + builder.getInt32ToEnumField()); + + Map<Integer, String> stringMap = builder.getMutableInt32ToStringField(); + stringMap.put(1, "1"); + assertEquals(newMap(1, "1"), builder.build().getInt32ToStringField()); + try { + stringMap.put(2, "2"); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + assertEquals(newMap(1, "1"), builder.getInt32ToStringField()); + builder.getMutableInt32ToStringField().put(2, "2"); + assertEquals( + newMap(1, "1", 2, "2"), + builder.getInt32ToStringField()); + + Map<Integer, TestMap.MessageValue> messageMap = builder.getMutableInt32ToMessageField(); + messageMap.put(1, TestMap.MessageValue.getDefaultInstance()); + assertEquals(newMap(1, TestMap.MessageValue.getDefaultInstance()), + builder.build().getInt32ToMessageField()); + try { + messageMap.put(2, TestMap.MessageValue.getDefaultInstance()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + assertEquals(newMap(1, TestMap.MessageValue.getDefaultInstance()), + builder.getInt32ToMessageField()); + builder.getMutableInt32ToMessageField().put(2, TestMap.MessageValue.getDefaultInstance()); + assertEquals( + newMap(1, TestMap.MessageValue.getDefaultInstance(), + 2, TestMap.MessageValue.getDefaultInstance()), + builder.getInt32ToMessageField()); + } + + public void testMutableMapLifecycle_collections() { + TestMap.Builder builder = TestMap.newBuilder(); + Map<Integer, Integer> intMap = builder.getMutableInt32ToInt32Field(); + intMap.put(1, 2); + assertEquals(newMap(1, 2), builder.build().getInt32ToInt32Field()); + try { + intMap.remove(2); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + try { + intMap.entrySet().remove(new Object()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + try { + intMap.entrySet().iterator().remove(); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + try { + intMap.keySet().remove(new Object()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + try { + intMap.values().remove(new Object()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + try { + intMap.values().iterator().remove(); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + assertEquals(newMap(1, 2), intMap); + assertEquals(newMap(1, 2), builder.getInt32ToInt32Field()); + assertEquals(newMap(1, 2), builder.build().getInt32ToInt32Field()); + } + + 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 testPutAll() throws Exception { + TestMap.Builder sourceBuilder = TestMap.newBuilder(); + setMapValues(sourceBuilder); + TestMap source = sourceBuilder.build(); + + TestMap.Builder destination = TestMap.newBuilder(); + copyMapValues(source, destination); + assertMapValuesSet(destination.build()); + } + + 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()); + } + + + public void testIterationOrder() throws Exception { + TestMap.Builder builder = TestMap.newBuilder(); + setMapValues(builder); + TestMap message = builder.build(); + + assertEquals(Arrays.asList("1", "2", "3"), + new ArrayList<String>(message.getStringToInt32Field().keySet())); + } + + private static <K, V> Map<K, V> newMap(K key1, V value1) { + Map<K, V> map = new HashMap<K, V>(); + map.put(key1, value1); + return map; + } + + private static <K, V> Map<K, V> newMap(K key1, V value1, K key2, V value2) { + Map<K, V> map = new HashMap<K, V>(); + map.put(key1, value1); + map.put(key2, value2); + return map; + } +} diff --git a/java/core/src/test/java/com/google/protobuf/MapForProto2Test.java b/java/core/src/test/java/com/google/protobuf/MapForProto2Test.java new file mode 100644 index 00000000..1fa3cbdb --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/MapForProto2Test.java @@ -0,0 +1,684 @@ +// 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.TestMap.MessageWithRequiredFields; +import map_test.MapForProto2TestProto.TestRecursiveMap; +import map_test.MapForProto2TestProto.TestUnknownEnumValue; + +import junit.framework.TestCase; + +import java.util.ArrayList; +import java.util.Arrays; +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 copyMapValues(TestMap source, TestMap.Builder destination) { + destination + .putAllInt32ToInt32Field(source.getInt32ToInt32Field()) + .putAllInt32ToStringField(source.getInt32ToStringField()) + .putAllInt32ToBytesField(source.getInt32ToBytesField()) + .putAllInt32ToEnumField(source.getInt32ToEnumField()) + .putAllInt32ToMessageField(source.getInt32ToMessageField()) + .putAllStringToInt32Field(source.getStringToInt32Field()); + } + + 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 testMutableMapLifecycle() { + TestMap.Builder builder = TestMap.newBuilder(); + Map<Integer, Integer> intMap = builder.getMutableInt32ToInt32Field(); + intMap.put(1, 2); + assertEquals(newMap(1, 2), builder.build().getInt32ToInt32Field()); + try { + intMap.put(2, 3); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + assertEquals(newMap(1, 2), builder.getInt32ToInt32Field()); + builder.getMutableInt32ToInt32Field().put(2, 3); + assertEquals(newMap(1, 2, 2, 3), builder.getInt32ToInt32Field()); + + Map<Integer, TestMap.EnumValue> enumMap = builder.getMutableInt32ToEnumField(); + enumMap.put(1, TestMap.EnumValue.BAR); + assertEquals(newMap(1, TestMap.EnumValue.BAR), builder.build().getInt32ToEnumField()); + try { + enumMap.put(2, TestMap.EnumValue.FOO); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + assertEquals(newMap(1, TestMap.EnumValue.BAR), builder.getInt32ToEnumField()); + builder.getMutableInt32ToEnumField().put(2, TestMap.EnumValue.FOO); + assertEquals( + newMap(1, TestMap.EnumValue.BAR, 2, TestMap.EnumValue.FOO), + builder.getInt32ToEnumField()); + + Map<Integer, String> stringMap = builder.getMutableInt32ToStringField(); + stringMap.put(1, "1"); + assertEquals(newMap(1, "1"), builder.build().getInt32ToStringField()); + try { + stringMap.put(2, "2"); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + assertEquals(newMap(1, "1"), builder.getInt32ToStringField()); + builder.getMutableInt32ToStringField().put(2, "2"); + assertEquals( + newMap(1, "1", 2, "2"), + builder.getInt32ToStringField()); + + Map<Integer, TestMap.MessageValue> messageMap = builder.getMutableInt32ToMessageField(); + messageMap.put(1, TestMap.MessageValue.getDefaultInstance()); + assertEquals(newMap(1, TestMap.MessageValue.getDefaultInstance()), + builder.build().getInt32ToMessageField()); + try { + messageMap.put(2, TestMap.MessageValue.getDefaultInstance()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + assertEquals(newMap(1, TestMap.MessageValue.getDefaultInstance()), + builder.getInt32ToMessageField()); + builder.getMutableInt32ToMessageField().put(2, TestMap.MessageValue.getDefaultInstance()); + assertEquals( + newMap(1, TestMap.MessageValue.getDefaultInstance(), + 2, TestMap.MessageValue.getDefaultInstance()), + builder.getInt32ToMessageField()); + } + + public void testMutableMapLifecycle_collections() { + TestMap.Builder builder = TestMap.newBuilder(); + Map<Integer, Integer> intMap = builder.getMutableInt32ToInt32Field(); + intMap.put(1, 2); + assertEquals(newMap(1, 2), builder.build().getInt32ToInt32Field()); + try { + intMap.remove(2); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + try { + intMap.entrySet().remove(new Object()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + try { + intMap.entrySet().iterator().remove(); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + try { + intMap.keySet().remove(new Object()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + try { + intMap.values().remove(new Object()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + try { + intMap.values().iterator().remove(); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + assertEquals(newMap(1, 2), intMap); + assertEquals(newMap(1, 2), builder.getInt32ToInt32Field()); + assertEquals(newMap(1, 2), builder.build().getInt32ToInt32Field()); + } + + 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 testPutAll() throws Exception { + TestMap.Builder sourceBuilder = TestMap.newBuilder(); + setMapValues(sourceBuilder); + TestMap source = sourceBuilder.build(); + + TestMap.Builder destination = TestMap.newBuilder(); + copyMapValues(source, destination); + assertMapValuesSet(destination.build()); + } + + 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 <KeyType, ValueType> + 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<Message> entryList = new ArrayList<Message>(); + 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 <KeyType, ValueType> + Map<KeyType, ValueType> mapForValues( + KeyType key1, ValueType value1, KeyType key2, ValueType value2) { + Map<KeyType, ValueType> map = new HashMap<KeyType, ValueType>(); + 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()); + } + + + public void testRequiredMessage() throws Exception { + TestMap.Builder builder = TestMap.newBuilder(); + builder.getMutableRequiredMessageMap().put(0, + MessageWithRequiredFields.newBuilder().buildPartial()); + TestMap message = builder.buildPartial(); + assertFalse(message.isInitialized()); + + builder.getMutableRequiredMessageMap().put(0, + MessageWithRequiredFields.newBuilder().setValue(1).build()); + message = builder.build(); + assertTrue(message.isInitialized()); + } + + public void testRecursiveMap() throws Exception { + TestRecursiveMap.Builder builder = TestRecursiveMap.newBuilder(); + builder.getMutableRecursiveMapField().put( + 1, TestRecursiveMap.newBuilder().setValue(2).build()); + builder.getMutableRecursiveMapField().put( + 3, TestRecursiveMap.newBuilder().setValue(4).build()); + ByteString data = builder.build().toByteString(); + + TestRecursiveMap message = TestRecursiveMap.parseFrom(data); + assertEquals(2, message.getRecursiveMapField().get(1).getValue()); + assertEquals(4, message.getRecursiveMapField().get(3).getValue()); + } + + public void testIterationOrder() throws Exception { + TestMap.Builder builder = TestMap.newBuilder(); + setMapValues(builder); + TestMap message = builder.build(); + + assertEquals(Arrays.asList("1", "2", "3"), + new ArrayList<String>(message.getStringToInt32Field().keySet())); + } + + // Regression test for b/20494788 + public void testMapInitializationOrder() throws Exception { + assertEquals("RedactAllTypes", map_test.RedactAllTypes + .getDefaultInstance().getDescriptorForType().getName()); + + map_test.Message1.Builder builder = + map_test.Message1.newBuilder(); + builder.getMutableMapField().put("key", true); + map_test.Message1 message = builder.build(); + Message mapEntry = (Message) message.getRepeatedField( + message.getDescriptorForType().findFieldByName("map_field"), 0); + assertEquals(2, mapEntry.getAllFields().size()); + } + + private static <K, V> Map<K, V> newMap(K key1, V value1) { + Map<K, V> map = new HashMap<K, V>(); + map.put(key1, value1); + return map; + } + + private static <K, V> Map<K, V> newMap(K key1, V value1, K key2, V value2) { + Map<K, V> map = new HashMap<K, V>(); + map.put(key1, value1); + map.put(key2, value2); + return map; + } +} + diff --git a/java/core/src/test/java/com/google/protobuf/MapTest.java b/java/core/src/test/java/com/google/protobuf/MapTest.java new file mode 100644 index 00000000..0e5c1284 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/MapTest.java @@ -0,0 +1,783 @@ +// 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.Arrays; +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 copyMapValues(TestMap source, TestMap.Builder destination) { + destination + .putAllInt32ToInt32Field(source.getInt32ToInt32Field()) + .putAllInt32ToStringField(source.getInt32ToStringField()) + .putAllInt32ToBytesField(source.getInt32ToBytesField()) + .putAllInt32ToEnumField(source.getInt32ToEnumField()) + .putAllInt32ToMessageField(source.getInt32ToMessageField()) + .putAllStringToInt32Field(source.getStringToInt32Field()); + } + + 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 testMutableMapLifecycle() { + TestMap.Builder builder = TestMap.newBuilder(); + Map<Integer, Integer> intMap = builder.getMutableInt32ToInt32Field(); + intMap.put(1, 2); + assertEquals(newMap(1, 2), builder.build().getInt32ToInt32Field()); + try { + intMap.put(2, 3); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + assertEquals(newMap(1, 2), builder.getInt32ToInt32Field()); + builder.getMutableInt32ToInt32Field().put(2, 3); + assertEquals(newMap(1, 2, 2, 3), builder.getInt32ToInt32Field()); + + Map<Integer, TestMap.EnumValue> enumMap = builder.getMutableInt32ToEnumField(); + enumMap.put(1, TestMap.EnumValue.BAR); + assertEquals(newMap(1, TestMap.EnumValue.BAR), builder.build().getInt32ToEnumField()); + try { + enumMap.put(2, TestMap.EnumValue.FOO); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + assertEquals(newMap(1, TestMap.EnumValue.BAR), builder.getInt32ToEnumField()); + builder.getMutableInt32ToEnumField().put(2, TestMap.EnumValue.FOO); + assertEquals( + newMap(1, TestMap.EnumValue.BAR, 2, TestMap.EnumValue.FOO), + builder.getInt32ToEnumField()); + + Map<Integer, String> stringMap = builder.getMutableInt32ToStringField(); + stringMap.put(1, "1"); + assertEquals(newMap(1, "1"), builder.build().getInt32ToStringField()); + try { + stringMap.put(2, "2"); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + assertEquals(newMap(1, "1"), builder.getInt32ToStringField()); + builder.getMutableInt32ToStringField().put(2, "2"); + assertEquals( + newMap(1, "1", 2, "2"), + builder.getInt32ToStringField()); + + Map<Integer, TestMap.MessageValue> messageMap = builder.getMutableInt32ToMessageField(); + messageMap.put(1, TestMap.MessageValue.getDefaultInstance()); + assertEquals(newMap(1, TestMap.MessageValue.getDefaultInstance()), + builder.build().getInt32ToMessageField()); + try { + messageMap.put(2, TestMap.MessageValue.getDefaultInstance()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + assertEquals(newMap(1, TestMap.MessageValue.getDefaultInstance()), + builder.getInt32ToMessageField()); + builder.getMutableInt32ToMessageField().put(2, TestMap.MessageValue.getDefaultInstance()); + assertEquals( + newMap(1, TestMap.MessageValue.getDefaultInstance(), + 2, TestMap.MessageValue.getDefaultInstance()), + builder.getInt32ToMessageField()); + } + + public void testMutableMapLifecycle_collections() { + TestMap.Builder builder = TestMap.newBuilder(); + Map<Integer, Integer> intMap = builder.getMutableInt32ToInt32Field(); + intMap.put(1, 2); + assertEquals(newMap(1, 2), builder.build().getInt32ToInt32Field()); + try { + intMap.remove(2); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + try { + intMap.entrySet().remove(new Object()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + try { + intMap.entrySet().iterator().remove(); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + try { + intMap.keySet().remove(new Object()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + try { + intMap.values().remove(new Object()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + try { + intMap.values().iterator().remove(); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + assertEquals(newMap(1, 2), intMap); + assertEquals(newMap(1, 2), builder.getInt32ToInt32Field()); + assertEquals(newMap(1, 2), builder.build().getInt32ToInt32Field()); + } + + 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 testPutAll() throws Exception { + TestMap.Builder sourceBuilder = TestMap.newBuilder(); + setMapValues(sourceBuilder); + TestMap source = sourceBuilder.build(); + + TestMap.Builder destination = TestMap.newBuilder(); + copyMapValues(source, destination); + assertMapValuesSet(destination.build()); + } + + public void testPutAllForUnknownEnumValues() throws Exception { + TestMap.Builder sourceBuilder = TestMap.newBuilder(); + sourceBuilder.getMutableInt32ToEnumFieldValue().put(0, 0); + sourceBuilder.getMutableInt32ToEnumFieldValue().put(1, 1); + sourceBuilder.getMutableInt32ToEnumFieldValue().put(2, 1000); // unknown value. + TestMap source = sourceBuilder.build(); + + TestMap.Builder destinationBuilder = TestMap.newBuilder(); + destinationBuilder.putAllInt32ToEnumFieldValue(source.getInt32ToEnumFieldValue()); + TestMap destination = destinationBuilder.build(); + + assertEquals(0, destination.getInt32ToEnumFieldValue().get(0).intValue()); + assertEquals(1, destination.getInt32ToEnumFieldValue().get(1).intValue()); + assertEquals(1000, destination.getInt32ToEnumFieldValue().get(2).intValue()); + } + + 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. + + // Regression test for b/18549190: if a map is a subset of the other map, + // equals() should return false. + b2.getMutableInt32ToInt32Field().remove(1); + m2 = b2.build(); + assertFalse(m1.equals(m2)); + assertFalse(m2.equals(m1)); + } + + 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()); + } + + public void testNestedBuilderOnChangeEventPropagationReflection() { + FieldDescriptor intMapField = f("int32_to_int32_field"); + // Create an outer message builder with nested builder. + TestOnChangeEventPropagation.Builder parentBuilder = + TestOnChangeEventPropagation.newBuilder(); + TestMap.Builder testMapBuilder = parentBuilder.getOptionalMessageBuilder(); + + // Create a map entry message. + TestMap.Builder entryBuilder = TestMap.newBuilder(); + entryBuilder.getMutableInt32ToInt32Field().put(1, 1); + + // Put the entry into the nested builder. + testMapBuilder.addRepeatedField( + intMapField, entryBuilder.getRepeatedField(intMapField, 0)); + + // Should be able to observe the change. + TestOnChangeEventPropagation message = parentBuilder.build(); + assertEquals(1, message.getOptionalMessage().getInt32ToInt32Field().size()); + + // Change the entry value. + entryBuilder.getMutableInt32ToInt32Field().put(1, 4); + testMapBuilder = parentBuilder.getOptionalMessageBuilder(); + testMapBuilder.setRepeatedField( + intMapField, 0, entryBuilder.getRepeatedField(intMapField, 0)); + + // Should be able to observe the change. + message = parentBuilder.build(); + assertEquals(4, + message.getOptionalMessage().getInt32ToInt32Field().get(1).intValue()); + + // Clear the nested builder. + testMapBuilder = parentBuilder.getOptionalMessageBuilder(); + testMapBuilder.clearField(intMapField); + + // Should be able to observe the change. + message = parentBuilder.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 <KeyType, ValueType> + 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<Message> entryList = new ArrayList<Message>(); + 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 <KeyType, ValueType> + Map<KeyType, ValueType> mapForValues( + KeyType key1, ValueType value1, KeyType key2, ValueType value2) { + Map<KeyType, ValueType> map = new HashMap<KeyType, ValueType>(); + 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<Integer, Integer> data = new HashMap<Integer, Integer>(); + data.put(0, 0); + data.put(1, 1); + data.put(2, 1000); // unknown value. + + TestMap.Builder builder = TestMap.newBuilder(); + for (Map.Entry<Integer, Integer> 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<Integer, Integer> entry : message.getInt32ToEnumFieldValue().entrySet()) { + assertEquals(data.get(entry.getKey()) + 1, entry.getValue().intValue()); + } + } + + public void testIterationOrder() throws Exception { + TestMap.Builder builder = TestMap.newBuilder(); + setMapValues(builder); + TestMap message = builder.build(); + + assertEquals(Arrays.asList("1", "2", "3"), + new ArrayList<String>(message.getStringToInt32Field().keySet())); + } + + private static <K, V> Map<K, V> newMap(K key1, V value1) { + Map<K, V> map = new HashMap<K, V>(); + map.put(key1, value1); + return map; + } + + private static <K, V> Map<K, V> newMap(K key1, V value1, K key2, V value2) { + Map<K, V> map = new HashMap<K, V>(); + map.put(key1, value1); + map.put(key2, value2); + return map; + } +} diff --git a/java/core/src/test/java/com/google/protobuf/MessageTest.java b/java/core/src/test/java/com/google/protobuf/MessageTest.java new file mode 100644 index 00000000..abcd3a1d --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/MessageTest.java @@ -0,0 +1,353 @@ +// 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.UnittestProto.TestAllTypes; +import protobuf_unittest.UnittestProto.TestAllExtensions; +import protobuf_unittest.UnittestProto.TestRequired; +import protobuf_unittest.UnittestProto.TestRequiredForeign; +import protobuf_unittest.UnittestProto.ForeignMessage; + +import junit.framework.TestCase; + +import java.util.List; + +/** + * Misc. unit tests for message operations that apply to both generated + * and dynamic messages. + * + * @author kenton@google.com Kenton Varda + */ +public class MessageTest extends TestCase { + // ================================================================= + // Message-merging tests. + + static final TestAllTypes MERGE_SOURCE = + TestAllTypes.newBuilder() + .setOptionalInt32(1) + .setOptionalString("foo") + .setOptionalForeignMessage(ForeignMessage.getDefaultInstance()) + .addRepeatedString("bar") + .build(); + + static final TestAllTypes MERGE_DEST = + TestAllTypes.newBuilder() + .setOptionalInt64(2) + .setOptionalString("baz") + .setOptionalForeignMessage(ForeignMessage.newBuilder().setC(3).build()) + .addRepeatedString("qux") + .build(); + + static final String MERGE_RESULT_TEXT = + "optional_int32: 1\n" + + "optional_int64: 2\n" + + "optional_string: \"foo\"\n" + + "optional_foreign_message {\n" + + " c: 3\n" + + "}\n" + + "repeated_string: \"qux\"\n" + + "repeated_string: \"bar\"\n"; + + public void testMergeFrom() throws Exception { + TestAllTypes result = + TestAllTypes.newBuilder(MERGE_DEST) + .mergeFrom(MERGE_SOURCE).build(); + + assertEquals(MERGE_RESULT_TEXT, result.toString()); + } + + /** + * Test merging a DynamicMessage into a GeneratedMessage. As long as they + * have the same descriptor, this should work, but it is an entirely different + * code path. + */ + public void testMergeFromDynamic() throws Exception { + TestAllTypes result = + TestAllTypes.newBuilder(MERGE_DEST) + .mergeFrom(DynamicMessage.newBuilder(MERGE_SOURCE).build()) + .build(); + + assertEquals(MERGE_RESULT_TEXT, result.toString()); + } + + /** Test merging two DynamicMessages. */ + public void testDynamicMergeFrom() throws Exception { + DynamicMessage result = + DynamicMessage.newBuilder(MERGE_DEST) + .mergeFrom(DynamicMessage.newBuilder(MERGE_SOURCE).build()) + .build(); + + assertEquals(MERGE_RESULT_TEXT, result.toString()); + } + + // ================================================================= + // Required-field-related tests. + + private static final TestRequired TEST_REQUIRED_UNINITIALIZED = + TestRequired.getDefaultInstance(); + private static final TestRequired TEST_REQUIRED_INITIALIZED = + TestRequired.newBuilder().setA(1).setB(2).setC(3).build(); + + public void testRequired() throws Exception { + TestRequired.Builder builder = TestRequired.newBuilder(); + + assertFalse(builder.isInitialized()); + builder.setA(1); + assertFalse(builder.isInitialized()); + builder.setB(1); + assertFalse(builder.isInitialized()); + builder.setC(1); + assertTrue(builder.isInitialized()); + } + + public void testRequiredForeign() throws Exception { + TestRequiredForeign.Builder builder = TestRequiredForeign.newBuilder(); + + assertTrue(builder.isInitialized()); + + builder.setOptionalMessage(TEST_REQUIRED_UNINITIALIZED); + assertFalse(builder.isInitialized()); + + builder.setOptionalMessage(TEST_REQUIRED_INITIALIZED); + assertTrue(builder.isInitialized()); + + builder.addRepeatedMessage(TEST_REQUIRED_UNINITIALIZED); + assertFalse(builder.isInitialized()); + + builder.setRepeatedMessage(0, TEST_REQUIRED_INITIALIZED); + assertTrue(builder.isInitialized()); + } + + public void testRequiredExtension() throws Exception { + TestAllExtensions.Builder builder = TestAllExtensions.newBuilder(); + + assertTrue(builder.isInitialized()); + + builder.setExtension(TestRequired.single, TEST_REQUIRED_UNINITIALIZED); + assertFalse(builder.isInitialized()); + + builder.setExtension(TestRequired.single, TEST_REQUIRED_INITIALIZED); + assertTrue(builder.isInitialized()); + + builder.addExtension(TestRequired.multi, TEST_REQUIRED_UNINITIALIZED); + assertFalse(builder.isInitialized()); + + builder.setExtension(TestRequired.multi, 0, TEST_REQUIRED_INITIALIZED); + assertTrue(builder.isInitialized()); + } + + public void testRequiredDynamic() throws Exception { + Descriptors.Descriptor descriptor = TestRequired.getDescriptor(); + DynamicMessage.Builder builder = DynamicMessage.newBuilder(descriptor); + + assertFalse(builder.isInitialized()); + builder.setField(descriptor.findFieldByName("a"), 1); + assertFalse(builder.isInitialized()); + builder.setField(descriptor.findFieldByName("b"), 1); + assertFalse(builder.isInitialized()); + builder.setField(descriptor.findFieldByName("c"), 1); + assertTrue(builder.isInitialized()); + } + + public void testRequiredDynamicForeign() throws Exception { + Descriptors.Descriptor descriptor = TestRequiredForeign.getDescriptor(); + DynamicMessage.Builder builder = DynamicMessage.newBuilder(descriptor); + + assertTrue(builder.isInitialized()); + + builder.setField(descriptor.findFieldByName("optional_message"), + TEST_REQUIRED_UNINITIALIZED); + assertFalse(builder.isInitialized()); + + builder.setField(descriptor.findFieldByName("optional_message"), + TEST_REQUIRED_INITIALIZED); + assertTrue(builder.isInitialized()); + + builder.addRepeatedField(descriptor.findFieldByName("repeated_message"), + TEST_REQUIRED_UNINITIALIZED); + assertFalse(builder.isInitialized()); + + builder.setRepeatedField(descriptor.findFieldByName("repeated_message"), 0, + TEST_REQUIRED_INITIALIZED); + assertTrue(builder.isInitialized()); + } + + public void testUninitializedException() throws Exception { + try { + TestRequired.newBuilder().build(); + fail("Should have thrown an exception."); + } catch (UninitializedMessageException e) { + assertEquals("Message missing required fields: a, b, c", e.getMessage()); + } + } + + public void testBuildPartial() throws Exception { + // We're mostly testing that no exception is thrown. + TestRequired message = TestRequired.newBuilder().buildPartial(); + assertFalse(message.isInitialized()); + } + + public void testNestedUninitializedException() throws Exception { + try { + TestRequiredForeign.newBuilder() + .setOptionalMessage(TEST_REQUIRED_UNINITIALIZED) + .addRepeatedMessage(TEST_REQUIRED_UNINITIALIZED) + .addRepeatedMessage(TEST_REQUIRED_UNINITIALIZED) + .build(); + fail("Should have thrown an exception."); + } catch (UninitializedMessageException e) { + assertEquals( + "Message missing required fields: " + + "optional_message.a, " + + "optional_message.b, " + + "optional_message.c, " + + "repeated_message[0].a, " + + "repeated_message[0].b, " + + "repeated_message[0].c, " + + "repeated_message[1].a, " + + "repeated_message[1].b, " + + "repeated_message[1].c", + e.getMessage()); + } + } + + public void testBuildNestedPartial() throws Exception { + // We're mostly testing that no exception is thrown. + TestRequiredForeign message = + TestRequiredForeign.newBuilder() + .setOptionalMessage(TEST_REQUIRED_UNINITIALIZED) + .addRepeatedMessage(TEST_REQUIRED_UNINITIALIZED) + .addRepeatedMessage(TEST_REQUIRED_UNINITIALIZED) + .buildPartial(); + assertFalse(message.isInitialized()); + } + + public void testParseUnititialized() throws Exception { + try { + TestRequired.parseFrom(ByteString.EMPTY); + fail("Should have thrown an exception."); + } catch (InvalidProtocolBufferException e) { + assertEquals("Message missing required fields: a, b, c", e.getMessage()); + } + } + + public void testParseNestedUnititialized() throws Exception { + ByteString data = + TestRequiredForeign.newBuilder() + .setOptionalMessage(TEST_REQUIRED_UNINITIALIZED) + .addRepeatedMessage(TEST_REQUIRED_UNINITIALIZED) + .addRepeatedMessage(TEST_REQUIRED_UNINITIALIZED) + .buildPartial().toByteString(); + + try { + TestRequiredForeign.parseFrom(data); + fail("Should have thrown an exception."); + } catch (InvalidProtocolBufferException e) { + assertEquals( + "Message missing required fields: " + + "optional_message.a, " + + "optional_message.b, " + + "optional_message.c, " + + "repeated_message[0].a, " + + "repeated_message[0].b, " + + "repeated_message[0].c, " + + "repeated_message[1].a, " + + "repeated_message[1].b, " + + "repeated_message[1].c", + e.getMessage()); + } + } + + public void testDynamicUninitializedException() throws Exception { + try { + DynamicMessage.newBuilder(TestRequired.getDescriptor()).build(); + fail("Should have thrown an exception."); + } catch (UninitializedMessageException e) { + assertEquals("Message missing required fields: a, b, c", e.getMessage()); + } + } + + public void testDynamicBuildPartial() throws Exception { + // We're mostly testing that no exception is thrown. + DynamicMessage message = + DynamicMessage.newBuilder(TestRequired.getDescriptor()) + .buildPartial(); + assertFalse(message.isInitialized()); + } + + public void testDynamicParseUnititialized() throws Exception { + try { + Descriptors.Descriptor descriptor = TestRequired.getDescriptor(); + DynamicMessage.parseFrom(descriptor, ByteString.EMPTY); + fail("Should have thrown an exception."); + } catch (InvalidProtocolBufferException e) { + assertEquals("Message missing required fields: a, b, c", e.getMessage()); + } + } + + /** Test reading unset repeated message from DynamicMessage. */ + public void testDynamicRepeatedMessageNull() throws Exception { + Descriptors.Descriptor descriptor = TestRequired.getDescriptor(); + DynamicMessage result = + DynamicMessage.newBuilder(TestAllTypes.getDescriptor()) + .mergeFrom(DynamicMessage.newBuilder(MERGE_SOURCE).build()) + .build(); + + assertTrue(result.getField(result.getDescriptorForType() + .findFieldByName("repeated_foreign_message")) instanceof List<?>); + assertEquals(result.getRepeatedFieldCount(result.getDescriptorForType() + .findFieldByName("repeated_foreign_message")), 0); + } + + /** Test reading repeated message from DynamicMessage. */ + public void testDynamicRepeatedMessageNotNull() throws Exception { + + TestAllTypes REPEATED_NESTED = + TestAllTypes.newBuilder() + .setOptionalInt32(1) + .setOptionalString("foo") + .setOptionalForeignMessage(ForeignMessage.getDefaultInstance()) + .addRepeatedString("bar") + .addRepeatedForeignMessage(ForeignMessage.getDefaultInstance()) + .addRepeatedForeignMessage(ForeignMessage.getDefaultInstance()) + .build(); + Descriptors.Descriptor descriptor = TestRequired.getDescriptor(); + DynamicMessage result = + DynamicMessage.newBuilder(TestAllTypes.getDescriptor()) + .mergeFrom(DynamicMessage.newBuilder(REPEATED_NESTED).build()) + .build(); + + assertTrue(result.getField(result.getDescriptorForType() + .findFieldByName("repeated_foreign_message")) instanceof List<?>); + assertEquals(result.getRepeatedFieldCount(result.getDescriptorForType() + .findFieldByName("repeated_foreign_message")), 2); + } +} diff --git a/java/core/src/test/java/com/google/protobuf/NestedBuildersTest.java b/java/core/src/test/java/com/google/protobuf/NestedBuildersTest.java new file mode 100644 index 00000000..23653126 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/NestedBuildersTest.java @@ -0,0 +1,185 @@ +// 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.Vehicle; +import protobuf_unittest.Wheel; + +import junit.framework.TestCase; + +import java.util.List; +import java.util.ArrayList; + +/** + * Test cases that exercise end-to-end use cases involving + * {@link SingleFieldBuilder} and {@link RepeatedFieldBuilder}. + * + * @author jonp@google.com (Jon Perlow) + */ +public class NestedBuildersTest extends TestCase { + + public void testMessagesAndBuilders() { + Vehicle.Builder vehicleBuilder = Vehicle.newBuilder(); + vehicleBuilder.addWheelBuilder() + .setRadius(4) + .setWidth(1); + vehicleBuilder.addWheelBuilder() + .setRadius(4) + .setWidth(2); + vehicleBuilder.addWheelBuilder() + .setRadius(4) + .setWidth(3); + vehicleBuilder.addWheelBuilder() + .setRadius(4) + .setWidth(4); + vehicleBuilder.getEngineBuilder() + .setLiters(10); + + Vehicle vehicle = vehicleBuilder.build(); + assertEquals(4, vehicle.getWheelCount()); + for (int i = 0; i < 4; i++) { + Wheel wheel = vehicle.getWheel(i); + assertEquals(4, wheel.getRadius()); + assertEquals(i + 1, wheel.getWidth()); + } + assertEquals(10, vehicle.getEngine().getLiters()); + + for (int i = 0; i < 4; i++) { + vehicleBuilder.getWheelBuilder(i) + .setRadius(5) + .setWidth(i + 10); + } + vehicleBuilder.getEngineBuilder().setLiters(20); + + vehicle = vehicleBuilder.build(); + for (int i = 0; i < 4; i++) { + Wheel wheel = vehicle.getWheel(i); + assertEquals(5, wheel.getRadius()); + assertEquals(i + 10, wheel.getWidth()); + } + assertEquals(20, vehicle.getEngine().getLiters()); + assertTrue(vehicle.hasEngine()); + } + + public void testMessagesAreCached() { + Vehicle.Builder vehicleBuilder = Vehicle.newBuilder(); + vehicleBuilder.addWheelBuilder() + .setRadius(1) + .setWidth(2); + vehicleBuilder.addWheelBuilder() + .setRadius(3) + .setWidth(4); + vehicleBuilder.addWheelBuilder() + .setRadius(5) + .setWidth(6); + vehicleBuilder.addWheelBuilder() + .setRadius(7) + .setWidth(8); + + // Make sure messages are cached. + List<Wheel> wheels = new ArrayList<Wheel>(vehicleBuilder.getWheelList()); + for (int i = 0; i < wheels.size(); i++) { + assertSame(wheels.get(i), vehicleBuilder.getWheel(i)); + } + + // Now get builders and check they didn't change. + for (int i = 0; i < wheels.size(); i++) { + vehicleBuilder.getWheel(i); + } + for (int i = 0; i < wheels.size(); i++) { + assertSame(wheels.get(i), vehicleBuilder.getWheel(i)); + } + + // Change just one + vehicleBuilder.getWheelBuilder(3) + .setRadius(20).setWidth(20); + + // Now get wheels and check that only that one changed + for (int i = 0; i < wheels.size(); i++) { + if (i < 3) { + assertSame(wheels.get(i), vehicleBuilder.getWheel(i)); + } else { + assertNotSame(wheels.get(i), vehicleBuilder.getWheel(i)); + } + } + } + + public void testRemove_WithNestedBuilders() { + Vehicle.Builder vehicleBuilder = Vehicle.newBuilder(); + vehicleBuilder.addWheelBuilder() + .setRadius(1) + .setWidth(1); + vehicleBuilder.addWheelBuilder() + .setRadius(2) + .setWidth(2); + vehicleBuilder.removeWheel(0); + + assertEquals(1, vehicleBuilder.getWheelCount()); + assertEquals(2, vehicleBuilder.getWheel(0).getRadius()); + } + + public void testRemove_WithNestedMessages() { + Vehicle.Builder vehicleBuilder = Vehicle.newBuilder(); + vehicleBuilder.addWheel(Wheel.newBuilder() + .setRadius(1) + .setWidth(1)); + vehicleBuilder.addWheel(Wheel.newBuilder() + .setRadius(2) + .setWidth(2)); + vehicleBuilder.removeWheel(0); + + assertEquals(1, vehicleBuilder.getWheelCount()); + assertEquals(2, vehicleBuilder.getWheel(0).getRadius()); + } + + public void testMerge() { + Vehicle vehicle1 = Vehicle.newBuilder() + .addWheel(Wheel.newBuilder().setRadius(1).build()) + .addWheel(Wheel.newBuilder().setRadius(2).build()) + .build(); + + Vehicle vehicle2 = Vehicle.newBuilder() + .mergeFrom(vehicle1) + .build(); + // List should be the same -- no allocation + assertSame(vehicle1.getWheelList(), vehicle2.getWheelList()); + + Vehicle vehicle3 = vehicle1.toBuilder().build(); + assertSame(vehicle1.getWheelList(), vehicle3.getWheelList()); + } + + public void testGettingBuilderMarksFieldAsHaving() { + Vehicle.Builder vehicleBuilder = Vehicle.newBuilder(); + vehicleBuilder.getEngineBuilder(); + Vehicle vehicle = vehicleBuilder.buildPartial(); + assertTrue(vehicle.hasEngine()); + } +} diff --git a/java/core/src/test/java/com/google/protobuf/NioByteStringTest.java b/java/core/src/test/java/com/google/protobuf/NioByteStringTest.java new file mode 100644 index 00000000..0679937f --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/NioByteStringTest.java @@ -0,0 +1,546 @@ +// 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 static com.google.protobuf.Internal.UTF_8; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.UnsupportedEncodingException; +import java.nio.BufferOverflowException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.List; +import java.util.NoSuchElementException; + +/** + * Tests for {@link NioByteString}. + */ +public class NioByteStringTest extends TestCase { + private static final ByteString EMPTY = UnsafeByteStrings.unsafeWrap( + ByteBuffer.wrap(new byte[0])); + private static final String CLASSNAME = NioByteString.class.getSimpleName(); + private static final byte[] BYTES = ByteStringTest.getTestBytes(1234, 11337766L); + private static final int EXPECTED_HASH = new LiteralByteString(BYTES).hashCode(); + private static final ByteBuffer BUFFER = ByteBuffer.wrap(BYTES.clone()); + private static final ByteString TEST_STRING = UnsafeByteStrings.unsafeWrap(BUFFER); + + public void testExpectedType() { + String actualClassName = getActualClassName(TEST_STRING); + assertEquals(CLASSNAME + " should match type exactly", CLASSNAME, actualClassName); + } + + protected String getActualClassName(Object object) { + String actualClassName = object.getClass().getName(); + actualClassName = actualClassName.substring(actualClassName.lastIndexOf('.') + 1); + return actualClassName; + } + + public void testByteAt() { + boolean stillEqual = true; + for (int i = 0; stillEqual && i < BYTES.length; ++i) { + stillEqual = (BYTES[i] == TEST_STRING.byteAt(i)); + } + assertTrue(CLASSNAME + " must capture the right bytes", stillEqual); + } + + public void testByteIterator() { + boolean stillEqual = true; + ByteString.ByteIterator iter = TEST_STRING.iterator(); + for (int i = 0; stillEqual && i < BYTES.length; ++i) { + stillEqual = (iter.hasNext() && BYTES[i] == iter.nextByte()); + } + assertTrue(CLASSNAME + " must capture the right bytes", stillEqual); + assertFalse(CLASSNAME + " must have exhausted the itertor", iter.hasNext()); + + try { + iter.nextByte(); + fail("Should have thrown an exception."); + } catch (NoSuchElementException e) { + // This is success + } + } + + public void testByteIterable() { + boolean stillEqual = true; + int j = 0; + for (byte quantum : TEST_STRING) { + stillEqual = (BYTES[j] == quantum); + ++j; + } + assertTrue(CLASSNAME + " must capture the right bytes as Bytes", stillEqual); + assertEquals(CLASSNAME + " iterable character count", BYTES.length, j); + } + + public void testSize() { + assertEquals(CLASSNAME + " must have the expected size", BYTES.length, + TEST_STRING.size()); + } + + public void testGetTreeDepth() { + assertEquals(CLASSNAME + " must have depth 0", 0, TEST_STRING.getTreeDepth()); + } + + public void testIsBalanced() { + assertTrue(CLASSNAME + " is technically balanced", TEST_STRING.isBalanced()); + } + + public void testCopyTo_ByteArrayOffsetLength() { + int destinationOffset = 50; + int length = 100; + byte[] destination = new byte[destinationOffset + length]; + int sourceOffset = 213; + TEST_STRING.copyTo(destination, sourceOffset, destinationOffset, length); + boolean stillEqual = true; + for (int i = 0; stillEqual && i < length; ++i) { + stillEqual = BYTES[i + sourceOffset] == destination[i + destinationOffset]; + } + assertTrue(CLASSNAME + ".copyTo(4 arg) must give the expected bytes", stillEqual); + } + + public void testCopyTo_ByteArrayOffsetLengthErrors() { + int destinationOffset = 50; + int length = 100; + byte[] destination = new byte[destinationOffset + length]; + + try { + // Copy one too many bytes + TEST_STRING.copyTo(destination, TEST_STRING.size() + 1 - length, + destinationOffset, length); + fail("Should have thrown an exception when copying too many bytes of a " + + CLASSNAME); + } catch (IndexOutOfBoundsException expected) { + // This is success + } + + try { + // Copy with illegal negative sourceOffset + TEST_STRING.copyTo(destination, -1, destinationOffset, length); + fail("Should have thrown an exception when given a negative sourceOffset in " + + CLASSNAME); + } catch (IndexOutOfBoundsException expected) { + // This is success + } + + try { + // Copy with illegal negative destinationOffset + TEST_STRING.copyTo(destination, 0, -1, length); + fail("Should have thrown an exception when given a negative destinationOffset in " + + CLASSNAME); + } catch (IndexOutOfBoundsException expected) { + // This is success + } + + try { + // Copy with illegal negative size + TEST_STRING.copyTo(destination, 0, 0, -1); + fail("Should have thrown an exception when given a negative size in " + + CLASSNAME); + } catch (IndexOutOfBoundsException expected) { + // This is success + } + + try { + // Copy with illegal too-large sourceOffset + TEST_STRING.copyTo(destination, 2 * TEST_STRING.size(), 0, length); + fail("Should have thrown an exception when the destinationOffset is too large in " + + CLASSNAME); + } catch (IndexOutOfBoundsException expected) { + // This is success + } + + try { + // Copy with illegal too-large destinationOffset + TEST_STRING.copyTo(destination, 0, 2 * destination.length, length); + fail("Should have thrown an exception when the destinationOffset is too large in " + + CLASSNAME); + } catch (IndexOutOfBoundsException expected) { + // This is success + } + } + + public void testCopyTo_ByteBuffer() { + // Same length. + ByteBuffer myBuffer = ByteBuffer.allocate(BYTES.length); + TEST_STRING.copyTo(myBuffer); + myBuffer.flip(); + assertEquals(CLASSNAME + ".copyTo(ByteBuffer) must give back the same bytes", + BUFFER, myBuffer); + + // Target buffer bigger than required. + myBuffer = ByteBuffer.allocate(TEST_STRING.size() + 1); + TEST_STRING.copyTo(myBuffer); + myBuffer.flip(); + assertEquals(BUFFER, myBuffer); + + // Target buffer has no space. + myBuffer = ByteBuffer.allocate(0); + try { + TEST_STRING.copyTo(myBuffer); + fail("Should have thrown an exception when target ByteBuffer has insufficient capacity"); + } catch (BufferOverflowException e) { + // Expected. + } + + // Target buffer too small. + myBuffer = ByteBuffer.allocate(1); + try { + TEST_STRING.copyTo(myBuffer); + fail("Should have thrown an exception when target ByteBuffer has insufficient capacity"); + } catch (BufferOverflowException e) { + // Expected. + } + } + + public void testMarkSupported() { + InputStream stream = TEST_STRING.newInput(); + assertTrue(CLASSNAME + ".newInput() must support marking", stream.markSupported()); + } + + public void testMarkAndReset() throws IOException { + int fraction = TEST_STRING.size() / 3; + + InputStream stream = TEST_STRING.newInput(); + stream.mark(TEST_STRING.size()); // First, mark() the end. + + skipFully(stream, fraction); // Skip a large fraction, but not all. + assertEquals( + CLASSNAME + ": after skipping to the 'middle', half the bytes are available", + (TEST_STRING.size() - fraction), stream.available()); + stream.reset(); + assertEquals( + CLASSNAME + ": after resetting, all bytes are available", + TEST_STRING.size(), stream.available()); + + skipFully(stream, TEST_STRING.size()); // Skip to the end. + assertEquals( + CLASSNAME + ": after skipping to the end, no more bytes are available", + 0, stream.available()); + } + + /** + * Discards {@code n} bytes of data from the input stream. This method + * will block until the full amount has been skipped. Does not close the + * stream. + * <p>Copied from com.google.common.io.ByteStreams to avoid adding dependency. + * + * @param in the input stream to read from + * @param n the number of bytes to skip + * @throws EOFException if this stream reaches the end before skipping all + * the bytes + * @throws IOException if an I/O error occurs, or the stream does not + * support skipping + */ + static void skipFully(InputStream in, long n) throws IOException { + long toSkip = n; + while (n > 0) { + long amt = in.skip(n); + if (amt == 0) { + // Force a blocking read to avoid infinite loop + if (in.read() == -1) { + long skipped = toSkip - n; + throw new EOFException("reached end of stream after skipping " + + skipped + " bytes; " + toSkip + " bytes expected"); + } + n--; + } else { + n -= amt; + } + } + } + + public void testAsReadOnlyByteBuffer() { + ByteBuffer byteBuffer = TEST_STRING.asReadOnlyByteBuffer(); + byte[] roundTripBytes = new byte[BYTES.length]; + assertTrue(byteBuffer.remaining() == BYTES.length); + assertTrue(byteBuffer.isReadOnly()); + byteBuffer.get(roundTripBytes); + assertTrue(CLASSNAME + ".asReadOnlyByteBuffer() must give back the same bytes", + Arrays.equals(BYTES, roundTripBytes)); + } + + public void testAsReadOnlyByteBufferList() { + List<ByteBuffer> byteBuffers = TEST_STRING.asReadOnlyByteBufferList(); + int bytesSeen = 0; + byte[] roundTripBytes = new byte[BYTES.length]; + for (ByteBuffer byteBuffer : byteBuffers) { + int thisLength = byteBuffer.remaining(); + assertTrue(byteBuffer.isReadOnly()); + assertTrue(bytesSeen + thisLength <= BYTES.length); + byteBuffer.get(roundTripBytes, bytesSeen, thisLength); + bytesSeen += thisLength; + } + assertTrue(bytesSeen == BYTES.length); + assertTrue(CLASSNAME + ".asReadOnlyByteBufferTest() must give back the same bytes", + Arrays.equals(BYTES, roundTripBytes)); + } + + public void testToByteArray() { + byte[] roundTripBytes = TEST_STRING.toByteArray(); + assertTrue(CLASSNAME + ".toByteArray() must give back the same bytes", + Arrays.equals(BYTES, roundTripBytes)); + } + + public void testWriteTo() throws IOException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + TEST_STRING.writeTo(bos); + byte[] roundTripBytes = bos.toByteArray(); + assertTrue(CLASSNAME + ".writeTo() must give back the same bytes", + Arrays.equals(BYTES, roundTripBytes)); + } + + public void testNewOutput() throws IOException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ByteString.Output output = ByteString.newOutput(); + TEST_STRING.writeTo(output); + assertEquals("Output Size returns correct result", + output.size(), TEST_STRING.size()); + output.writeTo(bos); + assertTrue("Output.writeTo() must give back the same bytes", + Arrays.equals(BYTES, bos.toByteArray())); + + // write the output stream to itself! This should cause it to double + output.writeTo(output); + assertEquals("Writing an output stream to itself is successful", + TEST_STRING.concat(TEST_STRING), output.toByteString()); + + output.reset(); + assertEquals("Output.reset() resets the output", 0, output.size()); + assertEquals("Output.reset() resets the output", + EMPTY, output.toByteString()); + } + + public void testToString() { + String testString = "I love unicode \u1234\u5678 characters"; + ByteString unicode = forString(testString); + String roundTripString = unicode.toString(UTF_8); + assertEquals(CLASSNAME + " unicode must match", testString, roundTripString); + } + + public void testCharsetToString() { + String testString = "I love unicode \u1234\u5678 characters"; + ByteString unicode = forString(testString); + String roundTripString = unicode.toString(UTF_8); + assertEquals(CLASSNAME + " unicode must match", testString, roundTripString); + } + + public void testToString_returnsCanonicalEmptyString() { + assertSame(CLASSNAME + " must be the same string references", + EMPTY.toString(UTF_8), + UnsafeByteStrings.unsafeWrap(ByteBuffer.wrap(new byte[0])).toString(UTF_8)); + } + + public void testToString_raisesException() { + try { + EMPTY.toString("invalid"); + fail("Should have thrown an exception."); + } catch (UnsupportedEncodingException expected) { + // This is success + } + + try { + TEST_STRING.toString("invalid"); + fail("Should have thrown an exception."); + } catch (UnsupportedEncodingException expected) { + // This is success + } + } + + public void testEquals() { + assertEquals(CLASSNAME + " must not equal null", false, TEST_STRING.equals(null)); + assertEquals(CLASSNAME + " must equal self", TEST_STRING, TEST_STRING); + assertFalse(CLASSNAME + " must not equal the empty string", + TEST_STRING.equals(EMPTY)); + assertEquals(CLASSNAME + " empty strings must be equal", + EMPTY, TEST_STRING.substring(55, 55)); + assertEquals(CLASSNAME + " must equal another string with the same value", + TEST_STRING, UnsafeByteStrings.unsafeWrap(BUFFER)); + + byte[] mungedBytes = mungedBytes(); + assertFalse(CLASSNAME + " must not equal every string with the same length", + TEST_STRING.equals(UnsafeByteStrings.unsafeWrap(ByteBuffer.wrap(mungedBytes)))); + } + + public void testEqualsLiteralByteString() { + ByteString literal = ByteString.copyFrom(BYTES); + assertEquals(CLASSNAME + " must equal LiteralByteString with same value", literal, + TEST_STRING); + assertEquals(CLASSNAME + " must equal LiteralByteString with same value", TEST_STRING, + literal); + assertFalse(CLASSNAME + " must not equal the empty string", + TEST_STRING.equals(ByteString.EMPTY)); + assertEquals(CLASSNAME + " empty strings must be equal", + ByteString.EMPTY, TEST_STRING.substring(55, 55)); + + literal = ByteString.copyFrom(mungedBytes()); + assertFalse(CLASSNAME + " must not equal every LiteralByteString with the same length", + TEST_STRING.equals(literal)); + assertFalse(CLASSNAME + " must not equal every LiteralByteString with the same length", + literal.equals(TEST_STRING)); + } + + public void testEqualsRopeByteString() { + ByteString p1 = ByteString.copyFrom(BYTES, 0, 5); + ByteString p2 = ByteString.copyFrom(BYTES, 5, BYTES.length - 5); + ByteString rope = p1.concat(p2); + + assertEquals(CLASSNAME + " must equal RopeByteString with same value", rope, + TEST_STRING); + assertEquals(CLASSNAME + " must equal RopeByteString with same value", TEST_STRING, + rope); + assertFalse(CLASSNAME + " must not equal the empty string", + TEST_STRING.equals(ByteString.EMPTY.concat(ByteString.EMPTY))); + assertEquals(CLASSNAME + " empty strings must be equal", + ByteString.EMPTY.concat(ByteString.EMPTY), TEST_STRING.substring(55, 55)); + + byte[] mungedBytes = mungedBytes(); + p1 = ByteString.copyFrom(mungedBytes, 0, 5); + p2 = ByteString.copyFrom(mungedBytes, 5, mungedBytes.length - 5); + rope = p1.concat(p2); + assertFalse(CLASSNAME + " must not equal every RopeByteString with the same length", + TEST_STRING.equals(rope)); + assertFalse(CLASSNAME + " must not equal every RopeByteString with the same length", + rope.equals(TEST_STRING)); + } + + private byte[] mungedBytes() { + byte[] mungedBytes = new byte[BYTES.length]; + System.arraycopy(BYTES, 0, mungedBytes, 0, BYTES.length); + mungedBytes[mungedBytes.length - 5] = (byte) (mungedBytes[mungedBytes.length - 5] ^ 0xFF); + return mungedBytes; + } + + public void testHashCode() { + int hash = TEST_STRING.hashCode(); + assertEquals(CLASSNAME + " must have expected hashCode", EXPECTED_HASH, hash); + } + + public void testPeekCachedHashCode() { + ByteString newString = UnsafeByteStrings.unsafeWrap(BUFFER); + assertEquals(CLASSNAME + ".peekCachedHashCode() should return zero at first", 0, + newString.peekCachedHashCode()); + newString.hashCode(); + assertEquals(CLASSNAME + ".peekCachedHashCode should return zero at first", + EXPECTED_HASH, newString.peekCachedHashCode()); + } + + public void testPartialHash() { + // partialHash() is more strenuously tested elsewhere by testing hashes of substrings. + // This test would fail if the expected hash were 1. It's not. + int hash = TEST_STRING.partialHash(TEST_STRING.size(), 0, TEST_STRING.size()); + assertEquals(CLASSNAME + ".partialHash() must yield expected hashCode", + EXPECTED_HASH, hash); + } + + public void testNewInput() throws IOException { + InputStream input = TEST_STRING.newInput(); + assertEquals("InputStream.available() returns correct value", + TEST_STRING.size(), input.available()); + boolean stillEqual = true; + for (byte referenceByte : BYTES) { + int expectedInt = (referenceByte & 0xFF); + stillEqual = (expectedInt == input.read()); + } + assertEquals("InputStream.available() returns correct value", + 0, input.available()); + assertTrue(CLASSNAME + " must give the same bytes from the InputStream", stillEqual); + assertEquals(CLASSNAME + " InputStream must now be exhausted", -1, input.read()); + } + + public void testNewInput_skip() throws IOException { + InputStream input = TEST_STRING.newInput(); + int stringSize = TEST_STRING.size(); + int nearEndIndex = stringSize * 2 / 3; + long skipped1 = input.skip(nearEndIndex); + assertEquals("InputStream.skip()", skipped1, nearEndIndex); + assertEquals("InputStream.available()", + stringSize - skipped1, input.available()); + assertTrue("InputStream.mark() is available", input.markSupported()); + input.mark(0); + assertEquals("InputStream.skip(), read()", + TEST_STRING.byteAt(nearEndIndex) & 0xFF, input.read()); + assertEquals("InputStream.available()", + stringSize - skipped1 - 1, input.available()); + long skipped2 = input.skip(stringSize); + assertEquals("InputStream.skip() incomplete", + skipped2, stringSize - skipped1 - 1); + assertEquals("InputStream.skip(), no more input", 0, input.available()); + assertEquals("InputStream.skip(), no more input", -1, input.read()); + input.reset(); + assertEquals("InputStream.reset() succeded", + stringSize - skipped1, input.available()); + assertEquals("InputStream.reset(), read()", + TEST_STRING.byteAt(nearEndIndex) & 0xFF, input.read()); + } + + public void testNewCodedInput() throws IOException { + CodedInputStream cis = TEST_STRING.newCodedInput(); + byte[] roundTripBytes = cis.readRawBytes(BYTES.length); + assertTrue(CLASSNAME + " must give the same bytes back from the CodedInputStream", + Arrays.equals(BYTES, roundTripBytes)); + assertTrue(CLASSNAME + " CodedInputStream must now be exhausted", cis.isAtEnd()); + } + + /** + * Make sure we keep things simple when concatenating with empty. See also + * {@link ByteStringTest#testConcat_empty()}. + */ + public void testConcat_empty() { + assertSame(CLASSNAME + " concatenated with empty must give " + CLASSNAME, + TEST_STRING.concat(EMPTY), TEST_STRING); + assertSame("empty concatenated with " + CLASSNAME + " must give " + CLASSNAME, + EMPTY.concat(TEST_STRING), TEST_STRING); + } + + public void testJavaSerialization() throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(out); + oos.writeObject(TEST_STRING); + oos.close(); + byte[] pickled = out.toByteArray(); + InputStream in = new ByteArrayInputStream(pickled); + ObjectInputStream ois = new ObjectInputStream(in); + Object o = ois.readObject(); + assertTrue("Didn't get a ByteString back", o instanceof ByteString); + assertEquals("Should get an equal ByteString back", TEST_STRING, o); + } + + private static ByteString forString(String str) { + return UnsafeByteStrings.unsafeWrap(ByteBuffer.wrap(str.getBytes(UTF_8))); + } +} diff --git a/java/core/src/test/java/com/google/protobuf/ParserTest.java b/java/core/src/test/java/com/google/protobuf/ParserTest.java new file mode 100644 index 00000000..5a92bacf --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/ParserTest.java @@ -0,0 +1,377 @@ +// 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.UnittestLite.TestAllTypesLite; +import com.google.protobuf.UnittestLite.TestPackedExtensionsLite; +import com.google.protobuf.UnittestLite.TestParsingMergeLite; +import protobuf_unittest.UnittestOptimizeFor.TestOptimizedForSize; +import protobuf_unittest.UnittestOptimizeFor.TestRequiredOptimizedForSize; +import protobuf_unittest.UnittestOptimizeFor; +import protobuf_unittest.UnittestProto.ForeignMessage; +import protobuf_unittest.UnittestProto.TestAllTypes; +import protobuf_unittest.UnittestProto.TestEmptyMessage; +import protobuf_unittest.UnittestProto.TestParsingMerge; +import protobuf_unittest.UnittestProto.TestRequired; +import protobuf_unittest.UnittestProto; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Unit test for {@link Parser}. + * + * @author liujisi@google.com (Pherl Liu) + */ +public class ParserTest extends TestCase { + public void testGeneratedMessageParserSingleton() throws Exception { + for (int i = 0; i < 10; i++) { + assertEquals(TestAllTypes.parser(), TestUtil.getAllSet().getParserForType()); + } + } + + private void assertRoundTripEquals(MessageLite message, + ExtensionRegistryLite registry) + throws Exception { + final byte[] data = message.toByteArray(); + final int offset = 20; + final int length = data.length; + final int padding = 30; + Parser<? extends MessageLite> parser = message.getParserForType(); + assertMessageEquals(message, parser.parseFrom(data, registry)); + assertMessageEquals(message, parser.parseFrom( + generatePaddingArray(data, offset, padding), + offset, length, registry)); + assertMessageEquals(message, parser.parseFrom( + message.toByteString(), registry)); + assertMessageEquals(message, parser.parseFrom( + new ByteArrayInputStream(data), registry)); + assertMessageEquals(message, parser.parseFrom( + CodedInputStream.newInstance(data), registry)); + } + + @SuppressWarnings("unchecked") + private void assertRoundTripEquals(MessageLite message) throws Exception { + final byte[] data = message.toByteArray(); + final int offset = 20; + final int length = data.length; + final int padding = 30; + + Parser<MessageLite> parser = + (Parser<MessageLite>) message.getParserForType(); + assertMessageEquals(message, parser.parseFrom(data)); + assertMessageEquals(message, parser.parseFrom( + generatePaddingArray(data, offset, padding), + offset, length)); + assertMessageEquals(message, parser.parseFrom(message.toByteString())); + assertMessageEquals(message, parser.parseFrom( + new ByteArrayInputStream(data))); + assertMessageEquals(message, parser.parseFrom( + CodedInputStream.newInstance(data))); + } + + private void assertMessageEquals( + MessageLite expected, MessageLite actual) + throws Exception { + if (expected instanceof Message) { + assertEquals(expected, actual); + } else { + assertEquals(expected.toByteString(), actual.toByteString()); + } + } + + private byte[] generatePaddingArray(byte[] data, int offset, int padding) { + byte[] result = new byte[offset + data.length + padding]; + System.arraycopy(data, 0, result, offset, data.length); + return result; + } + + public void testNormalMessage() throws Exception { + assertRoundTripEquals(TestUtil.getAllSet()); + } + + + public void testParsePartial() throws Exception { + assertParsePartial(TestRequired.parser(), TestRequired.newBuilder().setA(1).buildPartial()); + } + + private <T extends MessageLite> void assertParsePartial( + Parser<T> parser, T partialMessage) throws Exception { + final String errorString = + "Should throw exceptions when the parsed message isn't initialized."; + + // parsePartialFrom should pass. + byte[] data = partialMessage.toByteArray(); + assertEquals(partialMessage, parser.parsePartialFrom(data)); + assertEquals(partialMessage, parser.parsePartialFrom( + partialMessage.toByteString())); + assertEquals(partialMessage, parser.parsePartialFrom( + new ByteArrayInputStream(data))); + assertEquals(partialMessage, parser.parsePartialFrom( + CodedInputStream.newInstance(data))); + + // parseFrom(ByteArray) + try { + parser.parseFrom(partialMessage.toByteArray()); + fail(errorString); + } catch (InvalidProtocolBufferException e) { + // pass. + } + + // parseFrom(ByteString) + try { + parser.parseFrom(partialMessage.toByteString()); + fail(errorString); + } catch (InvalidProtocolBufferException e) { + // pass. + } + + // parseFrom(InputStream) + try { + parser.parseFrom(new ByteArrayInputStream(partialMessage.toByteArray())); + fail(errorString); + } catch (IOException e) { + // pass. + } + + // parseFrom(CodedInputStream) + try { + parser.parseFrom(CodedInputStream.newInstance( + partialMessage.toByteArray())); + fail(errorString); + } catch (IOException e) { + // pass. + } + } + + public void testParseExtensions() throws Exception { + assertRoundTripEquals(TestUtil.getAllExtensionsSet(), + TestUtil.getExtensionRegistry()); + assertRoundTripEquals(TestUtil.getAllLiteExtensionsSet(), + TestUtil.getExtensionRegistryLite()); + } + + public void testParsePacked() throws Exception { + assertRoundTripEquals(TestUtil.getPackedSet()); + assertRoundTripEquals(TestUtil.getPackedExtensionsSet(), + TestUtil.getExtensionRegistry()); + assertRoundTripEquals(TestUtil.getLitePackedExtensionsSet(), + TestUtil.getExtensionRegistryLite()); + } + + public void testParseDelimitedTo() throws Exception { + // Write normal Message. + TestAllTypes normalMessage = TestUtil.getAllSet(); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + normalMessage.writeDelimitedTo(output); + + // Write MessageLite with packed extension fields. + TestPackedExtensionsLite packedMessage = + TestUtil.getLitePackedExtensionsSet(); + packedMessage.writeDelimitedTo(output); + + InputStream input = new ByteArrayInputStream(output.toByteArray()); + assertMessageEquals( + normalMessage, + normalMessage.getParserForType().parseDelimitedFrom(input)); + assertMessageEquals( + packedMessage, + packedMessage.getParserForType().parseDelimitedFrom( + input, TestUtil.getExtensionRegistryLite())); + } + + public void testParseUnknownFields() throws Exception { + // All fields will be treated as unknown fields in emptyMessage. + TestEmptyMessage emptyMessage = + TestEmptyMessage.parser().parseFrom(TestUtil.getAllSet().toByteString()); + assertEquals( + TestUtil.getAllSet().toByteString(), + emptyMessage.toByteString()); + } + + + public void testOptimizeForSize() throws Exception { + TestOptimizedForSize.Builder builder = TestOptimizedForSize.newBuilder(); + builder.setI(12).setMsg(ForeignMessage.newBuilder().setC(34).build()); + builder.setExtension(TestOptimizedForSize.testExtension, 56); + builder.setExtension(TestOptimizedForSize.testExtension2, + TestRequiredOptimizedForSize.newBuilder().setX(78).build()); + + TestOptimizedForSize message = builder.build(); + ExtensionRegistry registry = ExtensionRegistry.newInstance(); + UnittestOptimizeFor.registerAllExtensions(registry); + + assertRoundTripEquals(message, registry); + } + + /** Helper method for {@link #testParsingMerge()}.*/ + private void assertMessageMerged(TestAllTypes allTypes) + throws Exception { + assertEquals(3, allTypes.getOptionalInt32()); + assertEquals(2, allTypes.getOptionalInt64()); + assertEquals("hello", allTypes.getOptionalString()); + } + + /** Helper method for {@link #testParsingMergeLite()}.*/ + private void assertMessageMerged(TestAllTypesLite allTypes) + throws Exception { + assertEquals(3, allTypes.getOptionalInt32()); + assertEquals(2, allTypes.getOptionalInt64()); + assertEquals("hello", allTypes.getOptionalString()); + } + + public void testParsingMerge() throws Exception { + // Build messages. + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TestAllTypes msg1 = builder.setOptionalInt32(1).build(); + builder.clear(); + TestAllTypes msg2 = builder.setOptionalInt64(2).build(); + builder.clear(); + TestAllTypes msg3 = builder.setOptionalInt32(3) + .setOptionalString("hello").build(); + + // Build groups. + TestParsingMerge.RepeatedFieldsGenerator.Group1 optionalG1 = + TestParsingMerge.RepeatedFieldsGenerator.Group1.newBuilder() + .setField1(msg1).build(); + TestParsingMerge.RepeatedFieldsGenerator.Group1 optionalG2 = + TestParsingMerge.RepeatedFieldsGenerator.Group1.newBuilder() + .setField1(msg2).build(); + TestParsingMerge.RepeatedFieldsGenerator.Group1 optionalG3 = + TestParsingMerge.RepeatedFieldsGenerator.Group1.newBuilder() + .setField1(msg3).build(); + TestParsingMerge.RepeatedFieldsGenerator.Group2 repeatedG1 = + TestParsingMerge.RepeatedFieldsGenerator.Group2.newBuilder() + .setField1(msg1).build(); + TestParsingMerge.RepeatedFieldsGenerator.Group2 repeatedG2 = + TestParsingMerge.RepeatedFieldsGenerator.Group2.newBuilder() + .setField1(msg2).build(); + TestParsingMerge.RepeatedFieldsGenerator.Group2 repeatedG3 = + TestParsingMerge.RepeatedFieldsGenerator.Group2.newBuilder() + .setField1(msg3).build(); + + // Assign and serialize RepeatedFieldsGenerator. + ByteString data = TestParsingMerge.RepeatedFieldsGenerator.newBuilder() + .addField1(msg1).addField1(msg2).addField1(msg3) + .addField2(msg1).addField2(msg2).addField2(msg3) + .addField3(msg1).addField3(msg2).addField3(msg3) + .addGroup1(optionalG1).addGroup1(optionalG2).addGroup1(optionalG3) + .addGroup2(repeatedG1).addGroup2(repeatedG2).addGroup2(repeatedG3) + .addExt1(msg1).addExt1(msg2).addExt1(msg3) + .addExt2(msg1).addExt2(msg2).addExt2(msg3) + .build().toByteString(); + + // Parse TestParsingMerge. + ExtensionRegistry registry = ExtensionRegistry.newInstance(); + UnittestProto.registerAllExtensions(registry); + TestParsingMerge parsingMerge = TestParsingMerge.parser().parseFrom(data, registry); + + // Required and optional fields should be merged. + assertMessageMerged(parsingMerge.getRequiredAllTypes()); + assertMessageMerged(parsingMerge.getOptionalAllTypes()); + assertMessageMerged( + parsingMerge.getOptionalGroup().getOptionalGroupAllTypes()); + assertMessageMerged(parsingMerge.getExtension( + TestParsingMerge.optionalExt)); + + // Repeated fields should not be merged. + assertEquals(3, parsingMerge.getRepeatedAllTypesCount()); + assertEquals(3, parsingMerge.getRepeatedGroupCount()); + assertEquals(3, parsingMerge.getExtensionCount( + TestParsingMerge.repeatedExt)); + } + + public void testParsingMergeLite() throws Exception { + // Build messages. + TestAllTypesLite.Builder builder = + TestAllTypesLite.newBuilder(); + TestAllTypesLite msg1 = builder.setOptionalInt32(1).build(); + builder.clear(); + TestAllTypesLite msg2 = builder.setOptionalInt64(2).build(); + builder.clear(); + TestAllTypesLite msg3 = builder.setOptionalInt32(3) + .setOptionalString("hello").build(); + + // Build groups. + TestParsingMergeLite.RepeatedFieldsGenerator.Group1 optionalG1 = + TestParsingMergeLite.RepeatedFieldsGenerator.Group1.newBuilder() + .setField1(msg1).build(); + TestParsingMergeLite.RepeatedFieldsGenerator.Group1 optionalG2 = + TestParsingMergeLite.RepeatedFieldsGenerator.Group1.newBuilder() + .setField1(msg2).build(); + TestParsingMergeLite.RepeatedFieldsGenerator.Group1 optionalG3 = + TestParsingMergeLite.RepeatedFieldsGenerator.Group1.newBuilder() + .setField1(msg3).build(); + TestParsingMergeLite.RepeatedFieldsGenerator.Group2 repeatedG1 = + TestParsingMergeLite.RepeatedFieldsGenerator.Group2.newBuilder() + .setField1(msg1).build(); + TestParsingMergeLite.RepeatedFieldsGenerator.Group2 repeatedG2 = + TestParsingMergeLite.RepeatedFieldsGenerator.Group2.newBuilder() + .setField1(msg2).build(); + TestParsingMergeLite.RepeatedFieldsGenerator.Group2 repeatedG3 = + TestParsingMergeLite.RepeatedFieldsGenerator.Group2.newBuilder() + .setField1(msg3).build(); + + // Assign and serialize RepeatedFieldsGenerator. + ByteString data = TestParsingMergeLite.RepeatedFieldsGenerator.newBuilder() + .addField1(msg1).addField1(msg2).addField1(msg3) + .addField2(msg1).addField2(msg2).addField2(msg3) + .addField3(msg1).addField3(msg2).addField3(msg3) + .addGroup1(optionalG1).addGroup1(optionalG2).addGroup1(optionalG3) + .addGroup2(repeatedG1).addGroup2(repeatedG2).addGroup2(repeatedG3) + .addExt1(msg1).addExt1(msg2).addExt1(msg3) + .addExt2(msg1).addExt2(msg2).addExt2(msg3) + .build().toByteString(); + + // Parse TestParsingMergeLite. + ExtensionRegistry registry = ExtensionRegistry.newInstance(); + UnittestLite.registerAllExtensions(registry); + TestParsingMergeLite parsingMerge = TestParsingMergeLite.parser().parseFrom(data, registry); + + // Required and optional fields should be merged. + assertMessageMerged(parsingMerge.getRequiredAllTypes()); + assertMessageMerged(parsingMerge.getOptionalAllTypes()); + assertMessageMerged( + parsingMerge.getOptionalGroup().getOptionalGroupAllTypes()); + assertMessageMerged(parsingMerge.getExtension( + TestParsingMergeLite.optionalExt)); + + // Repeated fields should not be merged. + assertEquals(3, parsingMerge.getRepeatedAllTypesCount()); + assertEquals(3, parsingMerge.getRepeatedGroupCount()); + assertEquals(3, parsingMerge.getExtensionCount( + TestParsingMergeLite.repeatedExt)); + } +} diff --git a/java/core/src/test/java/com/google/protobuf/ProtobufArrayListTest.java b/java/core/src/test/java/com/google/protobuf/ProtobufArrayListTest.java new file mode 100644 index 00000000..245c3dee --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/ProtobufArrayListTest.java @@ -0,0 +1,303 @@ +// 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 static java.util.Arrays.asList; + +import junit.framework.TestCase; + +import java.util.Collections; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.List; + +/** + * Tests for {@link ProtobufArrayList}. + */ +public class ProtobufArrayListTest extends TestCase { + + private static final ProtobufArrayList<Integer> UNARY_LIST = newImmutableProtoArrayList(1); + private static final ProtobufArrayList<Integer> TERTIARY_LIST = + newImmutableProtoArrayList(1, 2, 3); + + private ProtobufArrayList<Integer> list; + + @Override + protected void setUp() throws Exception { + list = new ProtobufArrayList<Integer>(); + } + + public void testEmptyListReturnsSameInstance() { + assertSame(ProtobufArrayList.emptyList(), ProtobufArrayList.emptyList()); + } + + public void testEmptyListIsImmutable() { + assertImmutable(ProtobufArrayList.<Integer>emptyList()); + } + + public void testCopyConstructor() { + ProtobufArrayList<Integer> copy = new ProtobufArrayList<Integer>(TERTIARY_LIST); + assertEquals(TERTIARY_LIST, copy); + + copy = new ProtobufArrayList<Integer>(IntArrayList.emptyList()); + assertEquals(ProtobufArrayList.emptyList(), copy); + + copy = new ProtobufArrayList<Integer>(asList(1, 2, 3)); + assertEquals(asList(1, 2, 3), copy); + + copy = new ProtobufArrayList<Integer>(Collections.<Integer>emptyList()); + assertEquals(ProtobufArrayList.emptyList(), copy); + } + + public void testModificationWithIteration() { + list.addAll(asList(1, 2, 3, 4)); + Iterator<Integer> iterator = list.iterator(); + assertEquals(4, list.size()); + assertEquals(1, (int) list.get(0)); + assertEquals(1, (int) iterator.next()); + + list.remove(0); + try { + iterator.next(); + fail(); + } catch (ConcurrentModificationException e) { + // expected + } + + iterator = list.iterator(); + list.set(0, 1); + try { + iterator.next(); + fail(); + } catch (ConcurrentModificationException e) { + // expected + } + + iterator = list.iterator(); + list.add(0, 0); + try { + iterator.next(); + fail(); + } catch (ConcurrentModificationException e) { + // expected + } + } + + public void testMakeImmutable() { + list.add(2); + list.add(4); + list.add(6); + list.add(8); + list.makeImmutable(); + assertImmutable(list); + } + + public void testRemove() { + list.add(2); + list.add(4); + list.add(6); + + list.remove(1); + assertEquals(asList(2, 6), list); + + list.remove(1); + assertEquals(asList(2), list); + + list.remove(0); + assertEquals(asList(), list); + } + + public void testGet() { + list.add(2); + list.add(6); + + assertEquals(2, (int) list.get(0)); + assertEquals(6, (int) list.get(1)); + } + + public void testSet() { + list.add(2); + list.add(6); + + list.set(0, 1); + assertEquals(1, (int) list.get(0)); + list.set(1, 2); + assertEquals(2, (int) list.get(1)); + } + + private void assertImmutable(List<Integer> list) { + if (list.contains(1)) { + throw new RuntimeException("Cannot test the immutability of lists that contain 1."); + } + + try { + list.add(1); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.add(0, 1); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(Collections.<Integer>emptyList()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(Collections.singletonList(1)); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(new ProtobufArrayList<Integer>()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(UNARY_LIST); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(0, Collections.singleton(1)); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(0, UNARY_LIST); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.addAll(0, Collections.<Integer>emptyList()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.clear(); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.remove(1); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.remove(new Object()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.removeAll(Collections.emptyList()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.removeAll(Collections.singleton(1)); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.removeAll(UNARY_LIST); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.retainAll(Collections.emptyList()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.retainAll(Collections.singleton(1)); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.retainAll(UNARY_LIST); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + list.set(0, 0); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + } + + private static ProtobufArrayList<Integer> newImmutableProtoArrayList(int... elements) { + ProtobufArrayList<Integer> list = new ProtobufArrayList<Integer>(); + for (int element : elements) { + list.add(element); + } + list.makeImmutable(); + return list; + } +} diff --git a/java/core/src/test/java/com/google/protobuf/RepeatedFieldBuilderTest.java b/java/core/src/test/java/com/google/protobuf/RepeatedFieldBuilderTest.java new file mode 100644 index 00000000..49d52321 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/RepeatedFieldBuilderTest.java @@ -0,0 +1,190 @@ +// 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.UnittestProto.TestAllTypes; +import protobuf_unittest.UnittestProto.TestAllTypesOrBuilder; + +import junit.framework.TestCase; + +import java.util.Collections; +import java.util.List; + +/** + * Tests for {@link RepeatedFieldBuilder}. This tests basic functionality. + * More extensive testing is provided via other tests that exercise the + * builder. + * + * @author jonp@google.com (Jon Perlow) + */ +public class RepeatedFieldBuilderTest extends TestCase { + + public void testBasicUse() { + TestUtil.MockBuilderParent mockParent = new TestUtil.MockBuilderParent(); + RepeatedFieldBuilder<TestAllTypes, TestAllTypes.Builder, + TestAllTypesOrBuilder> builder = newRepeatedFieldBuilder(mockParent); + builder.addMessage(TestAllTypes.newBuilder().setOptionalInt32(0).build()); + builder.addMessage(TestAllTypes.newBuilder().setOptionalInt32(1).build()); + assertEquals(0, builder.getMessage(0).getOptionalInt32()); + assertEquals(1, builder.getMessage(1).getOptionalInt32()); + + List<TestAllTypes> list = builder.build(); + assertEquals(2, list.size()); + assertEquals(0, list.get(0).getOptionalInt32()); + assertEquals(1, list.get(1).getOptionalInt32()); + assertIsUnmodifiable(list); + + // Make sure it doesn't change. + List<TestAllTypes> list2 = builder.build(); + assertSame(list, list2); + assertEquals(0, mockParent.getInvalidationCount()); + } + + public void testGoingBackAndForth() { + TestUtil.MockBuilderParent mockParent = new TestUtil.MockBuilderParent(); + RepeatedFieldBuilder<TestAllTypes, TestAllTypes.Builder, + TestAllTypesOrBuilder> builder = newRepeatedFieldBuilder(mockParent); + builder.addMessage(TestAllTypes.newBuilder().setOptionalInt32(0).build()); + builder.addMessage(TestAllTypes.newBuilder().setOptionalInt32(1).build()); + assertEquals(0, builder.getMessage(0).getOptionalInt32()); + assertEquals(1, builder.getMessage(1).getOptionalInt32()); + + // Convert to list + List<TestAllTypes> list = builder.build(); + assertEquals(2, list.size()); + assertEquals(0, list.get(0).getOptionalInt32()); + assertEquals(1, list.get(1).getOptionalInt32()); + assertIsUnmodifiable(list); + + // Update 0th item + assertEquals(0, mockParent.getInvalidationCount()); + builder.getBuilder(0).setOptionalString("foo"); + assertEquals(1, mockParent.getInvalidationCount()); + list = builder.build(); + assertEquals(2, list.size()); + assertEquals(0, list.get(0).getOptionalInt32()); + assertEquals("foo", list.get(0).getOptionalString()); + assertEquals(1, list.get(1).getOptionalInt32()); + assertIsUnmodifiable(list); + assertEquals(1, mockParent.getInvalidationCount()); + } + + public void testVariousMethods() { + TestUtil.MockBuilderParent mockParent = new TestUtil.MockBuilderParent(); + RepeatedFieldBuilder<TestAllTypes, TestAllTypes.Builder, + TestAllTypesOrBuilder> builder = newRepeatedFieldBuilder(mockParent); + builder.addMessage(TestAllTypes.newBuilder().setOptionalInt32(1).build()); + builder.addMessage(TestAllTypes.newBuilder().setOptionalInt32(2).build()); + builder.addBuilder(0, TestAllTypes.getDefaultInstance()) + .setOptionalInt32(0); + builder.addBuilder(TestAllTypes.getDefaultInstance()).setOptionalInt32(3); + + assertEquals(0, builder.getMessage(0).getOptionalInt32()); + assertEquals(1, builder.getMessage(1).getOptionalInt32()); + assertEquals(2, builder.getMessage(2).getOptionalInt32()); + assertEquals(3, builder.getMessage(3).getOptionalInt32()); + + assertEquals(0, mockParent.getInvalidationCount()); + List<TestAllTypes> messages = builder.build(); + assertEquals(4, messages.size()); + assertSame(messages, builder.build()); // expect same list + + // Remove a message. + builder.remove(2); + assertEquals(1, mockParent.getInvalidationCount()); + assertEquals(3, builder.getCount()); + assertEquals(0, builder.getMessage(0).getOptionalInt32()); + assertEquals(1, builder.getMessage(1).getOptionalInt32()); + assertEquals(3, builder.getMessage(2).getOptionalInt32()); + + // Remove a builder. + builder.remove(0); + assertEquals(1, mockParent.getInvalidationCount()); + assertEquals(2, builder.getCount()); + assertEquals(1, builder.getMessage(0).getOptionalInt32()); + assertEquals(3, builder.getMessage(1).getOptionalInt32()); + + // Test clear. + builder.clear(); + assertEquals(1, mockParent.getInvalidationCount()); + assertEquals(0, builder.getCount()); + assertTrue(builder.isEmpty()); + } + + public void testLists() { + TestUtil.MockBuilderParent mockParent = new TestUtil.MockBuilderParent(); + RepeatedFieldBuilder<TestAllTypes, TestAllTypes.Builder, + TestAllTypesOrBuilder> builder = newRepeatedFieldBuilder(mockParent); + builder.addMessage(TestAllTypes.newBuilder().setOptionalInt32(1).build()); + builder.addMessage(0, + TestAllTypes.newBuilder().setOptionalInt32(0).build()); + assertEquals(0, builder.getMessage(0).getOptionalInt32()); + assertEquals(1, builder.getMessage(1).getOptionalInt32()); + + // Use list of builders. + List<TestAllTypes.Builder> builders = builder.getBuilderList(); + assertEquals(0, builders.get(0).getOptionalInt32()); + assertEquals(1, builders.get(1).getOptionalInt32()); + builders.get(0).setOptionalInt32(10); + builders.get(1).setOptionalInt32(11); + + // Use list of protos + List<TestAllTypes> protos = builder.getMessageList(); + assertEquals(10, protos.get(0).getOptionalInt32()); + assertEquals(11, protos.get(1).getOptionalInt32()); + + // Add an item to the builders and verify it's updated in both + builder.addMessage(TestAllTypes.newBuilder().setOptionalInt32(12).build()); + assertEquals(3, builders.size()); + assertEquals(3, protos.size()); + } + + private void assertIsUnmodifiable(List<?> list) { + if (list == Collections.emptyList()) { + // OKAY -- Need to check this b/c EmptyList allows you to call clear. + } else { + try { + list.clear(); + fail("List wasn't immutable"); + } catch (UnsupportedOperationException e) { + // good + } + } + } + + private RepeatedFieldBuilder<TestAllTypes, TestAllTypes.Builder, + TestAllTypesOrBuilder> + newRepeatedFieldBuilder(GeneratedMessage.BuilderParent parent) { + return new RepeatedFieldBuilder<TestAllTypes, TestAllTypes.Builder, + TestAllTypesOrBuilder>(Collections.<TestAllTypes>emptyList(), false, + parent, false); + } +} diff --git a/java/core/src/test/java/com/google/protobuf/RopeByteStringSubstringTest.java b/java/core/src/test/java/com/google/protobuf/RopeByteStringSubstringTest.java new file mode 100644 index 00000000..dc56f2e9 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/RopeByteStringSubstringTest.java @@ -0,0 +1,127 @@ +// 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.UnsupportedEncodingException; +import java.util.Iterator; + +/** + * This class tests {@link RopeByteString#substring(int, int)} by inheriting the tests from + * {@link LiteralByteStringTest}. Only a couple of methods are overridden. + * + * @author carlanton@google.com (Carl Haverl) + */ +public class RopeByteStringSubstringTest extends LiteralByteStringTest { + + @Override + protected void setUp() throws Exception { + classUnderTest = "RopeByteString"; + byte[] sourceBytes = ByteStringTest.getTestBytes(22341, 22337766L); + Iterator<ByteString> iter = ByteStringTest.makeConcretePieces(sourceBytes).iterator(); + ByteString sourceString = iter.next(); + while (iter.hasNext()) { + sourceString = sourceString.concat(iter.next()); + } + + int from = 1130; + int to = sourceBytes.length - 5555; + stringUnderTest = sourceString.substring(from, to); + referenceBytes = new byte[to - from]; + System.arraycopy(sourceBytes, from, referenceBytes, 0, to - from); + expectedHashCode = -1259260680; + } + + @Override + public void testGetTreeDepth() { + assertEquals(classUnderTest + " must have the expected tree depth", + 3, stringUnderTest.getTreeDepth()); + } + + @Override + public void testToString() throws UnsupportedEncodingException { + String sourceString = "I love unicode \u1234\u5678 characters"; + ByteString sourceByteString = ByteString.copyFromUtf8(sourceString); + int copies = 250; + + // By building the RopeByteString by concatenating, this is actually a fairly strenuous test. + StringBuilder builder = new StringBuilder(copies * sourceString.length()); + ByteString unicode = ByteString.EMPTY; + for (int i = 0; i < copies; ++i) { + builder.append(sourceString); + unicode = RopeByteString.concatenate(unicode, sourceByteString); + } + String testString = builder.toString(); + + // Do the substring part + testString = testString.substring(2, testString.length() - 6); + unicode = unicode.substring(2, unicode.size() - 6); + + assertEquals(classUnderTest + " from string must have the expected type", + classUnderTest, getActualClassName(unicode)); + String roundTripString = unicode.toString(UTF_8); + assertEquals(classUnderTest + " unicode bytes must match", + testString, roundTripString); + ByteString flatString = ByteString.copyFromUtf8(testString); + assertEquals(classUnderTest + " string must equal the flat string", flatString, unicode); + assertEquals(classUnderTest + " string must must have same hashCode as the flat string", + flatString.hashCode(), unicode.hashCode()); + } + + @Override + public void testCharsetToString() { + String sourceString = "I love unicode \u1234\u5678 characters"; + ByteString sourceByteString = ByteString.copyFromUtf8(sourceString); + int copies = 250; + + // By building the RopeByteString by concatenating, this is actually a fairly strenuous test. + StringBuilder builder = new StringBuilder(copies * sourceString.length()); + ByteString unicode = ByteString.EMPTY; + for (int i = 0; i < copies; ++i) { + builder.append(sourceString); + unicode = RopeByteString.concatenate(unicode, sourceByteString); + } + String testString = builder.toString(); + + // Do the substring part + testString = testString.substring(2, testString.length() - 6); + unicode = unicode.substring(2, unicode.size() - 6); + + assertEquals(classUnderTest + " from string must have the expected type", + classUnderTest, getActualClassName(unicode)); + String roundTripString = unicode.toString(Internal.UTF_8); + assertEquals(classUnderTest + " unicode bytes must match", + testString, roundTripString); + ByteString flatString = ByteString.copyFromUtf8(testString); + assertEquals(classUnderTest + " string must equal the flat string", flatString, unicode); + assertEquals(classUnderTest + " string must must have same hashCode as the flat string", + flatString.hashCode(), unicode.hashCode()); + } +} diff --git a/java/core/src/test/java/com/google/protobuf/RopeByteStringTest.java b/java/core/src/test/java/com/google/protobuf/RopeByteStringTest.java new file mode 100644 index 00000000..4ec3a409 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/RopeByteStringTest.java @@ -0,0 +1,189 @@ +// 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.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.UnsupportedEncodingException; +import java.util.Arrays; +import java.util.Iterator; + +/** + * This class tests {@link RopeByteString} by inheriting the tests from + * {@link LiteralByteStringTest}. Only a couple of methods are overridden. + * + * <p>A full test of the result of {@link RopeByteString#substring(int, int)} is found in the + * separate class {@link RopeByteStringSubstringTest}. + * + * @author carlanton@google.com (Carl Haverl) + */ +public class RopeByteStringTest extends LiteralByteStringTest { + + @Override + protected void setUp() throws Exception { + classUnderTest = "RopeByteString"; + referenceBytes = ByteStringTest.getTestBytes(22341, 22337766L); + Iterator<ByteString> iter = ByteStringTest.makeConcretePieces(referenceBytes).iterator(); + stringUnderTest = iter.next(); + while (iter.hasNext()) { + stringUnderTest = stringUnderTest.concat(iter.next()); + } + expectedHashCode = -1214197238; + } + + @Override + public void testGetTreeDepth() { + assertEquals(classUnderTest + " must have the expected tree depth", + 4, stringUnderTest.getTreeDepth()); + } + + public void testBalance() { + int numberOfPieces = 10000; + int pieceSize = 64; + byte[] testBytes = ByteStringTest.getTestBytes(numberOfPieces * pieceSize, 113377L); + + // Build up a big ByteString from smaller pieces to force a rebalance + ByteString concatenated = ByteString.EMPTY; + for (int i = 0; i < numberOfPieces; ++i) { + concatenated = concatenated.concat(ByteString.copyFrom(testBytes, i * pieceSize, pieceSize)); + } + + assertEquals(classUnderTest + " from string must have the expected type", + classUnderTest, getActualClassName(concatenated)); + assertTrue(classUnderTest + " underlying bytes must match after balancing", + Arrays.equals(testBytes, concatenated.toByteArray())); + ByteString testString = ByteString.copyFrom(testBytes); + assertTrue(classUnderTest + " balanced string must equal flat string", + concatenated.equals(testString)); + assertTrue(classUnderTest + " flat string must equal balanced string", + testString.equals(concatenated)); + assertEquals(classUnderTest + " balanced string must have same hash code as flat string", + testString.hashCode(), concatenated.hashCode()); + } + + @Override + public void testToString() throws UnsupportedEncodingException { + String sourceString = "I love unicode \u1234\u5678 characters"; + ByteString sourceByteString = ByteString.copyFromUtf8(sourceString); + int copies = 250; + + // By building the RopeByteString by concatenating, this is actually a fairly strenuous test. + StringBuilder builder = new StringBuilder(copies * sourceString.length()); + ByteString unicode = ByteString.EMPTY; + for (int i = 0; i < copies; ++i) { + builder.append(sourceString); + unicode = RopeByteString.concatenate(unicode, sourceByteString); + } + String testString = builder.toString(); + + assertEquals(classUnderTest + " from string must have the expected type", + classUnderTest, getActualClassName(unicode)); + String roundTripString = unicode.toString(UTF_8); + assertEquals(classUnderTest + " unicode bytes must match", + testString, roundTripString); + ByteString flatString = ByteString.copyFromUtf8(testString); + assertEquals(classUnderTest + " string must equal the flat string", flatString, unicode); + assertEquals(classUnderTest + " string must must have same hashCode as the flat string", + flatString.hashCode(), unicode.hashCode()); + } + + @Override + public void testCharsetToString() { + String sourceString = "I love unicode \u1234\u5678 characters"; + ByteString sourceByteString = ByteString.copyFromUtf8(sourceString); + int copies = 250; + + // By building the RopeByteString by concatenating, this is actually a fairly strenuous test. + StringBuilder builder = new StringBuilder(copies * sourceString.length()); + ByteString unicode = ByteString.EMPTY; + for (int i = 0; i < copies; ++i) { + builder.append(sourceString); + unicode = RopeByteString.concatenate(unicode, sourceByteString); + } + String testString = builder.toString(); + + assertEquals(classUnderTest + " from string must have the expected type", + classUnderTest, getActualClassName(unicode)); + String roundTripString = unicode.toString(Internal.UTF_8); + assertEquals(classUnderTest + " unicode bytes must match", + testString, roundTripString); + ByteString flatString = ByteString.copyFromUtf8(testString); + assertEquals(classUnderTest + " string must equal the flat string", flatString, unicode); + assertEquals(classUnderTest + " string must must have same hashCode as the flat string", + flatString.hashCode(), unicode.hashCode()); + } + + @Override + public void testToString_returnsCanonicalEmptyString() { + RopeByteString ropeByteString = + RopeByteString.newInstanceForTest(ByteString.EMPTY, ByteString.EMPTY); + assertSame(classUnderTest + " must be the same string references", + ByteString.EMPTY.toString(Internal.UTF_8), ropeByteString.toString(Internal.UTF_8)); + } + + @Override + public void testToString_raisesException() { + try { + ByteString byteString = + RopeByteString.newInstanceForTest(ByteString.EMPTY, ByteString.EMPTY); + byteString.toString("invalid"); + fail("Should have thrown an exception."); + } catch (UnsupportedEncodingException expected) { + // This is success + } + + try { + ByteString byteString = RopeByteString.concatenate(ByteString.copyFromUtf8("foo"), + ByteString.copyFromUtf8("bar")); + byteString.toString("invalid"); + fail("Should have thrown an exception."); + } catch (UnsupportedEncodingException expected) { + // This is success + } + } + + @Override + public void testJavaSerialization() throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(out); + oos.writeObject(stringUnderTest); + oos.close(); + byte[] pickled = out.toByteArray(); + InputStream in = new ByteArrayInputStream(pickled); + ObjectInputStream ois = new ObjectInputStream(in); + Object o = ois.readObject(); + assertTrue("Didn't get a ByteString back", o instanceof ByteString); + assertEquals("Should get an equal ByteString back", stringUnderTest, o); + } +} diff --git a/java/core/src/test/java/com/google/protobuf/ServiceTest.java b/java/core/src/test/java/com/google/protobuf/ServiceTest.java new file mode 100644 index 00000000..ff980d66 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/ServiceTest.java @@ -0,0 +1,320 @@ +// 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.FileDescriptor; +import com.google.protobuf.Descriptors.MethodDescriptor; +import google.protobuf.no_generic_services_test.UnittestNoGenericServices; +import protobuf_unittest.MessageWithNoOuter; +import protobuf_unittest.ServiceWithNoOuter; +import protobuf_unittest.UnittestProto.TestAllTypes; +import protobuf_unittest.UnittestProto.TestService; +import protobuf_unittest.UnittestProto.FooRequest; +import protobuf_unittest.UnittestProto.FooResponse; +import protobuf_unittest.UnittestProto.BarRequest; +import protobuf_unittest.UnittestProto.BarResponse; + +import org.easymock.classextension.EasyMock; +import org.easymock.classextension.IMocksControl; +import org.easymock.IArgumentMatcher; + +import java.util.HashSet; +import java.util.Set; + +import junit.framework.TestCase; + +/** + * Tests services and stubs. + * + * @author kenton@google.com Kenton Varda + */ +public class ServiceTest extends TestCase { + private IMocksControl control; + private RpcController mockController; + + private final Descriptors.MethodDescriptor fooDescriptor = + TestService.getDescriptor().getMethods().get(0); + private final Descriptors.MethodDescriptor barDescriptor = + TestService.getDescriptor().getMethods().get(1); + + @Override + protected void setUp() throws Exception { + super.setUp(); + control = EasyMock.createStrictControl(); + mockController = control.createMock(RpcController.class); + } + + // ================================================================= + + /** Tests Service.callMethod(). */ + public void testCallMethod() throws Exception { + FooRequest fooRequest = FooRequest.newBuilder().build(); + BarRequest barRequest = BarRequest.newBuilder().build(); + MockCallback<Message> fooCallback = new MockCallback<Message>(); + MockCallback<Message> barCallback = new MockCallback<Message>(); + TestService mockService = control.createMock(TestService.class); + + mockService.foo(EasyMock.same(mockController), EasyMock.same(fooRequest), + this.<FooResponse>wrapsCallback(fooCallback)); + mockService.bar(EasyMock.same(mockController), EasyMock.same(barRequest), + this.<BarResponse>wrapsCallback(barCallback)); + control.replay(); + + mockService.callMethod(fooDescriptor, mockController, + fooRequest, fooCallback); + mockService.callMethod(barDescriptor, mockController, + barRequest, barCallback); + control.verify(); + } + + /** Tests Service.get{Request,Response}Prototype(). */ + public void testGetPrototype() throws Exception { + TestService mockService = control.createMock(TestService.class); + + assertSame(mockService.getRequestPrototype(fooDescriptor), + FooRequest.getDefaultInstance()); + assertSame(mockService.getResponsePrototype(fooDescriptor), + FooResponse.getDefaultInstance()); + assertSame(mockService.getRequestPrototype(barDescriptor), + BarRequest.getDefaultInstance()); + assertSame(mockService.getResponsePrototype(barDescriptor), + BarResponse.getDefaultInstance()); + } + + /** Tests generated stubs. */ + public void testStub() throws Exception { + FooRequest fooRequest = FooRequest.newBuilder().build(); + BarRequest barRequest = BarRequest.newBuilder().build(); + MockCallback<FooResponse> fooCallback = new MockCallback<FooResponse>(); + MockCallback<BarResponse> barCallback = new MockCallback<BarResponse>(); + RpcChannel mockChannel = control.createMock(RpcChannel.class); + TestService stub = TestService.newStub(mockChannel); + + mockChannel.callMethod( + EasyMock.same(fooDescriptor), + EasyMock.same(mockController), + EasyMock.same(fooRequest), + EasyMock.same(FooResponse.getDefaultInstance()), + this.<Message>wrapsCallback(fooCallback)); + mockChannel.callMethod( + EasyMock.same(barDescriptor), + EasyMock.same(mockController), + EasyMock.same(barRequest), + EasyMock.same(BarResponse.getDefaultInstance()), + this.<Message>wrapsCallback(barCallback)); + control.replay(); + + stub.foo(mockController, fooRequest, fooCallback); + stub.bar(mockController, barRequest, barCallback); + control.verify(); + } + + /** Tests generated blocking stubs. */ + public void testBlockingStub() throws Exception { + FooRequest fooRequest = FooRequest.newBuilder().build(); + BarRequest barRequest = BarRequest.newBuilder().build(); + BlockingRpcChannel mockChannel = + control.createMock(BlockingRpcChannel.class); + TestService.BlockingInterface stub = + TestService.newBlockingStub(mockChannel); + + FooResponse fooResponse = FooResponse.newBuilder().build(); + BarResponse barResponse = BarResponse.newBuilder().build(); + + EasyMock.expect(mockChannel.callBlockingMethod( + EasyMock.same(fooDescriptor), + EasyMock.same(mockController), + EasyMock.same(fooRequest), + EasyMock.same(FooResponse.getDefaultInstance()))).andReturn(fooResponse); + EasyMock.expect(mockChannel.callBlockingMethod( + EasyMock.same(barDescriptor), + EasyMock.same(mockController), + EasyMock.same(barRequest), + EasyMock.same(BarResponse.getDefaultInstance()))).andReturn(barResponse); + control.replay(); + + assertSame(fooResponse, stub.foo(mockController, fooRequest)); + assertSame(barResponse, stub.bar(mockController, barRequest)); + control.verify(); + } + + public void testNewReflectiveService() { + ServiceWithNoOuter.Interface impl = + control.createMock(ServiceWithNoOuter.Interface.class); + RpcController controller = control.createMock(RpcController.class); + Service service = ServiceWithNoOuter.newReflectiveService(impl); + + MethodDescriptor fooMethod = + ServiceWithNoOuter.getDescriptor().findMethodByName("Foo"); + MessageWithNoOuter request = MessageWithNoOuter.getDefaultInstance(); + RpcCallback<Message> callback = new RpcCallback<Message>() { + public void run(Message parameter) { + // No reason this should be run. + fail(); + } + }; + RpcCallback<TestAllTypes> specializedCallback = + RpcUtil.specializeCallback(callback); + + impl.foo(EasyMock.same(controller), EasyMock.same(request), + EasyMock.same(specializedCallback)); + EasyMock.expectLastCall(); + + control.replay(); + + service.callMethod(fooMethod, controller, request, callback); + + control.verify(); + } + + public void testNewReflectiveBlockingService() throws ServiceException { + ServiceWithNoOuter.BlockingInterface impl = + control.createMock(ServiceWithNoOuter.BlockingInterface.class); + RpcController controller = control.createMock(RpcController.class); + BlockingService service = + ServiceWithNoOuter.newReflectiveBlockingService(impl); + + MethodDescriptor fooMethod = + ServiceWithNoOuter.getDescriptor().findMethodByName("Foo"); + MessageWithNoOuter request = MessageWithNoOuter.getDefaultInstance(); + + TestAllTypes expectedResponse = TestAllTypes.getDefaultInstance(); + EasyMock.expect(impl.foo(EasyMock.same(controller), EasyMock.same(request))) + .andReturn(expectedResponse); + + control.replay(); + + Message response = + service.callBlockingMethod(fooMethod, controller, request); + assertEquals(expectedResponse, response); + + control.verify(); + } + + public void testNoGenericServices() throws Exception { + // Non-services should be usable. + UnittestNoGenericServices.TestMessage message = + UnittestNoGenericServices.TestMessage.newBuilder() + .setA(123) + .setExtension(UnittestNoGenericServices.testExtension, 456) + .build(); + assertEquals(123, message.getA()); + assertEquals(1, UnittestNoGenericServices.TestEnum.FOO.getNumber()); + + // Build a list of the class names nested in UnittestNoGenericServices. + String outerName = "google.protobuf.no_generic_services_test." + + "UnittestNoGenericServices"; + Class<?> outerClass = Class.forName(outerName); + + Set<String> innerClassNames = new HashSet<String>(); + for (Class<?> innerClass : outerClass.getClasses()) { + String fullName = innerClass.getName(); + // Figure out the unqualified name of the inner class. + // Note: Surprisingly, the full name of an inner class will be separated + // from the outer class name by a '$' rather than a '.'. This is not + // mentioned in the documentation for java.lang.Class. I don't want to + // make assumptions, so I'm just going to accept any character as the + // separator. + assertTrue(fullName.startsWith(outerName)); + + if (!Service.class.isAssignableFrom(innerClass) && + !Message.class.isAssignableFrom(innerClass) && + !ProtocolMessageEnum.class.isAssignableFrom(innerClass)) { + // Ignore any classes not generated by the base code generator. + continue; + } + + innerClassNames.add(fullName.substring(outerName.length() + 1)); + } + + // No service class should have been generated. + assertTrue(innerClassNames.contains("TestMessage")); + assertTrue(innerClassNames.contains("TestEnum")); + assertFalse(innerClassNames.contains("TestService")); + + // But descriptors are there. + FileDescriptor file = UnittestNoGenericServices.getDescriptor(); + assertEquals(1, file.getServices().size()); + assertEquals("TestService", file.getServices().get(0).getName()); + assertEquals(1, file.getServices().get(0).getMethods().size()); + assertEquals("Foo", + file.getServices().get(0).getMethods().get(0).getName()); + } + + // ================================================================= + + /** + * wrapsCallback() is an EasyMock argument predicate. wrapsCallback(c) + * matches a callback if calling that callback causes c to be called. + * In other words, c wraps the given callback. + */ + private <Type extends Message> RpcCallback<Type> wrapsCallback( + MockCallback<?> callback) { + EasyMock.reportMatcher(new WrapsCallback(callback)); + return null; + } + + /** The parameter to wrapsCallback() must be a MockCallback. */ + private static class MockCallback<Type extends Message> + implements RpcCallback<Type> { + private boolean called = false; + + public boolean isCalled() { return called; } + + public void reset() { called = false; } + public void run(Type message) { called = true; } + } + + /** Implementation of the wrapsCallback() argument matcher. */ + private static class WrapsCallback implements IArgumentMatcher { + private MockCallback<?> callback; + + public WrapsCallback(MockCallback<?> callback) { + this.callback = callback; + } + + @SuppressWarnings("unchecked") + public boolean matches(Object actual) { + if (!(actual instanceof RpcCallback)) { + return false; + } + RpcCallback actualCallback = (RpcCallback)actual; + + callback.reset(); + actualCallback.run(null); + return callback.isCalled(); + } + + public void appendTo(StringBuffer buffer) { + buffer.append("wrapsCallback(mockCallback)"); + } + } +} diff --git a/java/core/src/test/java/com/google/protobuf/SingleFieldBuilderTest.java b/java/core/src/test/java/com/google/protobuf/SingleFieldBuilderTest.java new file mode 100644 index 00000000..58b80007 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/SingleFieldBuilderTest.java @@ -0,0 +1,155 @@ +// 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.UnittestProto.TestAllTypes; +import protobuf_unittest.UnittestProto.TestAllTypesOrBuilder; + +import junit.framework.TestCase; + +/** + * Tests for {@link SingleFieldBuilder}. This tests basic functionality. + * More extensive testing is provided via other tests that exercise the + * builder. + * + * @author jonp@google.com (Jon Perlow) + */ +public class SingleFieldBuilderTest extends TestCase { + + public void testBasicUseAndInvalidations() { + TestUtil.MockBuilderParent mockParent = new TestUtil.MockBuilderParent(); + SingleFieldBuilder<TestAllTypes, TestAllTypes.Builder, + TestAllTypesOrBuilder> builder = + new SingleFieldBuilder<TestAllTypes, TestAllTypes.Builder, + TestAllTypesOrBuilder>( + TestAllTypes.getDefaultInstance(), + mockParent, + false); + assertSame(TestAllTypes.getDefaultInstance(), builder.getMessage()); + assertEquals(TestAllTypes.getDefaultInstance(), + builder.getBuilder().buildPartial()); + assertEquals(0, mockParent.getInvalidationCount()); + + builder.getBuilder().setOptionalInt32(10); + assertEquals(0, mockParent.getInvalidationCount()); + TestAllTypes message = builder.build(); + assertEquals(10, message.getOptionalInt32()); + + // Test that we receive invalidations now that build has been called. + assertEquals(0, mockParent.getInvalidationCount()); + builder.getBuilder().setOptionalInt32(20); + assertEquals(1, mockParent.getInvalidationCount()); + + // Test that we don't keep getting invalidations on every change + builder.getBuilder().setOptionalInt32(30); + assertEquals(1, mockParent.getInvalidationCount()); + + } + + public void testSetMessage() { + TestUtil.MockBuilderParent mockParent = new TestUtil.MockBuilderParent(); + SingleFieldBuilder<TestAllTypes, TestAllTypes.Builder, + TestAllTypesOrBuilder> builder = + new SingleFieldBuilder<TestAllTypes, TestAllTypes.Builder, + TestAllTypesOrBuilder>( + TestAllTypes.getDefaultInstance(), + mockParent, + false); + builder.setMessage(TestAllTypes.newBuilder().setOptionalInt32(0).build()); + assertEquals(0, builder.getMessage().getOptionalInt32()); + + // Update message using the builder + builder.getBuilder().setOptionalInt32(1); + assertEquals(0, mockParent.getInvalidationCount()); + assertEquals(1, builder.getBuilder().getOptionalInt32()); + assertEquals(1, builder.getMessage().getOptionalInt32()); + builder.build(); + builder.getBuilder().setOptionalInt32(2); + assertEquals(2, builder.getBuilder().getOptionalInt32()); + assertEquals(2, builder.getMessage().getOptionalInt32()); + + // Make sure message stays cached + assertSame(builder.getMessage(), builder.getMessage()); + } + + public void testClear() { + TestUtil.MockBuilderParent mockParent = new TestUtil.MockBuilderParent(); + SingleFieldBuilder<TestAllTypes, TestAllTypes.Builder, + TestAllTypesOrBuilder> builder = + new SingleFieldBuilder<TestAllTypes, TestAllTypes.Builder, + TestAllTypesOrBuilder>( + TestAllTypes.getDefaultInstance(), + mockParent, + false); + builder.setMessage(TestAllTypes.newBuilder().setOptionalInt32(0).build()); + assertNotSame(TestAllTypes.getDefaultInstance(), builder.getMessage()); + builder.clear(); + assertSame(TestAllTypes.getDefaultInstance(), builder.getMessage()); + + builder.getBuilder().setOptionalInt32(1); + assertNotSame(TestAllTypes.getDefaultInstance(), builder.getMessage()); + builder.clear(); + assertSame(TestAllTypes.getDefaultInstance(), builder.getMessage()); + } + + public void testMerge() { + TestUtil.MockBuilderParent mockParent = new TestUtil.MockBuilderParent(); + SingleFieldBuilder<TestAllTypes, TestAllTypes.Builder, + TestAllTypesOrBuilder> builder = + new SingleFieldBuilder<TestAllTypes, TestAllTypes.Builder, + TestAllTypesOrBuilder>( + TestAllTypes.getDefaultInstance(), + mockParent, + false); + + // Merge into default field. + builder.mergeFrom(TestAllTypes.getDefaultInstance()); + assertSame(TestAllTypes.getDefaultInstance(), builder.getMessage()); + + // Merge into non-default field on existing builder. + builder.getBuilder().setOptionalInt32(2); + builder.mergeFrom(TestAllTypes.newBuilder() + .setOptionalDouble(4.0) + .buildPartial()); + assertEquals(2, builder.getMessage().getOptionalInt32()); + assertEquals(4.0, builder.getMessage().getOptionalDouble()); + + // Merge into non-default field on existing message + builder.setMessage(TestAllTypes.newBuilder() + .setOptionalInt32(10) + .buildPartial()); + builder.mergeFrom(TestAllTypes.newBuilder() + .setOptionalDouble(5.0) + .buildPartial()); + assertEquals(10, builder.getMessage().getOptionalInt32()); + assertEquals(5.0, builder.getMessage().getOptionalDouble()); + } +} diff --git a/java/core/src/test/java/com/google/protobuf/SmallSortedMapTest.java b/java/core/src/test/java/com/google/protobuf/SmallSortedMapTest.java new file mode 100644 index 00000000..366086d3 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/SmallSortedMapTest.java @@ -0,0 +1,420 @@ +// 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 junit.framework.TestCase; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +/** + * @author darick@google.com Darick Tong + */ +public class SmallSortedMapTest extends TestCase { + // java.util.AbstractMap.SimpleEntry is private in JDK 1.5. We re-implement it + // here for JDK 1.5 users. + private static class SimpleEntry<K, V> implements Map.Entry<K, V> { + private final K key; + private V value; + + SimpleEntry(K key, V value) { + this.key = key; + this.value = value; + } + + public K getKey() { + return key; + } + + public V getValue() { + return value; + } + + public V setValue(V value) { + V oldValue = this.value; + this.value = value; + return oldValue; + } + + private static boolean eq(Object o1, Object o2) { + return o1 == null ? o2 == null : o1.equals(o2); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Map.Entry)) + return false; + Map.Entry e = (Map.Entry) o; + return eq(key, e.getKey()) && eq(value, e.getValue()); + } + + @Override + public int hashCode() { + return ((key == null) ? 0 : key.hashCode()) ^ + ((value == null) ? 0 : value.hashCode()); + } + } + + public void testPutAndGetArrayEntriesOnly() { + runPutAndGetTest(3); + } + + public void testPutAndGetOverflowEntries() { + runPutAndGetTest(6); + } + + private void runPutAndGetTest(int numElements) { + // Test with even and odd arraySize + SmallSortedMap<Integer, Integer> map1 = + SmallSortedMap.newInstanceForTest(3); + SmallSortedMap<Integer, Integer> map2 = + SmallSortedMap.newInstanceForTest(4); + SmallSortedMap<Integer, Integer> map3 = + SmallSortedMap.newInstanceForTest(3); + SmallSortedMap<Integer, Integer> map4 = + SmallSortedMap.newInstanceForTest(4); + + // Test with puts in ascending order. + for (int i = 0; i < numElements; i++) { + assertNull(map1.put(i, i + 1)); + assertNull(map2.put(i, i + 1)); + } + // Test with puts in descending order. + for (int i = numElements - 1; i >= 0; i--) { + assertNull(map3.put(i, i + 1)); + assertNull(map4.put(i, i + 1)); + } + + assertEquals(Math.min(3, numElements), map1.getNumArrayEntries()); + assertEquals(Math.min(4, numElements), map2.getNumArrayEntries()); + assertEquals(Math.min(3, numElements), map3.getNumArrayEntries()); + assertEquals(Math.min(4, numElements), map4.getNumArrayEntries()); + + List<SmallSortedMap<Integer, Integer>> allMaps = + new ArrayList<SmallSortedMap<Integer, Integer>>(); + allMaps.add(map1); + allMaps.add(map2); + allMaps.add(map3); + allMaps.add(map4); + + for (SmallSortedMap<Integer, Integer> map : allMaps) { + assertEquals(numElements, map.size()); + for (int i = 0; i < numElements; i++) { + assertEquals(new Integer(i + 1), map.get(i)); + } + } + + assertEquals(map1, map2); + assertEquals(map2, map3); + assertEquals(map3, map4); + } + + public void testReplacingPut() { + SmallSortedMap<Integer, Integer> map = SmallSortedMap.newInstanceForTest(3); + for (int i = 0; i < 6; i++) { + assertNull(map.put(i, i + 1)); + assertNull(map.remove(i + 1)); + } + for (int i = 0; i < 6; i++) { + assertEquals(new Integer(i + 1), map.put(i, i + 2)); + } + } + + public void testRemove() { + SmallSortedMap<Integer, Integer> map = SmallSortedMap.newInstanceForTest(3); + for (int i = 0; i < 6; i++) { + assertNull(map.put(i, i + 1)); + assertNull(map.remove(i + 1)); + } + + assertEquals(3, map.getNumArrayEntries()); + assertEquals(3, map.getNumOverflowEntries()); + assertEquals(6, map.size()); + assertEquals(makeSortedKeySet(0, 1, 2, 3, 4, 5), map.keySet()); + + assertEquals(new Integer(2), map.remove(1)); + assertEquals(3, map.getNumArrayEntries()); + assertEquals(2, map.getNumOverflowEntries()); + assertEquals(5, map.size()); + assertEquals(makeSortedKeySet(0, 2, 3, 4, 5), map.keySet()); + + assertEquals(new Integer(5), map.remove(4)); + assertEquals(3, map.getNumArrayEntries()); + assertEquals(1, map.getNumOverflowEntries()); + assertEquals(4, map.size()); + assertEquals(makeSortedKeySet(0, 2, 3, 5), map.keySet()); + + assertEquals(new Integer(4), map.remove(3)); + assertEquals(3, map.getNumArrayEntries()); + assertEquals(0, map.getNumOverflowEntries()); + assertEquals(3, map.size()); + assertEquals(makeSortedKeySet(0, 2, 5), map.keySet()); + + assertNull(map.remove(3)); + assertEquals(3, map.getNumArrayEntries()); + assertEquals(0, map.getNumOverflowEntries()); + assertEquals(3, map.size()); + + assertEquals(new Integer(1), map.remove(0)); + assertEquals(2, map.getNumArrayEntries()); + assertEquals(0, map.getNumOverflowEntries()); + assertEquals(2, map.size()); + } + + public void testClear() { + SmallSortedMap<Integer, Integer> map = SmallSortedMap.newInstanceForTest(3); + for (int i = 0; i < 6; i++) { + assertNull(map.put(i, i + 1)); + } + map.clear(); + assertEquals(0, map.getNumArrayEntries()); + assertEquals(0, map.getNumOverflowEntries()); + assertEquals(0, map.size()); + } + + public void testGetArrayEntryAndOverflowEntries() { + SmallSortedMap<Integer, Integer> map = SmallSortedMap.newInstanceForTest(3); + for (int i = 0; i < 6; i++) { + assertNull(map.put(i, i + 1)); + } + assertEquals(3, map.getNumArrayEntries()); + for (int i = 0; i < 3; i++) { + Map.Entry<Integer, Integer> entry = map.getArrayEntryAt(i); + assertEquals(new Integer(i), entry.getKey()); + assertEquals(new Integer(i + 1), entry.getValue()); + } + Iterator<Map.Entry<Integer, Integer>> it = + map.getOverflowEntries().iterator(); + for (int i = 3; i < 6; i++) { + assertTrue(it.hasNext()); + Map.Entry<Integer, Integer> entry = it.next(); + assertEquals(new Integer(i), entry.getKey()); + assertEquals(new Integer(i + 1), entry.getValue()); + } + assertFalse(it.hasNext()); + } + + public void testEntrySetContains() { + SmallSortedMap<Integer, Integer> map = SmallSortedMap.newInstanceForTest(3); + for (int i = 0; i < 6; i++) { + assertNull(map.put(i, i + 1)); + } + Set<Map.Entry<Integer, Integer>> entrySet = map.entrySet(); + for (int i = 0; i < 6; i++) { + assertTrue( + entrySet.contains(new SimpleEntry<Integer, Integer>(i, i + 1))); + assertFalse( + entrySet.contains(new SimpleEntry<Integer, Integer>(i, i))); + } + } + + public void testEntrySetAdd() { + SmallSortedMap<Integer, Integer> map = SmallSortedMap.newInstanceForTest(3); + Set<Map.Entry<Integer, Integer>> entrySet = map.entrySet(); + for (int i = 0; i < 6; i++) { + Map.Entry<Integer, Integer> entry = + new SimpleEntry<Integer, Integer>(i, i + 1); + assertTrue(entrySet.add(entry)); + assertFalse(entrySet.add(entry)); + } + for (int i = 0; i < 6; i++) { + assertEquals(new Integer(i + 1), map.get(i)); + } + assertEquals(3, map.getNumArrayEntries()); + assertEquals(3, map.getNumOverflowEntries()); + assertEquals(6, map.size()); + } + + public void testEntrySetRemove() { + SmallSortedMap<Integer, Integer> map = SmallSortedMap.newInstanceForTest(3); + Set<Map.Entry<Integer, Integer>> entrySet = map.entrySet(); + for (int i = 0; i < 6; i++) { + assertNull(map.put(i, i + 1)); + } + for (int i = 0; i < 6; i++) { + Map.Entry<Integer, Integer> entry = + new SimpleEntry<Integer, Integer>(i, i + 1); + assertTrue(entrySet.remove(entry)); + assertFalse(entrySet.remove(entry)); + } + assertTrue(map.isEmpty()); + assertEquals(0, map.getNumArrayEntries()); + assertEquals(0, map.getNumOverflowEntries()); + assertEquals(0, map.size()); + } + + public void testEntrySetClear() { + SmallSortedMap<Integer, Integer> map = SmallSortedMap.newInstanceForTest(3); + for (int i = 0; i < 6; i++) { + assertNull(map.put(i, i + 1)); + } + map.entrySet().clear(); + assertTrue(map.isEmpty()); + assertEquals(0, map.getNumArrayEntries()); + assertEquals(0, map.getNumOverflowEntries()); + assertEquals(0, map.size()); + } + + public void testEntrySetIteratorNext() { + SmallSortedMap<Integer, Integer> map = SmallSortedMap.newInstanceForTest(3); + for (int i = 0; i < 6; i++) { + assertNull(map.put(i, i + 1)); + } + Iterator<Map.Entry<Integer, Integer>> it = map.entrySet().iterator(); + for (int i = 0; i < 6; i++) { + assertTrue(it.hasNext()); + Map.Entry<Integer, Integer> entry = it.next(); + assertEquals(new Integer(i), entry.getKey()); + assertEquals(new Integer(i + 1), entry.getValue()); + } + assertFalse(it.hasNext()); + } + + public void testEntrySetIteratorRemove() { + SmallSortedMap<Integer, Integer> map = SmallSortedMap.newInstanceForTest(3); + for (int i = 0; i < 6; i++) { + assertNull(map.put(i, i + 1)); + } + Iterator<Map.Entry<Integer, Integer>> it = map.entrySet().iterator(); + for (int i = 0; i < 6; i++) { + assertTrue(map.containsKey(i)); + it.next(); + it.remove(); + assertFalse(map.containsKey(i)); + assertEquals(6 - i - 1, map.size()); + } + } + + public void testMapEntryModification() { + SmallSortedMap<Integer, Integer> map = SmallSortedMap.newInstanceForTest(3); + for (int i = 0; i < 6; i++) { + assertNull(map.put(i, i + 1)); + } + Iterator<Map.Entry<Integer, Integer>> it = map.entrySet().iterator(); + for (int i = 0; i < 6; i++) { + Map.Entry<Integer, Integer> entry = it.next(); + entry.setValue(i + 23); + } + for (int i = 0; i < 6; i++) { + assertEquals(new Integer(i + 23), map.get(i)); + } + } + + public void testMakeImmutable() { + SmallSortedMap<Integer, Integer> map = SmallSortedMap.newInstanceForTest(3); + for (int i = 0; i < 6; i++) { + assertNull(map.put(i, i + 1)); + } + map.makeImmutable(); + assertEquals(new Integer(1), map.get(0)); + assertEquals(6, map.size()); + + try { + map.put(23, 23); + fail("Expected UnsupportedOperationException"); + } catch (UnsupportedOperationException expected) { + } + + Map<Integer, Integer> other = new HashMap<Integer, Integer>(); + other.put(23, 23); + try { + map.putAll(other); + fail("Expected UnsupportedOperationException"); + } catch (UnsupportedOperationException expected) { + } + + try { + map.remove(0); + fail("Expected UnsupportedOperationException"); + } catch (UnsupportedOperationException expected) { + } + + try { + map.clear(); + fail("Expected UnsupportedOperationException"); + } catch (UnsupportedOperationException expected) { + } + + Set<Map.Entry<Integer, Integer>> entrySet = map.entrySet(); + try { + entrySet.clear(); + fail("Expected UnsupportedOperationException"); + } catch (UnsupportedOperationException expected) { + } + + Iterator<Map.Entry<Integer, Integer>> it = entrySet.iterator(); + while (it.hasNext()) { + Map.Entry<Integer, Integer> entry = it.next(); + try { + entry.setValue(0); + fail("Expected UnsupportedOperationException"); + } catch (UnsupportedOperationException expected) { + } + try { + it.remove(); + fail("Expected UnsupportedOperationException"); + } catch (UnsupportedOperationException expected) { + } + } + + Set<Integer> keySet = map.keySet(); + try { + keySet.clear(); + fail("Expected UnsupportedOperationException"); + } catch (UnsupportedOperationException expected) { + } + + Iterator<Integer> keys = keySet.iterator(); + while (keys.hasNext()) { + Integer key = keys.next(); + try { + keySet.remove(key); + fail("Expected UnsupportedOperationException"); + } catch (UnsupportedOperationException expected) { + } + try { + keys.remove(); + fail("Expected UnsupportedOperationException"); + } catch (UnsupportedOperationException expected) { + } + } + } + + private Set<Integer> makeSortedKeySet(Integer... keys) { + return new TreeSet<Integer>(Arrays.<Integer>asList(keys)); + } +} diff --git a/java/core/src/test/java/com/google/protobuf/TestBadIdentifiers.java b/java/core/src/test/java/com/google/protobuf/TestBadIdentifiers.java new file mode 100644 index 00000000..2c60fe0e --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/TestBadIdentifiers.java @@ -0,0 +1,96 @@ +// 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 junit.framework.TestCase; + +/** + * Tests that proto2 api generation doesn't cause compile errors when + * compiling protocol buffers that have names that would otherwise conflict + * if not fully qualified (like @Deprecated and @Override). + * + * @author jonp@google.com (Jon Perlow) + */ +public class TestBadIdentifiers extends TestCase { + + public void testCompilation() { + // If this compiles, it means the generation was correct. + TestBadIdentifiersProto.Deprecated.newBuilder(); + TestBadIdentifiersProto.Override.newBuilder(); + } + + public void testGetDescriptor() { + Descriptors.FileDescriptor fileDescriptor = + TestBadIdentifiersProto.getDescriptor(); + String descriptorField = TestBadIdentifiersProto.Descriptor + .getDefaultInstance().getDescriptor(); + Descriptors.Descriptor protoDescriptor = TestBadIdentifiersProto.Descriptor + .getDefaultInstance().getDescriptorForType(); + String nestedDescriptorField = TestBadIdentifiersProto.Descriptor + .NestedDescriptor.getDefaultInstance().getDescriptor(); + Descriptors.Descriptor nestedProtoDescriptor = TestBadIdentifiersProto + .Descriptor.NestedDescriptor.getDefaultInstance() + .getDescriptorForType(); + } + + public void testConflictingFieldNames() throws Exception { + TestBadIdentifiersProto.TestConflictingFieldNames message = + TestBadIdentifiersProto.TestConflictingFieldNames.getDefaultInstance(); + // Make sure generated accessors are properly named. + assertEquals(0, message.getInt32Field1Count()); + assertEquals(0, message.getEnumField2Count()); + assertEquals(0, message.getStringField3Count()); + assertEquals(0, message.getBytesField4Count()); + assertEquals(0, message.getMessageField5Count()); + + assertEquals(0, message.getInt32FieldCount11()); + assertEquals(0, message.getEnumFieldCount12().getNumber()); + assertEquals("", message.getStringFieldCount13()); + assertEquals(ByteString.EMPTY, message.getBytesFieldCount14()); + assertEquals(0, message.getMessageFieldCount15().getSerializedSize()); + + assertEquals(0, message.getInt32Field21Count()); + assertEquals(0, message.getEnumField22Count()); + assertEquals(0, message.getStringField23Count()); + assertEquals(0, message.getBytesField24Count()); + assertEquals(0, message.getMessageField25Count()); + + assertEquals(0, message.getInt32Field1List().size()); + assertEquals(0, message.getInt32FieldList31()); + + assertEquals(0, message.getInt64FieldCount()); + assertEquals(0L, message.getExtension( + TestBadIdentifiersProto.TestConflictingFieldNames.int64FieldCount).longValue()); + assertEquals(0L, message.getExtension( + TestBadIdentifiersProto.TestConflictingFieldNames.int64FieldList).longValue()); + + } +} diff --git a/java/core/src/test/java/com/google/protobuf/TestUtil.java b/java/core/src/test/java/com/google/protobuf/TestUtil.java new file mode 100644 index 00000000..01acb884 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/TestUtil.java @@ -0,0 +1,4275 @@ +// 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.UnittestProto; +import com.google.protobuf.UnittestLite; + +// The static imports are to avoid 100+ char lines. The following is roughly equivalent to +// import static protobuf_unittest.UnittestProto.*; +import static protobuf_unittest.UnittestProto.defaultInt32Extension; +import static protobuf_unittest.UnittestProto.defaultInt64Extension; +import static protobuf_unittest.UnittestProto.defaultUint32Extension; +import static protobuf_unittest.UnittestProto.defaultUint64Extension; +import static protobuf_unittest.UnittestProto.defaultSint32Extension; +import static protobuf_unittest.UnittestProto.defaultSint64Extension; +import static protobuf_unittest.UnittestProto.defaultFixed32Extension; +import static protobuf_unittest.UnittestProto.defaultFixed64Extension; +import static protobuf_unittest.UnittestProto.defaultSfixed32Extension; +import static protobuf_unittest.UnittestProto.defaultSfixed64Extension; +import static protobuf_unittest.UnittestProto.defaultFloatExtension; +import static protobuf_unittest.UnittestProto.defaultDoubleExtension; +import static protobuf_unittest.UnittestProto.defaultBoolExtension; +import static protobuf_unittest.UnittestProto.defaultStringExtension; +import static protobuf_unittest.UnittestProto.defaultBytesExtension; +import static protobuf_unittest.UnittestProto.defaultNestedEnumExtension; +import static protobuf_unittest.UnittestProto.defaultForeignEnumExtension; +import static protobuf_unittest.UnittestProto.defaultImportEnumExtension; +import static protobuf_unittest.UnittestProto.defaultStringPieceExtension; +import static protobuf_unittest.UnittestProto.defaultCordExtension; + +import static protobuf_unittest.UnittestProto.oneofUint32Extension; +import static protobuf_unittest.UnittestProto.oneofNestedMessageExtension; +import static protobuf_unittest.UnittestProto.oneofStringExtension; +import static protobuf_unittest.UnittestProto.oneofBytesExtension; + +import static protobuf_unittest.UnittestProto.optionalInt32Extension; +import static protobuf_unittest.UnittestProto.optionalInt64Extension; +import static protobuf_unittest.UnittestProto.optionalUint32Extension; +import static protobuf_unittest.UnittestProto.optionalUint64Extension; +import static protobuf_unittest.UnittestProto.optionalSint32Extension; +import static protobuf_unittest.UnittestProto.optionalSint64Extension; +import static protobuf_unittest.UnittestProto.optionalFixed32Extension; +import static protobuf_unittest.UnittestProto.optionalFixed64Extension; +import static protobuf_unittest.UnittestProto.optionalSfixed32Extension; +import static protobuf_unittest.UnittestProto.optionalSfixed64Extension; +import static protobuf_unittest.UnittestProto.optionalFloatExtension; +import static protobuf_unittest.UnittestProto.optionalDoubleExtension; +import static protobuf_unittest.UnittestProto.optionalBoolExtension; +import static protobuf_unittest.UnittestProto.optionalStringExtension; +import static protobuf_unittest.UnittestProto.optionalBytesExtension; +import static protobuf_unittest.UnittestProto.optionalGroupExtension; +import static protobuf_unittest.UnittestProto.optionalCordExtension; +import static protobuf_unittest.UnittestProto.optionalForeignEnumExtension; +import static protobuf_unittest.UnittestProto.optionalForeignMessageExtension; +import static protobuf_unittest.UnittestProto.optionalImportEnumExtension; +import static protobuf_unittest.UnittestProto.optionalImportMessageExtension; +import static protobuf_unittest.UnittestProto.optionalNestedEnumExtension; +import static protobuf_unittest.UnittestProto.optionalNestedMessageExtension; +import static protobuf_unittest.UnittestProto.optionalPublicImportMessageExtension; +import static protobuf_unittest.UnittestProto.optionalLazyMessageExtension; +import static protobuf_unittest.UnittestProto.optionalStringPieceExtension; + +import static protobuf_unittest.UnittestProto.repeatedInt32Extension; +import static protobuf_unittest.UnittestProto.repeatedInt64Extension; +import static protobuf_unittest.UnittestProto.repeatedUint32Extension; +import static protobuf_unittest.UnittestProto.repeatedUint64Extension; +import static protobuf_unittest.UnittestProto.repeatedSint32Extension; +import static protobuf_unittest.UnittestProto.repeatedSint64Extension; +import static protobuf_unittest.UnittestProto.repeatedFixed32Extension; +import static protobuf_unittest.UnittestProto.repeatedFixed64Extension; +import static protobuf_unittest.UnittestProto.repeatedSfixed32Extension; +import static protobuf_unittest.UnittestProto.repeatedSfixed64Extension; +import static protobuf_unittest.UnittestProto.repeatedFloatExtension; +import static protobuf_unittest.UnittestProto.repeatedDoubleExtension; +import static protobuf_unittest.UnittestProto.repeatedBoolExtension; +import static protobuf_unittest.UnittestProto.repeatedStringExtension; +import static protobuf_unittest.UnittestProto.repeatedBytesExtension; +import static protobuf_unittest.UnittestProto.repeatedGroupExtension; +import static protobuf_unittest.UnittestProto.repeatedNestedMessageExtension; +import static protobuf_unittest.UnittestProto.repeatedForeignMessageExtension; +import static protobuf_unittest.UnittestProto.repeatedImportMessageExtension; +import static protobuf_unittest.UnittestProto.repeatedLazyMessageExtension; +import static protobuf_unittest.UnittestProto.repeatedNestedEnumExtension; +import static protobuf_unittest.UnittestProto.repeatedForeignEnumExtension; +import static protobuf_unittest.UnittestProto.repeatedImportEnumExtension; +import static protobuf_unittest.UnittestProto.repeatedStringPieceExtension; +import static protobuf_unittest.UnittestProto.repeatedCordExtension; + +import static protobuf_unittest.UnittestProto.OptionalGroup_extension; +import static protobuf_unittest.UnittestProto.RepeatedGroup_extension; + +import static protobuf_unittest.UnittestProto.packedInt32Extension; +import static protobuf_unittest.UnittestProto.packedInt64Extension; +import static protobuf_unittest.UnittestProto.packedUint32Extension; +import static protobuf_unittest.UnittestProto.packedUint64Extension; +import static protobuf_unittest.UnittestProto.packedSint32Extension; +import static protobuf_unittest.UnittestProto.packedSint64Extension; +import static protobuf_unittest.UnittestProto.packedFixed32Extension; +import static protobuf_unittest.UnittestProto.packedFixed64Extension; +import static protobuf_unittest.UnittestProto.packedSfixed32Extension; +import static protobuf_unittest.UnittestProto.packedSfixed64Extension; +import static protobuf_unittest.UnittestProto.packedFloatExtension; +import static protobuf_unittest.UnittestProto.packedDoubleExtension; +import static protobuf_unittest.UnittestProto.packedBoolExtension; +import static protobuf_unittest.UnittestProto.packedEnumExtension; + +import static com.google.protobuf.UnittestLite.defaultInt32ExtensionLite; +import static com.google.protobuf.UnittestLite.defaultInt64ExtensionLite; +import static com.google.protobuf.UnittestLite.defaultUint32ExtensionLite; +import static com.google.protobuf.UnittestLite.defaultUint64ExtensionLite; +import static com.google.protobuf.UnittestLite.defaultSint32ExtensionLite; +import static com.google.protobuf.UnittestLite.defaultSint64ExtensionLite; +import static com.google.protobuf.UnittestLite.defaultFixed32ExtensionLite; +import static com.google.protobuf.UnittestLite.defaultFixed64ExtensionLite; +import static com.google.protobuf.UnittestLite.defaultSfixed32ExtensionLite; +import static com.google.protobuf.UnittestLite.defaultSfixed64ExtensionLite; +import static com.google.protobuf.UnittestLite.defaultFloatExtensionLite; +import static com.google.protobuf.UnittestLite.defaultDoubleExtensionLite; +import static com.google.protobuf.UnittestLite.defaultBoolExtensionLite; +import static com.google.protobuf.UnittestLite.defaultStringExtensionLite; +import static com.google.protobuf.UnittestLite.defaultBytesExtensionLite; +import static com.google.protobuf.UnittestLite.defaultNestedEnumExtensionLite; +import static com.google.protobuf.UnittestLite.defaultForeignEnumExtensionLite; +import static com.google.protobuf.UnittestLite.defaultImportEnumExtensionLite; +import static com.google.protobuf.UnittestLite.defaultStringPieceExtensionLite; +import static com.google.protobuf.UnittestLite.defaultCordExtensionLite; + +import static com.google.protobuf.UnittestLite.oneofUint32ExtensionLite; +import static com.google.protobuf.UnittestLite.oneofNestedMessageExtensionLite; +import static com.google.protobuf.UnittestLite.oneofStringExtensionLite; +import static com.google.protobuf.UnittestLite.oneofBytesExtensionLite; + +import static com.google.protobuf.UnittestLite.optionalInt32ExtensionLite; +import static com.google.protobuf.UnittestLite.optionalInt64ExtensionLite; +import static com.google.protobuf.UnittestLite.optionalUint32ExtensionLite; +import static com.google.protobuf.UnittestLite.optionalUint64ExtensionLite; +import static com.google.protobuf.UnittestLite.optionalSint32ExtensionLite; +import static com.google.protobuf.UnittestLite.optionalSint64ExtensionLite; +import static com.google.protobuf.UnittestLite.optionalFixed32ExtensionLite; +import static com.google.protobuf.UnittestLite.optionalFixed64ExtensionLite; +import static com.google.protobuf.UnittestLite.optionalSfixed32ExtensionLite; +import static com.google.protobuf.UnittestLite.optionalSfixed64ExtensionLite; +import static com.google.protobuf.UnittestLite.optionalFloatExtensionLite; +import static com.google.protobuf.UnittestLite.optionalDoubleExtensionLite; +import static com.google.protobuf.UnittestLite.optionalBoolExtensionLite; +import static com.google.protobuf.UnittestLite.optionalStringExtensionLite; +import static com.google.protobuf.UnittestLite.optionalBytesExtensionLite; +import static com.google.protobuf.UnittestLite.optionalGroupExtensionLite; +import static com.google.protobuf.UnittestLite.optionalNestedMessageExtensionLite; +import static com.google.protobuf.UnittestLite.optionalForeignEnumExtensionLite; +import static com.google.protobuf.UnittestLite.optionalForeignMessageExtensionLite; +import static com.google.protobuf.UnittestLite.optionalImportEnumExtensionLite; +import static com.google.protobuf.UnittestLite.optionalImportMessageExtensionLite; +import static com.google.protobuf.UnittestLite.optionalNestedEnumExtensionLite; +import static com.google.protobuf.UnittestLite.optionalPublicImportMessageExtensionLite; +import static com.google.protobuf.UnittestLite.optionalLazyMessageExtensionLite; +import static com.google.protobuf.UnittestLite.optionalStringPieceExtensionLite; +import static com.google.protobuf.UnittestLite.optionalCordExtensionLite; + +import static com.google.protobuf.UnittestLite.repeatedInt32ExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedInt64ExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedUint32ExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedUint64ExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedSint32ExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedSint64ExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedFixed32ExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedFixed64ExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedSfixed32ExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedSfixed64ExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedFloatExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedDoubleExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedBoolExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedStringExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedBytesExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedGroupExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedNestedMessageExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedForeignMessageExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedImportMessageExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedLazyMessageExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedNestedEnumExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedForeignEnumExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedImportEnumExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedStringPieceExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedCordExtensionLite; + +import static com.google.protobuf.UnittestLite.OptionalGroup_extension_lite; +import static com.google.protobuf.UnittestLite.RepeatedGroup_extension_lite; + +import static com.google.protobuf.UnittestLite.packedInt32ExtensionLite; +import static com.google.protobuf.UnittestLite.packedInt64ExtensionLite; +import static com.google.protobuf.UnittestLite.packedUint32ExtensionLite; +import static com.google.protobuf.UnittestLite.packedUint64ExtensionLite; +import static com.google.protobuf.UnittestLite.packedSint32ExtensionLite; +import static com.google.protobuf.UnittestLite.packedSint64ExtensionLite; +import static com.google.protobuf.UnittestLite.packedFixed32ExtensionLite; +import static com.google.protobuf.UnittestLite.packedFixed64ExtensionLite; +import static com.google.protobuf.UnittestLite.packedSfixed32ExtensionLite; +import static com.google.protobuf.UnittestLite.packedSfixed64ExtensionLite; +import static com.google.protobuf.UnittestLite.packedFloatExtensionLite; +import static com.google.protobuf.UnittestLite.packedDoubleExtensionLite; +import static com.google.protobuf.UnittestLite.packedBoolExtensionLite; +import static com.google.protobuf.UnittestLite.packedEnumExtensionLite; + +import protobuf_unittest.UnittestProto.TestAllExtensions; +import protobuf_unittest.UnittestProto.TestAllExtensionsOrBuilder; +import protobuf_unittest.UnittestProto.TestAllTypes; +import protobuf_unittest.UnittestProto.TestAllTypesOrBuilder; +import protobuf_unittest.UnittestProto.TestOneof2; +import protobuf_unittest.UnittestProto.TestPackedExtensions; +import protobuf_unittest.UnittestProto.TestPackedTypes; +import protobuf_unittest.UnittestProto.TestUnpackedTypes; +import protobuf_unittest.UnittestProto.ForeignMessage; +import protobuf_unittest.UnittestProto.ForeignEnum; +import com.google.protobuf.test.UnittestImport.ImportEnum; +import com.google.protobuf.test.UnittestImport.ImportMessage; +import com.google.protobuf.test.UnittestImportPublic.PublicImportMessage; + +import com.google.protobuf.UnittestLite.TestAllTypesLite; +import com.google.protobuf.UnittestLite.TestAllExtensionsLite; +import com.google.protobuf.UnittestLite.TestAllExtensionsLiteOrBuilder; +import com.google.protobuf.UnittestLite.TestPackedExtensionsLite; +import com.google.protobuf.UnittestLite.ForeignMessageLite; +import com.google.protobuf.UnittestLite.ForeignEnumLite; +import com.google.protobuf.UnittestImportLite.ImportEnumLite; +import com.google.protobuf.UnittestImportLite.ImportMessageLite; +import com.google.protobuf.UnittestImportPublicLite.PublicImportMessageLite; + +import junit.framework.Assert; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; + +/** + * Contains methods for setting all fields of {@code TestAllTypes} to + * some values as well as checking that all the fields are set to those values. + * These are useful for testing various protocol message features, e.g. + * set all fields of a message, serialize it, parse it, and check that all + * fields are set. + * + * <p>This code is not to be used outside of {@code com.google.protobuf} and + * subpackages. + * + * @author kenton@google.com Kenton Varda + */ +public final class TestUtil { + private TestUtil() {} + + /** Helper to convert a String to ByteString. */ + static ByteString toBytes(String str) { + return ByteString.copyFrom(str.getBytes(Internal.UTF_8)); + } + + /** + * Get a {@code TestAllTypes} with all fields set as they would be by + * {@link #setAllFields(TestAllTypes.Builder)}. + */ + public static TestAllTypes getAllSet() { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + setAllFields(builder); + return builder.build(); + } + + /** + * Get a {@code TestAllTypes.Builder} with all fields set as they would be by + * {@link #setAllFields(TestAllTypes.Builder)}. + */ + public static TestAllTypes.Builder getAllSetBuilder() { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + setAllFields(builder); + return builder; + } + + /** + * Get a {@code TestAllTypesLite.Builder} with all fields set as they would be by + * {@link #setAllFields(TestAllTypesLite.Builder)}. + */ + public static TestAllTypesLite.Builder getAllLiteSetBuilder() { + TestAllTypesLite.Builder builder = TestAllTypesLite.newBuilder(); + setAllFields(builder); + return builder; + } + + /** + * Get a {@code TestAllExtensions} with all fields set as they would be by + * {@link #setAllExtensions(TestAllExtensions.Builder)}. + */ + public static TestAllExtensions getAllExtensionsSet() { + TestAllExtensions.Builder builder = TestAllExtensions.newBuilder(); + setAllExtensions(builder); + return builder.build(); + } + + public static TestAllExtensionsLite getAllLiteExtensionsSet() { + TestAllExtensionsLite.Builder builder = TestAllExtensionsLite.newBuilder(); + setAllExtensions(builder); + return builder.build(); + } + + public static TestPackedTypes getPackedSet() { + TestPackedTypes.Builder builder = TestPackedTypes.newBuilder(); + setPackedFields(builder); + return builder.build(); + } + + public static TestUnpackedTypes getUnpackedSet() { + TestUnpackedTypes.Builder builder = TestUnpackedTypes.newBuilder(); + setUnpackedFields(builder); + return builder.build(); + } + + public static TestPackedExtensions getPackedExtensionsSet() { + TestPackedExtensions.Builder builder = TestPackedExtensions.newBuilder(); + setPackedExtensions(builder); + return builder.build(); + } + + public static TestPackedExtensionsLite getLitePackedExtensionsSet() { + TestPackedExtensionsLite.Builder builder = + TestPackedExtensionsLite.newBuilder(); + setPackedExtensions(builder); + return builder.build(); + } + + /** + * Set every field of {@code builder} to the values expected by + * {@code assertAllFieldsSet()}. + */ + public static void setAllFields(TestAllTypesLite.Builder builder) { + builder.setOptionalInt32 (101); + builder.setOptionalInt64 (102); + builder.setOptionalUint32 (103); + builder.setOptionalUint64 (104); + builder.setOptionalSint32 (105); + builder.setOptionalSint64 (106); + builder.setOptionalFixed32 (107); + builder.setOptionalFixed64 (108); + builder.setOptionalSfixed32(109); + builder.setOptionalSfixed64(110); + builder.setOptionalFloat (111); + builder.setOptionalDouble (112); + builder.setOptionalBool (true); + builder.setOptionalString ("115"); + builder.setOptionalBytes (toBytes("116")); + + builder.setOptionalGroup( + TestAllTypesLite.OptionalGroup.newBuilder().setA(117).build()); + builder.setOptionalNestedMessage( + TestAllTypesLite.NestedMessage.newBuilder().setBb(118).build()); + builder.setOptionalForeignMessage( + ForeignMessageLite.newBuilder().setC(119).build()); + builder.setOptionalImportMessage( + ImportMessageLite.newBuilder().setD(120).build()); + builder.setOptionalPublicImportMessage( + PublicImportMessageLite.newBuilder().setE(126).build()); + builder.setOptionalLazyMessage( + TestAllTypesLite.NestedMessage.newBuilder().setBb(127).build()); + + builder.setOptionalNestedEnum (TestAllTypesLite.NestedEnum.BAZ); + builder.setOptionalForeignEnum(ForeignEnumLite.FOREIGN_LITE_BAZ); + builder.setOptionalImportEnum (ImportEnumLite.IMPORT_LITE_BAZ); + + builder.setOptionalStringPiece("124"); + builder.setOptionalCord("125"); + + // ----------------------------------------------------------------- + + builder.addRepeatedInt32 (201); + builder.addRepeatedInt64 (202); + builder.addRepeatedUint32 (203); + builder.addRepeatedUint64 (204); + builder.addRepeatedSint32 (205); + builder.addRepeatedSint64 (206); + builder.addRepeatedFixed32 (207); + builder.addRepeatedFixed64 (208); + builder.addRepeatedSfixed32(209); + builder.addRepeatedSfixed64(210); + builder.addRepeatedFloat (211); + builder.addRepeatedDouble (212); + builder.addRepeatedBool (true); + builder.addRepeatedString ("215"); + builder.addRepeatedBytes (toBytes("216")); + + builder.addRepeatedGroup( + TestAllTypesLite.RepeatedGroup.newBuilder().setA(217).build()); + builder.addRepeatedNestedMessage( + TestAllTypesLite.NestedMessage.newBuilder().setBb(218).build()); + builder.addRepeatedForeignMessage( + ForeignMessageLite.newBuilder().setC(219).build()); + builder.addRepeatedImportMessage( + ImportMessageLite.newBuilder().setD(220).build()); + builder.addRepeatedLazyMessage( + TestAllTypesLite.NestedMessage.newBuilder().setBb(227).build()); + + builder.addRepeatedNestedEnum (TestAllTypesLite.NestedEnum.BAR); + builder.addRepeatedForeignEnum(ForeignEnumLite.FOREIGN_LITE_BAR); + builder.addRepeatedImportEnum (ImportEnumLite.IMPORT_LITE_BAR); + + builder.addRepeatedStringPiece("224"); + builder.addRepeatedCord("225"); + + // Add a second one of each field. + builder.addRepeatedInt32 (301); + builder.addRepeatedInt64 (302); + builder.addRepeatedUint32 (303); + builder.addRepeatedUint64 (304); + builder.addRepeatedSint32 (305); + builder.addRepeatedSint64 (306); + builder.addRepeatedFixed32 (307); + builder.addRepeatedFixed64 (308); + builder.addRepeatedSfixed32(309); + builder.addRepeatedSfixed64(310); + builder.addRepeatedFloat (311); + builder.addRepeatedDouble (312); + builder.addRepeatedBool (false); + builder.addRepeatedString ("315"); + builder.addRepeatedBytes (toBytes("316")); + + builder.addRepeatedGroup( + TestAllTypesLite.RepeatedGroup.newBuilder().setA(317).build()); + builder.addRepeatedNestedMessage( + TestAllTypesLite.NestedMessage.newBuilder().setBb(318).build()); + builder.addRepeatedForeignMessage( + ForeignMessageLite.newBuilder().setC(319).build()); + builder.addRepeatedImportMessage( + ImportMessageLite.newBuilder().setD(320).build()); + builder.addRepeatedLazyMessage( + TestAllTypesLite.NestedMessage.newBuilder().setBb(327).build()); + + builder.addRepeatedNestedEnum (TestAllTypesLite.NestedEnum.BAZ); + builder.addRepeatedForeignEnum(ForeignEnumLite.FOREIGN_LITE_BAZ); + builder.addRepeatedImportEnum (ImportEnumLite.IMPORT_LITE_BAZ); + + builder.addRepeatedStringPiece("324"); + builder.addRepeatedCord("325"); + + // ----------------------------------------------------------------- + + builder.setDefaultInt32 (401); + builder.setDefaultInt64 (402); + builder.setDefaultUint32 (403); + builder.setDefaultUint64 (404); + builder.setDefaultSint32 (405); + builder.setDefaultSint64 (406); + builder.setDefaultFixed32 (407); + builder.setDefaultFixed64 (408); + builder.setDefaultSfixed32(409); + builder.setDefaultSfixed64(410); + builder.setDefaultFloat (411); + builder.setDefaultDouble (412); + builder.setDefaultBool (false); + builder.setDefaultString ("415"); + builder.setDefaultBytes (toBytes("416")); + + builder.setDefaultNestedEnum (TestAllTypesLite.NestedEnum.FOO); + builder.setDefaultForeignEnum(ForeignEnumLite.FOREIGN_LITE_FOO); + builder.setDefaultImportEnum (ImportEnumLite.IMPORT_LITE_FOO); + + builder.setDefaultStringPiece("424"); + builder.setDefaultCord("425"); + + builder.setOneofUint32(601); + builder.setOneofNestedMessage( + TestAllTypesLite.NestedMessage.newBuilder().setBb(602).build()); + builder.setOneofString("603"); + builder.setOneofBytes(toBytes("604")); + } + + /** + * Set every field of {@code message} to the values expected by + * {@code assertAllFieldsSet()}. + */ + public static void setAllFields(TestAllTypes.Builder message) { + message.setOptionalInt32 (101); + message.setOptionalInt64 (102); + message.setOptionalUint32 (103); + message.setOptionalUint64 (104); + message.setOptionalSint32 (105); + message.setOptionalSint64 (106); + message.setOptionalFixed32 (107); + message.setOptionalFixed64 (108); + message.setOptionalSfixed32(109); + message.setOptionalSfixed64(110); + message.setOptionalFloat (111); + message.setOptionalDouble (112); + message.setOptionalBool (true); + message.setOptionalString ("115"); + message.setOptionalBytes (toBytes("116")); + + message.setOptionalGroup( + TestAllTypes.OptionalGroup.newBuilder().setA(117).build()); + message.setOptionalNestedMessage( + TestAllTypes.NestedMessage.newBuilder().setBb(118).build()); + message.setOptionalForeignMessage( + ForeignMessage.newBuilder().setC(119).build()); + message.setOptionalImportMessage( + ImportMessage.newBuilder().setD(120).build()); + message.setOptionalPublicImportMessage( + PublicImportMessage.newBuilder().setE(126).build()); + message.setOptionalLazyMessage( + TestAllTypes.NestedMessage.newBuilder().setBb(127).build()); + + message.setOptionalNestedEnum (TestAllTypes.NestedEnum.BAZ); + message.setOptionalForeignEnum(ForeignEnum.FOREIGN_BAZ); + message.setOptionalImportEnum (ImportEnum.IMPORT_BAZ); + + message.setOptionalStringPiece("124"); + message.setOptionalCord("125"); + + // ----------------------------------------------------------------- + + message.addRepeatedInt32 (201); + message.addRepeatedInt64 (202); + message.addRepeatedUint32 (203); + message.addRepeatedUint64 (204); + message.addRepeatedSint32 (205); + message.addRepeatedSint64 (206); + message.addRepeatedFixed32 (207); + message.addRepeatedFixed64 (208); + message.addRepeatedSfixed32(209); + message.addRepeatedSfixed64(210); + message.addRepeatedFloat (211); + message.addRepeatedDouble (212); + message.addRepeatedBool (true); + message.addRepeatedString ("215"); + message.addRepeatedBytes (toBytes("216")); + + message.addRepeatedGroup( + TestAllTypes.RepeatedGroup.newBuilder().setA(217).build()); + message.addRepeatedNestedMessage( + TestAllTypes.NestedMessage.newBuilder().setBb(218).build()); + message.addRepeatedForeignMessage( + ForeignMessage.newBuilder().setC(219).build()); + message.addRepeatedImportMessage( + ImportMessage.newBuilder().setD(220).build()); + message.addRepeatedLazyMessage( + TestAllTypes.NestedMessage.newBuilder().setBb(227).build()); + + message.addRepeatedNestedEnum (TestAllTypes.NestedEnum.BAR); + message.addRepeatedForeignEnum(ForeignEnum.FOREIGN_BAR); + message.addRepeatedImportEnum (ImportEnum.IMPORT_BAR); + + message.addRepeatedStringPiece("224"); + message.addRepeatedCord("225"); + + // Add a second one of each field. + message.addRepeatedInt32 (301); + message.addRepeatedInt64 (302); + message.addRepeatedUint32 (303); + message.addRepeatedUint64 (304); + message.addRepeatedSint32 (305); + message.addRepeatedSint64 (306); + message.addRepeatedFixed32 (307); + message.addRepeatedFixed64 (308); + message.addRepeatedSfixed32(309); + message.addRepeatedSfixed64(310); + message.addRepeatedFloat (311); + message.addRepeatedDouble (312); + message.addRepeatedBool (false); + message.addRepeatedString ("315"); + message.addRepeatedBytes (toBytes("316")); + + message.addRepeatedGroup( + TestAllTypes.RepeatedGroup.newBuilder().setA(317).build()); + message.addRepeatedNestedMessage( + TestAllTypes.NestedMessage.newBuilder().setBb(318).build()); + message.addRepeatedForeignMessage( + ForeignMessage.newBuilder().setC(319).build()); + message.addRepeatedImportMessage( + ImportMessage.newBuilder().setD(320).build()); + message.addRepeatedLazyMessage( + TestAllTypes.NestedMessage.newBuilder().setBb(327).build()); + + message.addRepeatedNestedEnum (TestAllTypes.NestedEnum.BAZ); + message.addRepeatedForeignEnum(ForeignEnum.FOREIGN_BAZ); + message.addRepeatedImportEnum (ImportEnum.IMPORT_BAZ); + + message.addRepeatedStringPiece("324"); + message.addRepeatedCord("325"); + + // ----------------------------------------------------------------- + + message.setDefaultInt32 (401); + message.setDefaultInt64 (402); + message.setDefaultUint32 (403); + message.setDefaultUint64 (404); + message.setDefaultSint32 (405); + message.setDefaultSint64 (406); + message.setDefaultFixed32 (407); + message.setDefaultFixed64 (408); + message.setDefaultSfixed32(409); + message.setDefaultSfixed64(410); + message.setDefaultFloat (411); + message.setDefaultDouble (412); + message.setDefaultBool (false); + message.setDefaultString ("415"); + message.setDefaultBytes (toBytes("416")); + + message.setDefaultNestedEnum (TestAllTypes.NestedEnum.FOO); + message.setDefaultForeignEnum(ForeignEnum.FOREIGN_FOO); + message.setDefaultImportEnum (ImportEnum.IMPORT_FOO); + + message.setDefaultStringPiece("424"); + message.setDefaultCord("425"); + + message.setOneofUint32(601); + message.setOneofNestedMessage( + TestAllTypes.NestedMessage.newBuilder().setBb(602).build()); + message.setOneofString("603"); + message.setOneofBytes(toBytes("604")); + } + + // ------------------------------------------------------------------- + + /** + * Modify the repeated fields of {@code message} to contain the values + * expected by {@code assertRepeatedFieldsModified()}. + */ + public static void modifyRepeatedFields(TestAllTypes.Builder message) { + message.setRepeatedInt32 (1, 501); + message.setRepeatedInt64 (1, 502); + message.setRepeatedUint32 (1, 503); + message.setRepeatedUint64 (1, 504); + message.setRepeatedSint32 (1, 505); + message.setRepeatedSint64 (1, 506); + message.setRepeatedFixed32 (1, 507); + message.setRepeatedFixed64 (1, 508); + message.setRepeatedSfixed32(1, 509); + message.setRepeatedSfixed64(1, 510); + message.setRepeatedFloat (1, 511); + message.setRepeatedDouble (1, 512); + message.setRepeatedBool (1, true); + message.setRepeatedString (1, "515"); + message.setRepeatedBytes (1, toBytes("516")); + + message.setRepeatedGroup(1, + TestAllTypes.RepeatedGroup.newBuilder().setA(517).build()); + message.setRepeatedNestedMessage(1, + TestAllTypes.NestedMessage.newBuilder().setBb(518).build()); + message.setRepeatedForeignMessage(1, + ForeignMessage.newBuilder().setC(519).build()); + message.setRepeatedImportMessage(1, + ImportMessage.newBuilder().setD(520).build()); + message.setRepeatedLazyMessage(1, + TestAllTypes.NestedMessage.newBuilder().setBb(527).build()); + + message.setRepeatedNestedEnum (1, TestAllTypes.NestedEnum.FOO); + message.setRepeatedForeignEnum(1, ForeignEnum.FOREIGN_FOO); + message.setRepeatedImportEnum (1, ImportEnum.IMPORT_FOO); + + message.setRepeatedStringPiece(1, "524"); + message.setRepeatedCord(1, "525"); + } + + // ------------------------------------------------------------------- + + /** + * Assert (using {@code junit.framework.Assert}} that all fields of + * {@code message} are set to the values assigned by {@code setAllFields}. + */ + public static void assertAllFieldsSet(TestAllTypesOrBuilder message) { + Assert.assertTrue(message.hasOptionalInt32 ()); + Assert.assertTrue(message.hasOptionalInt64 ()); + Assert.assertTrue(message.hasOptionalUint32 ()); + Assert.assertTrue(message.hasOptionalUint64 ()); + Assert.assertTrue(message.hasOptionalSint32 ()); + Assert.assertTrue(message.hasOptionalSint64 ()); + Assert.assertTrue(message.hasOptionalFixed32 ()); + Assert.assertTrue(message.hasOptionalFixed64 ()); + Assert.assertTrue(message.hasOptionalSfixed32()); + Assert.assertTrue(message.hasOptionalSfixed64()); + Assert.assertTrue(message.hasOptionalFloat ()); + Assert.assertTrue(message.hasOptionalDouble ()); + Assert.assertTrue(message.hasOptionalBool ()); + Assert.assertTrue(message.hasOptionalString ()); + Assert.assertTrue(message.hasOptionalBytes ()); + + Assert.assertTrue(message.hasOptionalGroup ()); + Assert.assertTrue(message.hasOptionalNestedMessage ()); + Assert.assertTrue(message.hasOptionalForeignMessage()); + Assert.assertTrue(message.hasOptionalImportMessage ()); + + Assert.assertTrue(message.getOptionalGroup ().hasA()); + Assert.assertTrue(message.getOptionalNestedMessage ().hasBb()); + Assert.assertTrue(message.getOptionalForeignMessage().hasC()); + Assert.assertTrue(message.getOptionalImportMessage ().hasD()); + + Assert.assertTrue(message.hasOptionalNestedEnum ()); + Assert.assertTrue(message.hasOptionalForeignEnum()); + Assert.assertTrue(message.hasOptionalImportEnum ()); + + Assert.assertTrue(message.hasOptionalStringPiece()); + Assert.assertTrue(message.hasOptionalCord()); + + Assert.assertEquals(101 , message.getOptionalInt32 ()); + Assert.assertEquals(102 , message.getOptionalInt64 ()); + Assert.assertEquals(103 , message.getOptionalUint32 ()); + Assert.assertEquals(104 , message.getOptionalUint64 ()); + Assert.assertEquals(105 , message.getOptionalSint32 ()); + Assert.assertEquals(106 , message.getOptionalSint64 ()); + Assert.assertEquals(107 , message.getOptionalFixed32 ()); + Assert.assertEquals(108 , message.getOptionalFixed64 ()); + Assert.assertEquals(109 , message.getOptionalSfixed32()); + Assert.assertEquals(110 , message.getOptionalSfixed64()); + Assert.assertEquals(111 , message.getOptionalFloat (), 0.0); + Assert.assertEquals(112 , message.getOptionalDouble (), 0.0); + Assert.assertEquals(true , message.getOptionalBool ()); + Assert.assertEquals("115", message.getOptionalString ()); + Assert.assertEquals(toBytes("116"), message.getOptionalBytes()); + + Assert.assertEquals(117, message.getOptionalGroup ().getA()); + Assert.assertEquals(118, message.getOptionalNestedMessage ().getBb()); + Assert.assertEquals(119, message.getOptionalForeignMessage ().getC()); + Assert.assertEquals(120, message.getOptionalImportMessage ().getD()); + Assert.assertEquals(126, message.getOptionalPublicImportMessage().getE()); + Assert.assertEquals(127, message.getOptionalLazyMessage ().getBb()); + + Assert.assertEquals(TestAllTypes.NestedEnum.BAZ, message.getOptionalNestedEnum()); + Assert.assertEquals(ForeignEnum.FOREIGN_BAZ, message.getOptionalForeignEnum()); + Assert.assertEquals(ImportEnum.IMPORT_BAZ, message.getOptionalImportEnum()); + + Assert.assertEquals("124", message.getOptionalStringPiece()); + Assert.assertEquals("125", message.getOptionalCord()); + + // ----------------------------------------------------------------- + + Assert.assertEquals(2, message.getRepeatedInt32Count ()); + Assert.assertEquals(2, message.getRepeatedInt64Count ()); + Assert.assertEquals(2, message.getRepeatedUint32Count ()); + Assert.assertEquals(2, message.getRepeatedUint64Count ()); + Assert.assertEquals(2, message.getRepeatedSint32Count ()); + Assert.assertEquals(2, message.getRepeatedSint64Count ()); + Assert.assertEquals(2, message.getRepeatedFixed32Count ()); + Assert.assertEquals(2, message.getRepeatedFixed64Count ()); + Assert.assertEquals(2, message.getRepeatedSfixed32Count()); + Assert.assertEquals(2, message.getRepeatedSfixed64Count()); + Assert.assertEquals(2, message.getRepeatedFloatCount ()); + Assert.assertEquals(2, message.getRepeatedDoubleCount ()); + Assert.assertEquals(2, message.getRepeatedBoolCount ()); + Assert.assertEquals(2, message.getRepeatedStringCount ()); + Assert.assertEquals(2, message.getRepeatedBytesCount ()); + + Assert.assertEquals(2, message.getRepeatedGroupCount ()); + Assert.assertEquals(2, message.getRepeatedNestedMessageCount ()); + Assert.assertEquals(2, message.getRepeatedForeignMessageCount()); + Assert.assertEquals(2, message.getRepeatedImportMessageCount ()); + Assert.assertEquals(2, message.getRepeatedLazyMessageCount ()); + Assert.assertEquals(2, message.getRepeatedNestedEnumCount ()); + Assert.assertEquals(2, message.getRepeatedForeignEnumCount ()); + Assert.assertEquals(2, message.getRepeatedImportEnumCount ()); + + Assert.assertEquals(2, message.getRepeatedStringPieceCount()); + Assert.assertEquals(2, message.getRepeatedCordCount()); + + Assert.assertEquals(201 , message.getRepeatedInt32 (0)); + Assert.assertEquals(202 , message.getRepeatedInt64 (0)); + Assert.assertEquals(203 , message.getRepeatedUint32 (0)); + Assert.assertEquals(204 , message.getRepeatedUint64 (0)); + Assert.assertEquals(205 , message.getRepeatedSint32 (0)); + Assert.assertEquals(206 , message.getRepeatedSint64 (0)); + Assert.assertEquals(207 , message.getRepeatedFixed32 (0)); + Assert.assertEquals(208 , message.getRepeatedFixed64 (0)); + Assert.assertEquals(209 , message.getRepeatedSfixed32(0)); + Assert.assertEquals(210 , message.getRepeatedSfixed64(0)); + Assert.assertEquals(211 , message.getRepeatedFloat (0), 0.0); + Assert.assertEquals(212 , message.getRepeatedDouble (0), 0.0); + Assert.assertEquals(true , message.getRepeatedBool (0)); + Assert.assertEquals("215", message.getRepeatedString (0)); + Assert.assertEquals(toBytes("216"), message.getRepeatedBytes(0)); + + Assert.assertEquals(217, message.getRepeatedGroup (0).getA()); + Assert.assertEquals(218, message.getRepeatedNestedMessage (0).getBb()); + Assert.assertEquals(219, message.getRepeatedForeignMessage(0).getC()); + Assert.assertEquals(220, message.getRepeatedImportMessage (0).getD()); + Assert.assertEquals(227, message.getRepeatedLazyMessage (0).getBb()); + + Assert.assertEquals(TestAllTypes.NestedEnum.BAR, message.getRepeatedNestedEnum (0)); + Assert.assertEquals(ForeignEnum.FOREIGN_BAR, message.getRepeatedForeignEnum(0)); + Assert.assertEquals(ImportEnum.IMPORT_BAR, message.getRepeatedImportEnum(0)); + + Assert.assertEquals("224", message.getRepeatedStringPiece(0)); + Assert.assertEquals("225", message.getRepeatedCord(0)); + + Assert.assertEquals(301 , message.getRepeatedInt32 (1)); + Assert.assertEquals(302 , message.getRepeatedInt64 (1)); + Assert.assertEquals(303 , message.getRepeatedUint32 (1)); + Assert.assertEquals(304 , message.getRepeatedUint64 (1)); + Assert.assertEquals(305 , message.getRepeatedSint32 (1)); + Assert.assertEquals(306 , message.getRepeatedSint64 (1)); + Assert.assertEquals(307 , message.getRepeatedFixed32 (1)); + Assert.assertEquals(308 , message.getRepeatedFixed64 (1)); + Assert.assertEquals(309 , message.getRepeatedSfixed32(1)); + Assert.assertEquals(310 , message.getRepeatedSfixed64(1)); + Assert.assertEquals(311 , message.getRepeatedFloat (1), 0.0); + Assert.assertEquals(312 , message.getRepeatedDouble (1), 0.0); + Assert.assertEquals(false, message.getRepeatedBool (1)); + Assert.assertEquals("315", message.getRepeatedString (1)); + Assert.assertEquals(toBytes("316"), message.getRepeatedBytes(1)); + + Assert.assertEquals(317, message.getRepeatedGroup (1).getA()); + Assert.assertEquals(318, message.getRepeatedNestedMessage (1).getBb()); + Assert.assertEquals(319, message.getRepeatedForeignMessage(1).getC()); + Assert.assertEquals(320, message.getRepeatedImportMessage (1).getD()); + Assert.assertEquals(327, message.getRepeatedLazyMessage (1).getBb()); + + Assert.assertEquals(TestAllTypes.NestedEnum.BAZ, message.getRepeatedNestedEnum (1)); + Assert.assertEquals(ForeignEnum.FOREIGN_BAZ, message.getRepeatedForeignEnum(1)); + Assert.assertEquals(ImportEnum.IMPORT_BAZ, message.getRepeatedImportEnum(1)); + + Assert.assertEquals("324", message.getRepeatedStringPiece(1)); + Assert.assertEquals("325", message.getRepeatedCord(1)); + + // ----------------------------------------------------------------- + + Assert.assertTrue(message.hasDefaultInt32 ()); + Assert.assertTrue(message.hasDefaultInt64 ()); + Assert.assertTrue(message.hasDefaultUint32 ()); + Assert.assertTrue(message.hasDefaultUint64 ()); + Assert.assertTrue(message.hasDefaultSint32 ()); + Assert.assertTrue(message.hasDefaultSint64 ()); + Assert.assertTrue(message.hasDefaultFixed32 ()); + Assert.assertTrue(message.hasDefaultFixed64 ()); + Assert.assertTrue(message.hasDefaultSfixed32()); + Assert.assertTrue(message.hasDefaultSfixed64()); + Assert.assertTrue(message.hasDefaultFloat ()); + Assert.assertTrue(message.hasDefaultDouble ()); + Assert.assertTrue(message.hasDefaultBool ()); + Assert.assertTrue(message.hasDefaultString ()); + Assert.assertTrue(message.hasDefaultBytes ()); + + Assert.assertTrue(message.hasDefaultNestedEnum ()); + Assert.assertTrue(message.hasDefaultForeignEnum()); + Assert.assertTrue(message.hasDefaultImportEnum ()); + + Assert.assertTrue(message.hasDefaultStringPiece()); + Assert.assertTrue(message.hasDefaultCord()); + + Assert.assertEquals(401 , message.getDefaultInt32 ()); + Assert.assertEquals(402 , message.getDefaultInt64 ()); + Assert.assertEquals(403 , message.getDefaultUint32 ()); + Assert.assertEquals(404 , message.getDefaultUint64 ()); + Assert.assertEquals(405 , message.getDefaultSint32 ()); + Assert.assertEquals(406 , message.getDefaultSint64 ()); + Assert.assertEquals(407 , message.getDefaultFixed32 ()); + Assert.assertEquals(408 , message.getDefaultFixed64 ()); + Assert.assertEquals(409 , message.getDefaultSfixed32()); + Assert.assertEquals(410 , message.getDefaultSfixed64()); + Assert.assertEquals(411 , message.getDefaultFloat (), 0.0); + Assert.assertEquals(412 , message.getDefaultDouble (), 0.0); + Assert.assertEquals(false, message.getDefaultBool ()); + Assert.assertEquals("415", message.getDefaultString ()); + Assert.assertEquals(toBytes("416"), message.getDefaultBytes()); + + Assert.assertEquals(TestAllTypes.NestedEnum.FOO, message.getDefaultNestedEnum ()); + Assert.assertEquals(ForeignEnum.FOREIGN_FOO, message.getDefaultForeignEnum()); + Assert.assertEquals(ImportEnum.IMPORT_FOO, message.getDefaultImportEnum()); + + Assert.assertEquals("424", message.getDefaultStringPiece()); + Assert.assertEquals("425", message.getDefaultCord()); + + Assert.assertEquals(TestAllTypes.OneofFieldCase.ONEOF_BYTES, message.getOneofFieldCase()); + Assert.assertFalse(message.hasOneofUint32()); + Assert.assertFalse(message.hasOneofNestedMessage()); + Assert.assertFalse(message.hasOneofString()); + Assert.assertTrue(message.hasOneofBytes()); + + Assert.assertEquals(toBytes("604"), message.getOneofBytes()); + } + + // ------------------------------------------------------------------- + /** + * Assert (using {@code junit.framework.Assert}} that all fields of + * {@code message} are cleared, and that getting the fields returns their + * default values. + */ + public static void assertClear(TestAllTypesOrBuilder message) { + // hasBlah() should initially be false for all optional fields. + Assert.assertFalse(message.hasOptionalInt32 ()); + Assert.assertFalse(message.hasOptionalInt64 ()); + Assert.assertFalse(message.hasOptionalUint32 ()); + Assert.assertFalse(message.hasOptionalUint64 ()); + Assert.assertFalse(message.hasOptionalSint32 ()); + Assert.assertFalse(message.hasOptionalSint64 ()); + Assert.assertFalse(message.hasOptionalFixed32 ()); + Assert.assertFalse(message.hasOptionalFixed64 ()); + Assert.assertFalse(message.hasOptionalSfixed32()); + Assert.assertFalse(message.hasOptionalSfixed64()); + Assert.assertFalse(message.hasOptionalFloat ()); + Assert.assertFalse(message.hasOptionalDouble ()); + Assert.assertFalse(message.hasOptionalBool ()); + Assert.assertFalse(message.hasOptionalString ()); + Assert.assertFalse(message.hasOptionalBytes ()); + + Assert.assertFalse(message.hasOptionalGroup ()); + Assert.assertFalse(message.hasOptionalNestedMessage ()); + Assert.assertFalse(message.hasOptionalForeignMessage()); + Assert.assertFalse(message.hasOptionalImportMessage ()); + + Assert.assertFalse(message.hasOptionalNestedEnum ()); + Assert.assertFalse(message.hasOptionalForeignEnum()); + Assert.assertFalse(message.hasOptionalImportEnum ()); + + Assert.assertFalse(message.hasOptionalStringPiece()); + Assert.assertFalse(message.hasOptionalCord()); + + // Optional fields without defaults are set to zero or something like it. + Assert.assertEquals(0 , message.getOptionalInt32 ()); + Assert.assertEquals(0 , message.getOptionalInt64 ()); + Assert.assertEquals(0 , message.getOptionalUint32 ()); + Assert.assertEquals(0 , message.getOptionalUint64 ()); + Assert.assertEquals(0 , message.getOptionalSint32 ()); + Assert.assertEquals(0 , message.getOptionalSint64 ()); + Assert.assertEquals(0 , message.getOptionalFixed32 ()); + Assert.assertEquals(0 , message.getOptionalFixed64 ()); + Assert.assertEquals(0 , message.getOptionalSfixed32()); + Assert.assertEquals(0 , message.getOptionalSfixed64()); + Assert.assertEquals(0 , message.getOptionalFloat (), 0.0); + Assert.assertEquals(0 , message.getOptionalDouble (), 0.0); + Assert.assertEquals(false, message.getOptionalBool ()); + Assert.assertEquals("" , message.getOptionalString ()); + Assert.assertEquals(ByteString.EMPTY, message.getOptionalBytes()); + + // Embedded messages should also be clear. + Assert.assertFalse(message.getOptionalGroup ().hasA()); + Assert.assertFalse(message.getOptionalNestedMessage ().hasBb()); + Assert.assertFalse(message.getOptionalForeignMessage ().hasC()); + Assert.assertFalse(message.getOptionalImportMessage ().hasD()); + Assert.assertFalse(message.getOptionalPublicImportMessage().hasE()); + Assert.assertFalse(message.getOptionalLazyMessage ().hasBb()); + + Assert.assertEquals(0, message.getOptionalGroup ().getA()); + Assert.assertEquals(0, message.getOptionalNestedMessage ().getBb()); + Assert.assertEquals(0, message.getOptionalForeignMessage ().getC()); + Assert.assertEquals(0, message.getOptionalImportMessage ().getD()); + Assert.assertEquals(0, message.getOptionalPublicImportMessage().getE()); + Assert.assertEquals(0, message.getOptionalLazyMessage ().getBb()); + + // Enums without defaults are set to the first value in the enum. + Assert.assertEquals(TestAllTypes.NestedEnum.FOO, message.getOptionalNestedEnum ()); + Assert.assertEquals(ForeignEnum.FOREIGN_FOO, message.getOptionalForeignEnum()); + Assert.assertEquals(ImportEnum.IMPORT_FOO, message.getOptionalImportEnum()); + + Assert.assertEquals("", message.getOptionalStringPiece()); + Assert.assertEquals("", message.getOptionalCord()); + + // Repeated fields are empty. + Assert.assertEquals(0, message.getRepeatedInt32Count ()); + Assert.assertEquals(0, message.getRepeatedInt64Count ()); + Assert.assertEquals(0, message.getRepeatedUint32Count ()); + Assert.assertEquals(0, message.getRepeatedUint64Count ()); + Assert.assertEquals(0, message.getRepeatedSint32Count ()); + Assert.assertEquals(0, message.getRepeatedSint64Count ()); + Assert.assertEquals(0, message.getRepeatedFixed32Count ()); + Assert.assertEquals(0, message.getRepeatedFixed64Count ()); + Assert.assertEquals(0, message.getRepeatedSfixed32Count()); + Assert.assertEquals(0, message.getRepeatedSfixed64Count()); + Assert.assertEquals(0, message.getRepeatedFloatCount ()); + Assert.assertEquals(0, message.getRepeatedDoubleCount ()); + Assert.assertEquals(0, message.getRepeatedBoolCount ()); + Assert.assertEquals(0, message.getRepeatedStringCount ()); + Assert.assertEquals(0, message.getRepeatedBytesCount ()); + + Assert.assertEquals(0, message.getRepeatedGroupCount ()); + Assert.assertEquals(0, message.getRepeatedNestedMessageCount ()); + Assert.assertEquals(0, message.getRepeatedForeignMessageCount()); + Assert.assertEquals(0, message.getRepeatedImportMessageCount ()); + Assert.assertEquals(0, message.getRepeatedLazyMessageCount ()); + Assert.assertEquals(0, message.getRepeatedNestedEnumCount ()); + Assert.assertEquals(0, message.getRepeatedForeignEnumCount ()); + Assert.assertEquals(0, message.getRepeatedImportEnumCount ()); + + Assert.assertEquals(0, message.getRepeatedStringPieceCount()); + Assert.assertEquals(0, message.getRepeatedCordCount()); + + // hasBlah() should also be false for all default fields. + Assert.assertFalse(message.hasDefaultInt32 ()); + Assert.assertFalse(message.hasDefaultInt64 ()); + Assert.assertFalse(message.hasDefaultUint32 ()); + Assert.assertFalse(message.hasDefaultUint64 ()); + Assert.assertFalse(message.hasDefaultSint32 ()); + Assert.assertFalse(message.hasDefaultSint64 ()); + Assert.assertFalse(message.hasDefaultFixed32 ()); + Assert.assertFalse(message.hasDefaultFixed64 ()); + Assert.assertFalse(message.hasDefaultSfixed32()); + Assert.assertFalse(message.hasDefaultSfixed64()); + Assert.assertFalse(message.hasDefaultFloat ()); + Assert.assertFalse(message.hasDefaultDouble ()); + Assert.assertFalse(message.hasDefaultBool ()); + Assert.assertFalse(message.hasDefaultString ()); + Assert.assertFalse(message.hasDefaultBytes ()); + + Assert.assertFalse(message.hasDefaultNestedEnum ()); + Assert.assertFalse(message.hasDefaultForeignEnum()); + Assert.assertFalse(message.hasDefaultImportEnum ()); + + Assert.assertFalse(message.hasDefaultStringPiece()); + Assert.assertFalse(message.hasDefaultCord()); + + // Fields with defaults have their default values (duh). + Assert.assertEquals( 41 , message.getDefaultInt32 ()); + Assert.assertEquals( 42 , message.getDefaultInt64 ()); + Assert.assertEquals( 43 , message.getDefaultUint32 ()); + Assert.assertEquals( 44 , message.getDefaultUint64 ()); + Assert.assertEquals(-45 , message.getDefaultSint32 ()); + Assert.assertEquals( 46 , message.getDefaultSint64 ()); + Assert.assertEquals( 47 , message.getDefaultFixed32 ()); + Assert.assertEquals( 48 , message.getDefaultFixed64 ()); + Assert.assertEquals( 49 , message.getDefaultSfixed32()); + Assert.assertEquals(-50 , message.getDefaultSfixed64()); + Assert.assertEquals( 51.5 , message.getDefaultFloat (), 0.0); + Assert.assertEquals( 52e3 , message.getDefaultDouble (), 0.0); + Assert.assertEquals(true , message.getDefaultBool ()); + Assert.assertEquals("hello", message.getDefaultString ()); + Assert.assertEquals(toBytes("world"), message.getDefaultBytes()); + + Assert.assertEquals(TestAllTypes.NestedEnum.BAR, message.getDefaultNestedEnum ()); + Assert.assertEquals(ForeignEnum.FOREIGN_BAR, message.getDefaultForeignEnum()); + Assert.assertEquals(ImportEnum.IMPORT_BAR, message.getDefaultImportEnum()); + + Assert.assertEquals("abc", message.getDefaultStringPiece()); + Assert.assertEquals("123", message.getDefaultCord()); + + Assert.assertFalse(message.hasOneofUint32()); + Assert.assertFalse(message.hasOneofNestedMessage()); + Assert.assertFalse(message.hasOneofString()); + Assert.assertFalse(message.hasOneofBytes()); + } + + // ------------------------------------------------------------------- + + /** + * Assert (using {@code junit.framework.Assert}} that all fields of + * {@code message} are set to the values assigned by {@code setAllFields} + * followed by {@code modifyRepeatedFields}. + */ + public static void assertRepeatedFieldsModified( + TestAllTypesOrBuilder message) { + // ModifyRepeatedFields only sets the second repeated element of each + // field. In addition to verifying this, we also verify that the first + // element and size were *not* modified. + Assert.assertEquals(2, message.getRepeatedInt32Count ()); + Assert.assertEquals(2, message.getRepeatedInt64Count ()); + Assert.assertEquals(2, message.getRepeatedUint32Count ()); + Assert.assertEquals(2, message.getRepeatedUint64Count ()); + Assert.assertEquals(2, message.getRepeatedSint32Count ()); + Assert.assertEquals(2, message.getRepeatedSint64Count ()); + Assert.assertEquals(2, message.getRepeatedFixed32Count ()); + Assert.assertEquals(2, message.getRepeatedFixed64Count ()); + Assert.assertEquals(2, message.getRepeatedSfixed32Count()); + Assert.assertEquals(2, message.getRepeatedSfixed64Count()); + Assert.assertEquals(2, message.getRepeatedFloatCount ()); + Assert.assertEquals(2, message.getRepeatedDoubleCount ()); + Assert.assertEquals(2, message.getRepeatedBoolCount ()); + Assert.assertEquals(2, message.getRepeatedStringCount ()); + Assert.assertEquals(2, message.getRepeatedBytesCount ()); + + Assert.assertEquals(2, message.getRepeatedGroupCount ()); + Assert.assertEquals(2, message.getRepeatedNestedMessageCount ()); + Assert.assertEquals(2, message.getRepeatedForeignMessageCount()); + Assert.assertEquals(2, message.getRepeatedImportMessageCount ()); + Assert.assertEquals(2, message.getRepeatedLazyMessageCount ()); + Assert.assertEquals(2, message.getRepeatedNestedEnumCount ()); + Assert.assertEquals(2, message.getRepeatedForeignEnumCount ()); + Assert.assertEquals(2, message.getRepeatedImportEnumCount ()); + + Assert.assertEquals(2, message.getRepeatedStringPieceCount()); + Assert.assertEquals(2, message.getRepeatedCordCount()); + + Assert.assertEquals(201 , message.getRepeatedInt32 (0)); + Assert.assertEquals(202L , message.getRepeatedInt64 (0)); + Assert.assertEquals(203 , message.getRepeatedUint32 (0)); + Assert.assertEquals(204L , message.getRepeatedUint64 (0)); + Assert.assertEquals(205 , message.getRepeatedSint32 (0)); + Assert.assertEquals(206L , message.getRepeatedSint64 (0)); + Assert.assertEquals(207 , message.getRepeatedFixed32 (0)); + Assert.assertEquals(208L , message.getRepeatedFixed64 (0)); + Assert.assertEquals(209 , message.getRepeatedSfixed32(0)); + Assert.assertEquals(210L , message.getRepeatedSfixed64(0)); + Assert.assertEquals(211F , message.getRepeatedFloat (0)); + Assert.assertEquals(212D , message.getRepeatedDouble (0)); + Assert.assertEquals(true , message.getRepeatedBool (0)); + Assert.assertEquals("215", message.getRepeatedString (0)); + Assert.assertEquals(toBytes("216"), message.getRepeatedBytes(0)); + + Assert.assertEquals(217, message.getRepeatedGroup (0).getA()); + Assert.assertEquals(218, message.getRepeatedNestedMessage (0).getBb()); + Assert.assertEquals(219, message.getRepeatedForeignMessage(0).getC()); + Assert.assertEquals(220, message.getRepeatedImportMessage (0).getD()); + Assert.assertEquals(227, message.getRepeatedLazyMessage (0).getBb()); + + Assert.assertEquals(TestAllTypes.NestedEnum.BAR, message.getRepeatedNestedEnum (0)); + Assert.assertEquals(ForeignEnum.FOREIGN_BAR, message.getRepeatedForeignEnum(0)); + Assert.assertEquals(ImportEnum.IMPORT_BAR, message.getRepeatedImportEnum(0)); + + Assert.assertEquals("224", message.getRepeatedStringPiece(0)); + Assert.assertEquals("225", message.getRepeatedCord(0)); + + // Actually verify the second (modified) elements now. + Assert.assertEquals(501 , message.getRepeatedInt32 (1)); + Assert.assertEquals(502L , message.getRepeatedInt64 (1)); + Assert.assertEquals(503 , message.getRepeatedUint32 (1)); + Assert.assertEquals(504L , message.getRepeatedUint64 (1)); + Assert.assertEquals(505 , message.getRepeatedSint32 (1)); + Assert.assertEquals(506L , message.getRepeatedSint64 (1)); + Assert.assertEquals(507 , message.getRepeatedFixed32 (1)); + Assert.assertEquals(508L , message.getRepeatedFixed64 (1)); + Assert.assertEquals(509 , message.getRepeatedSfixed32(1)); + Assert.assertEquals(510L , message.getRepeatedSfixed64(1)); + Assert.assertEquals(511F , message.getRepeatedFloat (1)); + Assert.assertEquals(512D , message.getRepeatedDouble (1)); + Assert.assertEquals(true , message.getRepeatedBool (1)); + Assert.assertEquals("515", message.getRepeatedString (1)); + Assert.assertEquals(toBytes("516"), message.getRepeatedBytes(1)); + + Assert.assertEquals(517, message.getRepeatedGroup (1).getA()); + Assert.assertEquals(518, message.getRepeatedNestedMessage (1).getBb()); + Assert.assertEquals(519, message.getRepeatedForeignMessage(1).getC()); + Assert.assertEquals(520, message.getRepeatedImportMessage (1).getD()); + Assert.assertEquals(527, message.getRepeatedLazyMessage (1).getBb()); + + Assert.assertEquals(TestAllTypes.NestedEnum.FOO, message.getRepeatedNestedEnum (1)); + Assert.assertEquals(ForeignEnum.FOREIGN_FOO, message.getRepeatedForeignEnum(1)); + Assert.assertEquals(ImportEnum.IMPORT_FOO, message.getRepeatedImportEnum(1)); + + Assert.assertEquals("524", message.getRepeatedStringPiece(1)); + Assert.assertEquals("525", message.getRepeatedCord(1)); + } + + /** + * Set every field of {@code message} to a unique value. + */ + public static void setPackedFields(TestPackedTypes.Builder message) { + message.addPackedInt32 (601); + message.addPackedInt64 (602); + message.addPackedUint32 (603); + message.addPackedUint64 (604); + message.addPackedSint32 (605); + message.addPackedSint64 (606); + message.addPackedFixed32 (607); + message.addPackedFixed64 (608); + message.addPackedSfixed32(609); + message.addPackedSfixed64(610); + message.addPackedFloat (611); + message.addPackedDouble (612); + message.addPackedBool (true); + message.addPackedEnum (ForeignEnum.FOREIGN_BAR); + // Add a second one of each field. + message.addPackedInt32 (701); + message.addPackedInt64 (702); + message.addPackedUint32 (703); + message.addPackedUint64 (704); + message.addPackedSint32 (705); + message.addPackedSint64 (706); + message.addPackedFixed32 (707); + message.addPackedFixed64 (708); + message.addPackedSfixed32(709); + message.addPackedSfixed64(710); + message.addPackedFloat (711); + message.addPackedDouble (712); + message.addPackedBool (false); + message.addPackedEnum (ForeignEnum.FOREIGN_BAZ); + } + + /** + * Set every field of {@code message} to a unique value. Must correspond with + * the values applied by {@code setPackedFields}. + */ + public static void setUnpackedFields(TestUnpackedTypes.Builder message) { + message.addUnpackedInt32 (601); + message.addUnpackedInt64 (602); + message.addUnpackedUint32 (603); + message.addUnpackedUint64 (604); + message.addUnpackedSint32 (605); + message.addUnpackedSint64 (606); + message.addUnpackedFixed32 (607); + message.addUnpackedFixed64 (608); + message.addUnpackedSfixed32(609); + message.addUnpackedSfixed64(610); + message.addUnpackedFloat (611); + message.addUnpackedDouble (612); + message.addUnpackedBool (true); + message.addUnpackedEnum (ForeignEnum.FOREIGN_BAR); + // Add a second one of each field. + message.addUnpackedInt32 (701); + message.addUnpackedInt64 (702); + message.addUnpackedUint32 (703); + message.addUnpackedUint64 (704); + message.addUnpackedSint32 (705); + message.addUnpackedSint64 (706); + message.addUnpackedFixed32 (707); + message.addUnpackedFixed64 (708); + message.addUnpackedSfixed32(709); + message.addUnpackedSfixed64(710); + message.addUnpackedFloat (711); + message.addUnpackedDouble (712); + message.addUnpackedBool (false); + message.addUnpackedEnum (ForeignEnum.FOREIGN_BAZ); + } + + /** + * Assert (using {@code junit.framework.Assert}} that all fields of + * {@code message} are set to the values assigned by {@code setPackedFields}. + */ + public static void assertPackedFieldsSet(TestPackedTypes message) { + Assert.assertEquals(2, message.getPackedInt32Count ()); + Assert.assertEquals(2, message.getPackedInt64Count ()); + Assert.assertEquals(2, message.getPackedUint32Count ()); + Assert.assertEquals(2, message.getPackedUint64Count ()); + Assert.assertEquals(2, message.getPackedSint32Count ()); + Assert.assertEquals(2, message.getPackedSint64Count ()); + Assert.assertEquals(2, message.getPackedFixed32Count ()); + Assert.assertEquals(2, message.getPackedFixed64Count ()); + Assert.assertEquals(2, message.getPackedSfixed32Count()); + Assert.assertEquals(2, message.getPackedSfixed64Count()); + Assert.assertEquals(2, message.getPackedFloatCount ()); + Assert.assertEquals(2, message.getPackedDoubleCount ()); + Assert.assertEquals(2, message.getPackedBoolCount ()); + Assert.assertEquals(2, message.getPackedEnumCount ()); + Assert.assertEquals(601 , message.getPackedInt32 (0)); + Assert.assertEquals(602 , message.getPackedInt64 (0)); + Assert.assertEquals(603 , message.getPackedUint32 (0)); + Assert.assertEquals(604 , message.getPackedUint64 (0)); + Assert.assertEquals(605 , message.getPackedSint32 (0)); + Assert.assertEquals(606 , message.getPackedSint64 (0)); + Assert.assertEquals(607 , message.getPackedFixed32 (0)); + Assert.assertEquals(608 , message.getPackedFixed64 (0)); + Assert.assertEquals(609 , message.getPackedSfixed32(0)); + Assert.assertEquals(610 , message.getPackedSfixed64(0)); + Assert.assertEquals(611 , message.getPackedFloat (0), 0.0); + Assert.assertEquals(612 , message.getPackedDouble (0), 0.0); + Assert.assertEquals(true , message.getPackedBool (0)); + Assert.assertEquals(ForeignEnum.FOREIGN_BAR, message.getPackedEnum(0)); + Assert.assertEquals(701 , message.getPackedInt32 (1)); + Assert.assertEquals(702 , message.getPackedInt64 (1)); + Assert.assertEquals(703 , message.getPackedUint32 (1)); + Assert.assertEquals(704 , message.getPackedUint64 (1)); + Assert.assertEquals(705 , message.getPackedSint32 (1)); + Assert.assertEquals(706 , message.getPackedSint64 (1)); + Assert.assertEquals(707 , message.getPackedFixed32 (1)); + Assert.assertEquals(708 , message.getPackedFixed64 (1)); + Assert.assertEquals(709 , message.getPackedSfixed32(1)); + Assert.assertEquals(710 , message.getPackedSfixed64(1)); + Assert.assertEquals(711 , message.getPackedFloat (1), 0.0); + Assert.assertEquals(712 , message.getPackedDouble (1), 0.0); + Assert.assertEquals(false, message.getPackedBool (1)); + Assert.assertEquals(ForeignEnum.FOREIGN_BAZ, message.getPackedEnum(1)); + } + + /** + * Assert (using {@code junit.framework.Assert}} that all fields of + * {@code message} are set to the values assigned by {@code setUnpackedFields}. + */ + public static void assertUnpackedFieldsSet(TestUnpackedTypes message) { + Assert.assertEquals(2, message.getUnpackedInt32Count ()); + Assert.assertEquals(2, message.getUnpackedInt64Count ()); + Assert.assertEquals(2, message.getUnpackedUint32Count ()); + Assert.assertEquals(2, message.getUnpackedUint64Count ()); + Assert.assertEquals(2, message.getUnpackedSint32Count ()); + Assert.assertEquals(2, message.getUnpackedSint64Count ()); + Assert.assertEquals(2, message.getUnpackedFixed32Count ()); + Assert.assertEquals(2, message.getUnpackedFixed64Count ()); + Assert.assertEquals(2, message.getUnpackedSfixed32Count()); + Assert.assertEquals(2, message.getUnpackedSfixed64Count()); + Assert.assertEquals(2, message.getUnpackedFloatCount ()); + Assert.assertEquals(2, message.getUnpackedDoubleCount ()); + Assert.assertEquals(2, message.getUnpackedBoolCount ()); + Assert.assertEquals(2, message.getUnpackedEnumCount ()); + Assert.assertEquals(601 , message.getUnpackedInt32 (0)); + Assert.assertEquals(602 , message.getUnpackedInt64 (0)); + Assert.assertEquals(603 , message.getUnpackedUint32 (0)); + Assert.assertEquals(604 , message.getUnpackedUint64 (0)); + Assert.assertEquals(605 , message.getUnpackedSint32 (0)); + Assert.assertEquals(606 , message.getUnpackedSint64 (0)); + Assert.assertEquals(607 , message.getUnpackedFixed32 (0)); + Assert.assertEquals(608 , message.getUnpackedFixed64 (0)); + Assert.assertEquals(609 , message.getUnpackedSfixed32(0)); + Assert.assertEquals(610 , message.getUnpackedSfixed64(0)); + Assert.assertEquals(611 , message.getUnpackedFloat (0), 0.0); + Assert.assertEquals(612 , message.getUnpackedDouble (0), 0.0); + Assert.assertEquals(true , message.getUnpackedBool (0)); + Assert.assertEquals(ForeignEnum.FOREIGN_BAR, message.getUnpackedEnum(0)); + Assert.assertEquals(701 , message.getUnpackedInt32 (1)); + Assert.assertEquals(702 , message.getUnpackedInt64 (1)); + Assert.assertEquals(703 , message.getUnpackedUint32 (1)); + Assert.assertEquals(704 , message.getUnpackedUint64 (1)); + Assert.assertEquals(705 , message.getUnpackedSint32 (1)); + Assert.assertEquals(706 , message.getUnpackedSint64 (1)); + Assert.assertEquals(707 , message.getUnpackedFixed32 (1)); + Assert.assertEquals(708 , message.getUnpackedFixed64 (1)); + Assert.assertEquals(709 , message.getUnpackedSfixed32(1)); + Assert.assertEquals(710 , message.getUnpackedSfixed64(1)); + Assert.assertEquals(711 , message.getUnpackedFloat (1), 0.0); + Assert.assertEquals(712 , message.getUnpackedDouble (1), 0.0); + Assert.assertEquals(false, message.getUnpackedBool (1)); + Assert.assertEquals(ForeignEnum.FOREIGN_BAZ, message.getUnpackedEnum(1)); + } + + // =================================================================== + // Like above, but for extensions + + // Java gets confused with things like assertEquals(int, Integer): it can't + // decide whether to call assertEquals(int, int) or assertEquals(Object, + // Object). So we define these methods to help it. + private static void assertEqualsExactType(int a, int b) { + Assert.assertEquals(a, b); + } + private static void assertEqualsExactType(long a, long b) { + Assert.assertEquals(a, b); + } + private static void assertEqualsExactType(float a, float b) { + Assert.assertEquals(a, b, 0.0); + } + private static void assertEqualsExactType(double a, double b) { + Assert.assertEquals(a, b, 0.0); + } + private static void assertEqualsExactType(boolean a, boolean b) { + Assert.assertEquals(a, b); + } + private static void assertEqualsExactType(String a, String b) { + Assert.assertEquals(a, b); + } + private static void assertEqualsExactType(ByteString a, ByteString b) { + Assert.assertEquals(a, b); + } + private static void assertEqualsExactType(TestAllTypes.NestedEnum a, + TestAllTypes.NestedEnum b) { + Assert.assertEquals(a, b); + } + private static void assertEqualsExactType(ForeignEnum a, ForeignEnum b) { + Assert.assertEquals(a, b); + } + private static void assertEqualsExactType(ImportEnum a, ImportEnum b) { + Assert.assertEquals(a, b); + } + private static void assertEqualsExactType(TestAllTypesLite.NestedEnum a, + TestAllTypesLite.NestedEnum b) { + Assert.assertEquals(a, b); + } + private static void assertEqualsExactType(ForeignEnumLite a, + ForeignEnumLite b) { + Assert.assertEquals(a, b); + } + private static void assertEqualsExactType(ImportEnumLite a, + ImportEnumLite b) { + Assert.assertEquals(a, b); + } + + /** + * Get an unmodifiable {@link ExtensionRegistry} containing all the + * extensions of {@code TestAllExtensions}. + */ + public static ExtensionRegistry getExtensionRegistry() { + ExtensionRegistry registry = ExtensionRegistry.newInstance(); + registerAllExtensions(registry); + return registry.getUnmodifiable(); + } + + public static ExtensionRegistryLite getExtensionRegistryLite() { + ExtensionRegistryLite registry = ExtensionRegistryLite.newInstance(); + registerAllExtensionsLite(registry); + return registry.getUnmodifiable(); + } + + /** + * Register all of {@code TestAllExtensions}'s extensions with the + * given {@link ExtensionRegistry}. + */ + public static void registerAllExtensions(ExtensionRegistry registry) { + UnittestProto.registerAllExtensions(registry); + registerAllExtensionsLite(registry); + } + + public static void registerAllExtensionsLite(ExtensionRegistryLite registry) { + UnittestLite.registerAllExtensions(registry); + } + + /** + * Set every field of {@code message} to the values expected by + * {@code assertAllExtensionsSet()}. + */ + public static void setAllExtensions(TestAllExtensions.Builder message) { + message.setExtension(optionalInt32Extension , 101); + message.setExtension(optionalInt64Extension , 102L); + message.setExtension(optionalUint32Extension , 103); + message.setExtension(optionalUint64Extension , 104L); + message.setExtension(optionalSint32Extension , 105); + message.setExtension(optionalSint64Extension , 106L); + message.setExtension(optionalFixed32Extension , 107); + message.setExtension(optionalFixed64Extension , 108L); + message.setExtension(optionalSfixed32Extension, 109); + message.setExtension(optionalSfixed64Extension, 110L); + message.setExtension(optionalFloatExtension , 111F); + message.setExtension(optionalDoubleExtension , 112D); + message.setExtension(optionalBoolExtension , true); + message.setExtension(optionalStringExtension , "115"); + message.setExtension(optionalBytesExtension , toBytes("116")); + + message.setExtension(optionalGroupExtension, + OptionalGroup_extension.newBuilder().setA(117).build()); + message.setExtension(optionalNestedMessageExtension, + TestAllTypes.NestedMessage.newBuilder().setBb(118).build()); + message.setExtension(optionalForeignMessageExtension, + ForeignMessage.newBuilder().setC(119).build()); + message.setExtension(optionalImportMessageExtension, + ImportMessage.newBuilder().setD(120).build()); + message.setExtension(optionalPublicImportMessageExtension, + PublicImportMessage.newBuilder().setE(126).build()); + message.setExtension(optionalLazyMessageExtension, + TestAllTypes.NestedMessage.newBuilder().setBb(127).build()); + + message.setExtension(optionalNestedEnumExtension, TestAllTypes.NestedEnum.BAZ); + message.setExtension(optionalForeignEnumExtension, ForeignEnum.FOREIGN_BAZ); + message.setExtension(optionalImportEnumExtension, ImportEnum.IMPORT_BAZ); + + message.setExtension(optionalStringPieceExtension, "124"); + message.setExtension(optionalCordExtension, "125"); + + // ----------------------------------------------------------------- + + message.addExtension(repeatedInt32Extension , 201); + message.addExtension(repeatedInt64Extension , 202L); + message.addExtension(repeatedUint32Extension , 203); + message.addExtension(repeatedUint64Extension , 204L); + message.addExtension(repeatedSint32Extension , 205); + message.addExtension(repeatedSint64Extension , 206L); + message.addExtension(repeatedFixed32Extension , 207); + message.addExtension(repeatedFixed64Extension , 208L); + message.addExtension(repeatedSfixed32Extension, 209); + message.addExtension(repeatedSfixed64Extension, 210L); + message.addExtension(repeatedFloatExtension , 211F); + message.addExtension(repeatedDoubleExtension , 212D); + message.addExtension(repeatedBoolExtension , true); + message.addExtension(repeatedStringExtension , "215"); + message.addExtension(repeatedBytesExtension , toBytes("216")); + + message.addExtension(repeatedGroupExtension, + RepeatedGroup_extension.newBuilder().setA(217).build()); + message.addExtension(repeatedNestedMessageExtension, + TestAllTypes.NestedMessage.newBuilder().setBb(218).build()); + message.addExtension(repeatedForeignMessageExtension, + ForeignMessage.newBuilder().setC(219).build()); + message.addExtension(repeatedImportMessageExtension, + ImportMessage.newBuilder().setD(220).build()); + message.addExtension(repeatedLazyMessageExtension, + TestAllTypes.NestedMessage.newBuilder().setBb(227).build()); + + message.addExtension(repeatedNestedEnumExtension, TestAllTypes.NestedEnum.BAR); + message.addExtension(repeatedForeignEnumExtension, ForeignEnum.FOREIGN_BAR); + message.addExtension(repeatedImportEnumExtension, ImportEnum.IMPORT_BAR); + + message.addExtension(repeatedStringPieceExtension, "224"); + message.addExtension(repeatedCordExtension, "225"); + + // Add a second one of each field. + message.addExtension(repeatedInt32Extension , 301); + message.addExtension(repeatedInt64Extension , 302L); + message.addExtension(repeatedUint32Extension , 303); + message.addExtension(repeatedUint64Extension , 304L); + message.addExtension(repeatedSint32Extension , 305); + message.addExtension(repeatedSint64Extension , 306L); + message.addExtension(repeatedFixed32Extension , 307); + message.addExtension(repeatedFixed64Extension , 308L); + message.addExtension(repeatedSfixed32Extension, 309); + message.addExtension(repeatedSfixed64Extension, 310L); + message.addExtension(repeatedFloatExtension , 311F); + message.addExtension(repeatedDoubleExtension , 312D); + message.addExtension(repeatedBoolExtension , false); + message.addExtension(repeatedStringExtension , "315"); + message.addExtension(repeatedBytesExtension , toBytes("316")); + + message.addExtension(repeatedGroupExtension, + RepeatedGroup_extension.newBuilder().setA(317).build()); + message.addExtension(repeatedNestedMessageExtension, + TestAllTypes.NestedMessage.newBuilder().setBb(318).build()); + message.addExtension(repeatedForeignMessageExtension, + ForeignMessage.newBuilder().setC(319).build()); + message.addExtension(repeatedImportMessageExtension, + ImportMessage.newBuilder().setD(320).build()); + message.addExtension(repeatedLazyMessageExtension, + TestAllTypes.NestedMessage.newBuilder().setBb(327).build()); + + message.addExtension(repeatedNestedEnumExtension, TestAllTypes.NestedEnum.BAZ); + message.addExtension(repeatedForeignEnumExtension, ForeignEnum.FOREIGN_BAZ); + message.addExtension(repeatedImportEnumExtension, ImportEnum.IMPORT_BAZ); + + message.addExtension(repeatedStringPieceExtension, "324"); + message.addExtension(repeatedCordExtension, "325"); + + // ----------------------------------------------------------------- + + message.setExtension(defaultInt32Extension , 401); + message.setExtension(defaultInt64Extension , 402L); + message.setExtension(defaultUint32Extension , 403); + message.setExtension(defaultUint64Extension , 404L); + message.setExtension(defaultSint32Extension , 405); + message.setExtension(defaultSint64Extension , 406L); + message.setExtension(defaultFixed32Extension , 407); + message.setExtension(defaultFixed64Extension , 408L); + message.setExtension(defaultSfixed32Extension, 409); + message.setExtension(defaultSfixed64Extension, 410L); + message.setExtension(defaultFloatExtension , 411F); + message.setExtension(defaultDoubleExtension , 412D); + message.setExtension(defaultBoolExtension , false); + message.setExtension(defaultStringExtension , "415"); + message.setExtension(defaultBytesExtension , toBytes("416")); + + message.setExtension(defaultNestedEnumExtension, TestAllTypes.NestedEnum.FOO); + message.setExtension(defaultForeignEnumExtension, ForeignEnum.FOREIGN_FOO); + message.setExtension(defaultImportEnumExtension, ImportEnum.IMPORT_FOO); + + message.setExtension(defaultStringPieceExtension, "424"); + message.setExtension(defaultCordExtension, "425"); + + message.setExtension(oneofUint32Extension, 601); + message.setExtension(oneofNestedMessageExtension, + TestAllTypes.NestedMessage.newBuilder().setBb(602).build()); + message.setExtension(oneofStringExtension, "603"); + message.setExtension(oneofBytesExtension, toBytes("604")); + } + + // ------------------------------------------------------------------- + + /** + * Modify the repeated extensions of {@code message} to contain the values + * expected by {@code assertRepeatedExtensionsModified()}. + */ + public static void modifyRepeatedExtensions( + TestAllExtensions.Builder message) { + message.setExtension(repeatedInt32Extension , 1, 501); + message.setExtension(repeatedInt64Extension , 1, 502L); + message.setExtension(repeatedUint32Extension , 1, 503); + message.setExtension(repeatedUint64Extension , 1, 504L); + message.setExtension(repeatedSint32Extension , 1, 505); + message.setExtension(repeatedSint64Extension , 1, 506L); + message.setExtension(repeatedFixed32Extension , 1, 507); + message.setExtension(repeatedFixed64Extension , 1, 508L); + message.setExtension(repeatedSfixed32Extension, 1, 509); + message.setExtension(repeatedSfixed64Extension, 1, 510L); + message.setExtension(repeatedFloatExtension , 1, 511F); + message.setExtension(repeatedDoubleExtension , 1, 512D); + message.setExtension(repeatedBoolExtension , 1, true); + message.setExtension(repeatedStringExtension , 1, "515"); + message.setExtension(repeatedBytesExtension , 1, toBytes("516")); + + message.setExtension(repeatedGroupExtension, 1, + RepeatedGroup_extension.newBuilder().setA(517).build()); + message.setExtension(repeatedNestedMessageExtension, 1, + TestAllTypes.NestedMessage.newBuilder().setBb(518).build()); + message.setExtension(repeatedForeignMessageExtension, 1, + ForeignMessage.newBuilder().setC(519).build()); + message.setExtension(repeatedImportMessageExtension, 1, + ImportMessage.newBuilder().setD(520).build()); + message.setExtension(repeatedLazyMessageExtension, 1, + TestAllTypes.NestedMessage.newBuilder().setBb(527).build()); + + message.setExtension(repeatedNestedEnumExtension , 1, TestAllTypes.NestedEnum.FOO); + message.setExtension(repeatedForeignEnumExtension, 1, ForeignEnum.FOREIGN_FOO); + message.setExtension(repeatedImportEnumExtension , 1, ImportEnum.IMPORT_FOO); + + message.setExtension(repeatedStringPieceExtension, 1, "524"); + message.setExtension(repeatedCordExtension, 1, "525"); + } + + // ------------------------------------------------------------------- + + /** + * Assert (using {@code junit.framework.Assert}} that all extensions of + * {@code message} are set to the values assigned by {@code setAllExtensions}. + */ + public static void assertAllExtensionsSet( + TestAllExtensionsOrBuilder message) { + Assert.assertTrue(message.hasExtension(optionalInt32Extension )); + Assert.assertTrue(message.hasExtension(optionalInt64Extension )); + Assert.assertTrue(message.hasExtension(optionalUint32Extension )); + Assert.assertTrue(message.hasExtension(optionalUint64Extension )); + Assert.assertTrue(message.hasExtension(optionalSint32Extension )); + Assert.assertTrue(message.hasExtension(optionalSint64Extension )); + Assert.assertTrue(message.hasExtension(optionalFixed32Extension )); + Assert.assertTrue(message.hasExtension(optionalFixed64Extension )); + Assert.assertTrue(message.hasExtension(optionalSfixed32Extension)); + Assert.assertTrue(message.hasExtension(optionalSfixed64Extension)); + Assert.assertTrue(message.hasExtension(optionalFloatExtension )); + Assert.assertTrue(message.hasExtension(optionalDoubleExtension )); + Assert.assertTrue(message.hasExtension(optionalBoolExtension )); + Assert.assertTrue(message.hasExtension(optionalStringExtension )); + Assert.assertTrue(message.hasExtension(optionalBytesExtension )); + + Assert.assertTrue(message.hasExtension(optionalGroupExtension )); + Assert.assertTrue(message.hasExtension(optionalNestedMessageExtension )); + Assert.assertTrue(message.hasExtension(optionalForeignMessageExtension)); + Assert.assertTrue(message.hasExtension(optionalImportMessageExtension )); + + Assert.assertTrue(message.getExtension(optionalGroupExtension ).hasA()); + Assert.assertTrue(message.getExtension(optionalNestedMessageExtension ).hasBb()); + Assert.assertTrue(message.getExtension(optionalForeignMessageExtension).hasC()); + Assert.assertTrue(message.getExtension(optionalImportMessageExtension ).hasD()); + + Assert.assertTrue(message.hasExtension(optionalNestedEnumExtension )); + Assert.assertTrue(message.hasExtension(optionalForeignEnumExtension)); + Assert.assertTrue(message.hasExtension(optionalImportEnumExtension )); + + Assert.assertTrue(message.hasExtension(optionalStringPieceExtension)); + Assert.assertTrue(message.hasExtension(optionalCordExtension)); + + assertEqualsExactType(101 , message.getExtension(optionalInt32Extension )); + assertEqualsExactType(102L , message.getExtension(optionalInt64Extension )); + assertEqualsExactType(103 , message.getExtension(optionalUint32Extension )); + assertEqualsExactType(104L , message.getExtension(optionalUint64Extension )); + assertEqualsExactType(105 , message.getExtension(optionalSint32Extension )); + assertEqualsExactType(106L , message.getExtension(optionalSint64Extension )); + assertEqualsExactType(107 , message.getExtension(optionalFixed32Extension )); + assertEqualsExactType(108L , message.getExtension(optionalFixed64Extension )); + assertEqualsExactType(109 , message.getExtension(optionalSfixed32Extension)); + assertEqualsExactType(110L , message.getExtension(optionalSfixed64Extension)); + assertEqualsExactType(111F , message.getExtension(optionalFloatExtension )); + assertEqualsExactType(112D , message.getExtension(optionalDoubleExtension )); + assertEqualsExactType(true , message.getExtension(optionalBoolExtension )); + assertEqualsExactType("115", message.getExtension(optionalStringExtension )); + assertEqualsExactType(toBytes("116"), message.getExtension(optionalBytesExtension)); + + assertEqualsExactType(117, message.getExtension(optionalGroupExtension ).getA()); + assertEqualsExactType(118, message.getExtension(optionalNestedMessageExtension ).getBb()); + assertEqualsExactType(119, message.getExtension(optionalForeignMessageExtension ).getC()); + assertEqualsExactType(120, message.getExtension(optionalImportMessageExtension ).getD()); + assertEqualsExactType(126, message.getExtension(optionalPublicImportMessageExtension).getE()); + assertEqualsExactType(127, message.getExtension(optionalLazyMessageExtension ).getBb()); + + assertEqualsExactType(TestAllTypes.NestedEnum.BAZ, + message.getExtension(optionalNestedEnumExtension)); + assertEqualsExactType(ForeignEnum.FOREIGN_BAZ, + message.getExtension(optionalForeignEnumExtension)); + assertEqualsExactType(ImportEnum.IMPORT_BAZ, + message.getExtension(optionalImportEnumExtension)); + + assertEqualsExactType("124", message.getExtension(optionalStringPieceExtension)); + assertEqualsExactType("125", message.getExtension(optionalCordExtension)); + + // ----------------------------------------------------------------- + + Assert.assertEquals(2, message.getExtensionCount(repeatedInt32Extension )); + Assert.assertEquals(2, message.getExtensionCount(repeatedInt64Extension )); + Assert.assertEquals(2, message.getExtensionCount(repeatedUint32Extension )); + Assert.assertEquals(2, message.getExtensionCount(repeatedUint64Extension )); + Assert.assertEquals(2, message.getExtensionCount(repeatedSint32Extension )); + Assert.assertEquals(2, message.getExtensionCount(repeatedSint64Extension )); + Assert.assertEquals(2, message.getExtensionCount(repeatedFixed32Extension )); + Assert.assertEquals(2, message.getExtensionCount(repeatedFixed64Extension )); + Assert.assertEquals(2, message.getExtensionCount(repeatedSfixed32Extension)); + Assert.assertEquals(2, message.getExtensionCount(repeatedSfixed64Extension)); + Assert.assertEquals(2, message.getExtensionCount(repeatedFloatExtension )); + Assert.assertEquals(2, message.getExtensionCount(repeatedDoubleExtension )); + Assert.assertEquals(2, message.getExtensionCount(repeatedBoolExtension )); + Assert.assertEquals(2, message.getExtensionCount(repeatedStringExtension )); + Assert.assertEquals(2, message.getExtensionCount(repeatedBytesExtension )); + + Assert.assertEquals(2, message.getExtensionCount(repeatedGroupExtension )); + Assert.assertEquals(2, message.getExtensionCount(repeatedNestedMessageExtension )); + Assert.assertEquals(2, message.getExtensionCount(repeatedForeignMessageExtension)); + Assert.assertEquals(2, message.getExtensionCount(repeatedImportMessageExtension )); + Assert.assertEquals(2, message.getExtensionCount(repeatedLazyMessageExtension )); + Assert.assertEquals(2, message.getExtensionCount(repeatedNestedEnumExtension )); + Assert.assertEquals(2, message.getExtensionCount(repeatedForeignEnumExtension )); + Assert.assertEquals(2, message.getExtensionCount(repeatedImportEnumExtension )); + + Assert.assertEquals(2, message.getExtensionCount(repeatedStringPieceExtension)); + Assert.assertEquals(2, message.getExtensionCount(repeatedCordExtension)); + + assertEqualsExactType(201 , message.getExtension(repeatedInt32Extension , 0)); + assertEqualsExactType(202L , message.getExtension(repeatedInt64Extension , 0)); + assertEqualsExactType(203 , message.getExtension(repeatedUint32Extension , 0)); + assertEqualsExactType(204L , message.getExtension(repeatedUint64Extension , 0)); + assertEqualsExactType(205 , message.getExtension(repeatedSint32Extension , 0)); + assertEqualsExactType(206L , message.getExtension(repeatedSint64Extension , 0)); + assertEqualsExactType(207 , message.getExtension(repeatedFixed32Extension , 0)); + assertEqualsExactType(208L , message.getExtension(repeatedFixed64Extension , 0)); + assertEqualsExactType(209 , message.getExtension(repeatedSfixed32Extension, 0)); + assertEqualsExactType(210L , message.getExtension(repeatedSfixed64Extension, 0)); + assertEqualsExactType(211F , message.getExtension(repeatedFloatExtension , 0)); + assertEqualsExactType(212D , message.getExtension(repeatedDoubleExtension , 0)); + assertEqualsExactType(true , message.getExtension(repeatedBoolExtension , 0)); + assertEqualsExactType("215", message.getExtension(repeatedStringExtension , 0)); + assertEqualsExactType(toBytes("216"), message.getExtension(repeatedBytesExtension, 0)); + + assertEqualsExactType(217, message.getExtension(repeatedGroupExtension , 0).getA()); + assertEqualsExactType(218, message.getExtension(repeatedNestedMessageExtension , 0).getBb()); + assertEqualsExactType(219, message.getExtension(repeatedForeignMessageExtension, 0).getC()); + assertEqualsExactType(220, message.getExtension(repeatedImportMessageExtension , 0).getD()); + assertEqualsExactType(227, message.getExtension(repeatedLazyMessageExtension , 0).getBb()); + + assertEqualsExactType(TestAllTypes.NestedEnum.BAR, + message.getExtension(repeatedNestedEnumExtension, 0)); + assertEqualsExactType(ForeignEnum.FOREIGN_BAR, + message.getExtension(repeatedForeignEnumExtension, 0)); + assertEqualsExactType(ImportEnum.IMPORT_BAR, + message.getExtension(repeatedImportEnumExtension, 0)); + + assertEqualsExactType("224", message.getExtension(repeatedStringPieceExtension, 0)); + assertEqualsExactType("225", message.getExtension(repeatedCordExtension, 0)); + + assertEqualsExactType(301 , message.getExtension(repeatedInt32Extension , 1)); + assertEqualsExactType(302L , message.getExtension(repeatedInt64Extension , 1)); + assertEqualsExactType(303 , message.getExtension(repeatedUint32Extension , 1)); + assertEqualsExactType(304L , message.getExtension(repeatedUint64Extension , 1)); + assertEqualsExactType(305 , message.getExtension(repeatedSint32Extension , 1)); + assertEqualsExactType(306L , message.getExtension(repeatedSint64Extension , 1)); + assertEqualsExactType(307 , message.getExtension(repeatedFixed32Extension , 1)); + assertEqualsExactType(308L , message.getExtension(repeatedFixed64Extension , 1)); + assertEqualsExactType(309 , message.getExtension(repeatedSfixed32Extension, 1)); + assertEqualsExactType(310L , message.getExtension(repeatedSfixed64Extension, 1)); + assertEqualsExactType(311F , message.getExtension(repeatedFloatExtension , 1)); + assertEqualsExactType(312D , message.getExtension(repeatedDoubleExtension , 1)); + assertEqualsExactType(false, message.getExtension(repeatedBoolExtension , 1)); + assertEqualsExactType("315", message.getExtension(repeatedStringExtension , 1)); + assertEqualsExactType(toBytes("316"), message.getExtension(repeatedBytesExtension, 1)); + + assertEqualsExactType(317, message.getExtension(repeatedGroupExtension , 1).getA()); + assertEqualsExactType(318, message.getExtension(repeatedNestedMessageExtension , 1).getBb()); + assertEqualsExactType(319, message.getExtension(repeatedForeignMessageExtension, 1).getC()); + assertEqualsExactType(320, message.getExtension(repeatedImportMessageExtension , 1).getD()); + assertEqualsExactType(327, message.getExtension(repeatedLazyMessageExtension , 1).getBb()); + + assertEqualsExactType(TestAllTypes.NestedEnum.BAZ, + message.getExtension(repeatedNestedEnumExtension, 1)); + assertEqualsExactType(ForeignEnum.FOREIGN_BAZ, + message.getExtension(repeatedForeignEnumExtension, 1)); + assertEqualsExactType(ImportEnum.IMPORT_BAZ, + message.getExtension(repeatedImportEnumExtension, 1)); + + assertEqualsExactType("324", message.getExtension(repeatedStringPieceExtension, 1)); + assertEqualsExactType("325", message.getExtension(repeatedCordExtension, 1)); + + // ----------------------------------------------------------------- + + Assert.assertTrue(message.hasExtension(defaultInt32Extension )); + Assert.assertTrue(message.hasExtension(defaultInt64Extension )); + Assert.assertTrue(message.hasExtension(defaultUint32Extension )); + Assert.assertTrue(message.hasExtension(defaultUint64Extension )); + Assert.assertTrue(message.hasExtension(defaultSint32Extension )); + Assert.assertTrue(message.hasExtension(defaultSint64Extension )); + Assert.assertTrue(message.hasExtension(defaultFixed32Extension )); + Assert.assertTrue(message.hasExtension(defaultFixed64Extension )); + Assert.assertTrue(message.hasExtension(defaultSfixed32Extension)); + Assert.assertTrue(message.hasExtension(defaultSfixed64Extension)); + Assert.assertTrue(message.hasExtension(defaultFloatExtension )); + Assert.assertTrue(message.hasExtension(defaultDoubleExtension )); + Assert.assertTrue(message.hasExtension(defaultBoolExtension )); + Assert.assertTrue(message.hasExtension(defaultStringExtension )); + Assert.assertTrue(message.hasExtension(defaultBytesExtension )); + + Assert.assertTrue(message.hasExtension(defaultNestedEnumExtension )); + Assert.assertTrue(message.hasExtension(defaultForeignEnumExtension)); + Assert.assertTrue(message.hasExtension(defaultImportEnumExtension )); + + Assert.assertTrue(message.hasExtension(defaultStringPieceExtension)); + Assert.assertTrue(message.hasExtension(defaultCordExtension)); + + assertEqualsExactType(401 , message.getExtension(defaultInt32Extension )); + assertEqualsExactType(402L , message.getExtension(defaultInt64Extension )); + assertEqualsExactType(403 , message.getExtension(defaultUint32Extension )); + assertEqualsExactType(404L , message.getExtension(defaultUint64Extension )); + assertEqualsExactType(405 , message.getExtension(defaultSint32Extension )); + assertEqualsExactType(406L , message.getExtension(defaultSint64Extension )); + assertEqualsExactType(407 , message.getExtension(defaultFixed32Extension )); + assertEqualsExactType(408L , message.getExtension(defaultFixed64Extension )); + assertEqualsExactType(409 , message.getExtension(defaultSfixed32Extension)); + assertEqualsExactType(410L , message.getExtension(defaultSfixed64Extension)); + assertEqualsExactType(411F , message.getExtension(defaultFloatExtension )); + assertEqualsExactType(412D , message.getExtension(defaultDoubleExtension )); + assertEqualsExactType(false, message.getExtension(defaultBoolExtension )); + assertEqualsExactType("415", message.getExtension(defaultStringExtension )); + assertEqualsExactType(toBytes("416"), message.getExtension(defaultBytesExtension)); + + assertEqualsExactType(TestAllTypes.NestedEnum.FOO, + message.getExtension(defaultNestedEnumExtension )); + assertEqualsExactType(ForeignEnum.FOREIGN_FOO, + message.getExtension(defaultForeignEnumExtension)); + assertEqualsExactType(ImportEnum.IMPORT_FOO, + message.getExtension(defaultImportEnumExtension)); + + assertEqualsExactType("424", message.getExtension(defaultStringPieceExtension)); + assertEqualsExactType("425", message.getExtension(defaultCordExtension)); + + Assert.assertTrue(message.hasExtension(oneofBytesExtension)); + + assertEqualsExactType(toBytes("604"), message.getExtension(oneofBytesExtension)); + } + + // ------------------------------------------------------------------- + + /** + * Assert (using {@code junit.framework.Assert}} that all extensions of + * {@code message} are cleared, and that getting the extensions returns their + * default values. + */ + public static void assertExtensionsClear(TestAllExtensionsOrBuilder message) { + // hasBlah() should initially be false for all optional fields. + Assert.assertFalse(message.hasExtension(optionalInt32Extension )); + Assert.assertFalse(message.hasExtension(optionalInt64Extension )); + Assert.assertFalse(message.hasExtension(optionalUint32Extension )); + Assert.assertFalse(message.hasExtension(optionalUint64Extension )); + Assert.assertFalse(message.hasExtension(optionalSint32Extension )); + Assert.assertFalse(message.hasExtension(optionalSint64Extension )); + Assert.assertFalse(message.hasExtension(optionalFixed32Extension )); + Assert.assertFalse(message.hasExtension(optionalFixed64Extension )); + Assert.assertFalse(message.hasExtension(optionalSfixed32Extension)); + Assert.assertFalse(message.hasExtension(optionalSfixed64Extension)); + Assert.assertFalse(message.hasExtension(optionalFloatExtension )); + Assert.assertFalse(message.hasExtension(optionalDoubleExtension )); + Assert.assertFalse(message.hasExtension(optionalBoolExtension )); + Assert.assertFalse(message.hasExtension(optionalStringExtension )); + Assert.assertFalse(message.hasExtension(optionalBytesExtension )); + + Assert.assertFalse(message.hasExtension(optionalGroupExtension )); + Assert.assertFalse(message.hasExtension(optionalNestedMessageExtension )); + Assert.assertFalse(message.hasExtension(optionalForeignMessageExtension)); + Assert.assertFalse(message.hasExtension(optionalImportMessageExtension )); + + Assert.assertFalse(message.hasExtension(optionalNestedEnumExtension )); + Assert.assertFalse(message.hasExtension(optionalForeignEnumExtension)); + Assert.assertFalse(message.hasExtension(optionalImportEnumExtension )); + + Assert.assertFalse(message.hasExtension(optionalStringPieceExtension)); + Assert.assertFalse(message.hasExtension(optionalCordExtension)); + + // Optional fields without defaults are set to zero or something like it. + assertEqualsExactType(0 , message.getExtension(optionalInt32Extension )); + assertEqualsExactType(0L , message.getExtension(optionalInt64Extension )); + assertEqualsExactType(0 , message.getExtension(optionalUint32Extension )); + assertEqualsExactType(0L , message.getExtension(optionalUint64Extension )); + assertEqualsExactType(0 , message.getExtension(optionalSint32Extension )); + assertEqualsExactType(0L , message.getExtension(optionalSint64Extension )); + assertEqualsExactType(0 , message.getExtension(optionalFixed32Extension )); + assertEqualsExactType(0L , message.getExtension(optionalFixed64Extension )); + assertEqualsExactType(0 , message.getExtension(optionalSfixed32Extension)); + assertEqualsExactType(0L , message.getExtension(optionalSfixed64Extension)); + assertEqualsExactType(0F , message.getExtension(optionalFloatExtension )); + assertEqualsExactType(0D , message.getExtension(optionalDoubleExtension )); + assertEqualsExactType(false, message.getExtension(optionalBoolExtension )); + assertEqualsExactType("" , message.getExtension(optionalStringExtension )); + assertEqualsExactType(ByteString.EMPTY, message.getExtension(optionalBytesExtension)); + + // Embedded messages should also be clear. + Assert.assertFalse(message.getExtension(optionalGroupExtension ).hasA()); + Assert.assertFalse(message.getExtension(optionalNestedMessageExtension ).hasBb()); + Assert.assertFalse(message.getExtension(optionalForeignMessageExtension).hasC()); + Assert.assertFalse(message.getExtension(optionalImportMessageExtension ).hasD()); + + assertEqualsExactType(0, message.getExtension(optionalGroupExtension ).getA()); + assertEqualsExactType(0, message.getExtension(optionalNestedMessageExtension ).getBb()); + assertEqualsExactType(0, message.getExtension(optionalForeignMessageExtension).getC()); + assertEqualsExactType(0, message.getExtension(optionalImportMessageExtension ).getD()); + + // Enums without defaults are set to the first value in the enum. + assertEqualsExactType(TestAllTypes.NestedEnum.FOO, + message.getExtension(optionalNestedEnumExtension )); + assertEqualsExactType(ForeignEnum.FOREIGN_FOO, + message.getExtension(optionalForeignEnumExtension)); + assertEqualsExactType(ImportEnum.IMPORT_FOO, + message.getExtension(optionalImportEnumExtension)); + + assertEqualsExactType("", message.getExtension(optionalStringPieceExtension)); + assertEqualsExactType("", message.getExtension(optionalCordExtension)); + + // Repeated fields are empty. + Assert.assertEquals(0, message.getExtensionCount(repeatedInt32Extension )); + Assert.assertEquals(0, message.getExtensionCount(repeatedInt64Extension )); + Assert.assertEquals(0, message.getExtensionCount(repeatedUint32Extension )); + Assert.assertEquals(0, message.getExtensionCount(repeatedUint64Extension )); + Assert.assertEquals(0, message.getExtensionCount(repeatedSint32Extension )); + Assert.assertEquals(0, message.getExtensionCount(repeatedSint64Extension )); + Assert.assertEquals(0, message.getExtensionCount(repeatedFixed32Extension )); + Assert.assertEquals(0, message.getExtensionCount(repeatedFixed64Extension )); + Assert.assertEquals(0, message.getExtensionCount(repeatedSfixed32Extension)); + Assert.assertEquals(0, message.getExtensionCount(repeatedSfixed64Extension)); + Assert.assertEquals(0, message.getExtensionCount(repeatedFloatExtension )); + Assert.assertEquals(0, message.getExtensionCount(repeatedDoubleExtension )); + Assert.assertEquals(0, message.getExtensionCount(repeatedBoolExtension )); + Assert.assertEquals(0, message.getExtensionCount(repeatedStringExtension )); + Assert.assertEquals(0, message.getExtensionCount(repeatedBytesExtension )); + + Assert.assertEquals(0, message.getExtensionCount(repeatedGroupExtension )); + Assert.assertEquals(0, message.getExtensionCount(repeatedNestedMessageExtension )); + Assert.assertEquals(0, message.getExtensionCount(repeatedForeignMessageExtension)); + Assert.assertEquals(0, message.getExtensionCount(repeatedImportMessageExtension )); + Assert.assertEquals(0, message.getExtensionCount(repeatedLazyMessageExtension )); + Assert.assertEquals(0, message.getExtensionCount(repeatedNestedEnumExtension )); + Assert.assertEquals(0, message.getExtensionCount(repeatedForeignEnumExtension )); + Assert.assertEquals(0, message.getExtensionCount(repeatedImportEnumExtension )); + + Assert.assertEquals(0, message.getExtensionCount(repeatedStringPieceExtension)); + Assert.assertEquals(0, message.getExtensionCount(repeatedCordExtension)); + + // Repeated fields are empty via getExtension().size(). + Assert.assertEquals(0, message.getExtension(repeatedInt32Extension ).size()); + Assert.assertEquals(0, message.getExtension(repeatedInt64Extension ).size()); + Assert.assertEquals(0, message.getExtension(repeatedUint32Extension ).size()); + Assert.assertEquals(0, message.getExtension(repeatedUint64Extension ).size()); + Assert.assertEquals(0, message.getExtension(repeatedSint32Extension ).size()); + Assert.assertEquals(0, message.getExtension(repeatedSint64Extension ).size()); + Assert.assertEquals(0, message.getExtension(repeatedFixed32Extension ).size()); + Assert.assertEquals(0, message.getExtension(repeatedFixed64Extension ).size()); + Assert.assertEquals(0, message.getExtension(repeatedSfixed32Extension).size()); + Assert.assertEquals(0, message.getExtension(repeatedSfixed64Extension).size()); + Assert.assertEquals(0, message.getExtension(repeatedFloatExtension ).size()); + Assert.assertEquals(0, message.getExtension(repeatedDoubleExtension ).size()); + Assert.assertEquals(0, message.getExtension(repeatedBoolExtension ).size()); + Assert.assertEquals(0, message.getExtension(repeatedStringExtension ).size()); + Assert.assertEquals(0, message.getExtension(repeatedBytesExtension ).size()); + + Assert.assertEquals(0, message.getExtension(repeatedGroupExtension ).size()); + Assert.assertEquals(0, message.getExtension(repeatedNestedMessageExtension ).size()); + Assert.assertEquals(0, message.getExtension(repeatedForeignMessageExtension).size()); + Assert.assertEquals(0, message.getExtension(repeatedImportMessageExtension ).size()); + Assert.assertEquals(0, message.getExtension(repeatedLazyMessageExtension ).size()); + Assert.assertEquals(0, message.getExtension(repeatedNestedEnumExtension ).size()); + Assert.assertEquals(0, message.getExtension(repeatedForeignEnumExtension ).size()); + Assert.assertEquals(0, message.getExtension(repeatedImportEnumExtension ).size()); + + Assert.assertEquals(0, message.getExtension(repeatedStringPieceExtension).size()); + Assert.assertEquals(0, message.getExtension(repeatedCordExtension).size()); + + // hasBlah() should also be false for all default fields. + Assert.assertFalse(message.hasExtension(defaultInt32Extension )); + Assert.assertFalse(message.hasExtension(defaultInt64Extension )); + Assert.assertFalse(message.hasExtension(defaultUint32Extension )); + Assert.assertFalse(message.hasExtension(defaultUint64Extension )); + Assert.assertFalse(message.hasExtension(defaultSint32Extension )); + Assert.assertFalse(message.hasExtension(defaultSint64Extension )); + Assert.assertFalse(message.hasExtension(defaultFixed32Extension )); + Assert.assertFalse(message.hasExtension(defaultFixed64Extension )); + Assert.assertFalse(message.hasExtension(defaultSfixed32Extension)); + Assert.assertFalse(message.hasExtension(defaultSfixed64Extension)); + Assert.assertFalse(message.hasExtension(defaultFloatExtension )); + Assert.assertFalse(message.hasExtension(defaultDoubleExtension )); + Assert.assertFalse(message.hasExtension(defaultBoolExtension )); + Assert.assertFalse(message.hasExtension(defaultStringExtension )); + Assert.assertFalse(message.hasExtension(defaultBytesExtension )); + + Assert.assertFalse(message.hasExtension(defaultNestedEnumExtension )); + Assert.assertFalse(message.hasExtension(defaultForeignEnumExtension)); + Assert.assertFalse(message.hasExtension(defaultImportEnumExtension )); + + Assert.assertFalse(message.hasExtension(defaultStringPieceExtension)); + Assert.assertFalse(message.hasExtension(defaultCordExtension)); + + // Fields with defaults have their default values (duh). + assertEqualsExactType( 41 , message.getExtension(defaultInt32Extension )); + assertEqualsExactType( 42L , message.getExtension(defaultInt64Extension )); + assertEqualsExactType( 43 , message.getExtension(defaultUint32Extension )); + assertEqualsExactType( 44L , message.getExtension(defaultUint64Extension )); + assertEqualsExactType(-45 , message.getExtension(defaultSint32Extension )); + assertEqualsExactType( 46L , message.getExtension(defaultSint64Extension )); + assertEqualsExactType( 47 , message.getExtension(defaultFixed32Extension )); + assertEqualsExactType( 48L , message.getExtension(defaultFixed64Extension )); + assertEqualsExactType( 49 , message.getExtension(defaultSfixed32Extension)); + assertEqualsExactType(-50L , message.getExtension(defaultSfixed64Extension)); + assertEqualsExactType( 51.5F , message.getExtension(defaultFloatExtension )); + assertEqualsExactType( 52e3D , message.getExtension(defaultDoubleExtension )); + assertEqualsExactType(true , message.getExtension(defaultBoolExtension )); + assertEqualsExactType("hello", message.getExtension(defaultStringExtension )); + assertEqualsExactType(toBytes("world"), message.getExtension(defaultBytesExtension)); + + assertEqualsExactType(TestAllTypes.NestedEnum.BAR, + message.getExtension(defaultNestedEnumExtension )); + assertEqualsExactType(ForeignEnum.FOREIGN_BAR, + message.getExtension(defaultForeignEnumExtension)); + assertEqualsExactType(ImportEnum.IMPORT_BAR, + message.getExtension(defaultImportEnumExtension)); + + assertEqualsExactType("abc", message.getExtension(defaultStringPieceExtension)); + assertEqualsExactType("123", message.getExtension(defaultCordExtension)); + + Assert.assertFalse(message.hasExtension(oneofUint32Extension)); + Assert.assertFalse(message.hasExtension(oneofNestedMessageExtension)); + Assert.assertFalse(message.hasExtension(oneofStringExtension)); + Assert.assertFalse(message.hasExtension(oneofBytesExtension)); + } + + // ------------------------------------------------------------------- + + /** + * Assert (using {@code junit.framework.Assert}} that all extensions of + * {@code message} are set to the values assigned by {@code setAllExtensions} + * followed by {@code modifyRepeatedExtensions}. + */ + public static void assertRepeatedExtensionsModified( + TestAllExtensionsOrBuilder message) { + // ModifyRepeatedFields only sets the second repeated element of each + // field. In addition to verifying this, we also verify that the first + // element and size were *not* modified. + Assert.assertEquals(2, message.getExtensionCount(repeatedInt32Extension )); + Assert.assertEquals(2, message.getExtensionCount(repeatedInt64Extension )); + Assert.assertEquals(2, message.getExtensionCount(repeatedUint32Extension )); + Assert.assertEquals(2, message.getExtensionCount(repeatedUint64Extension )); + Assert.assertEquals(2, message.getExtensionCount(repeatedSint32Extension )); + Assert.assertEquals(2, message.getExtensionCount(repeatedSint64Extension )); + Assert.assertEquals(2, message.getExtensionCount(repeatedFixed32Extension )); + Assert.assertEquals(2, message.getExtensionCount(repeatedFixed64Extension )); + Assert.assertEquals(2, message.getExtensionCount(repeatedSfixed32Extension)); + Assert.assertEquals(2, message.getExtensionCount(repeatedSfixed64Extension)); + Assert.assertEquals(2, message.getExtensionCount(repeatedFloatExtension )); + Assert.assertEquals(2, message.getExtensionCount(repeatedDoubleExtension )); + Assert.assertEquals(2, message.getExtensionCount(repeatedBoolExtension )); + Assert.assertEquals(2, message.getExtensionCount(repeatedStringExtension )); + Assert.assertEquals(2, message.getExtensionCount(repeatedBytesExtension )); + + Assert.assertEquals(2, message.getExtensionCount(repeatedGroupExtension )); + Assert.assertEquals(2, message.getExtensionCount(repeatedNestedMessageExtension )); + Assert.assertEquals(2, message.getExtensionCount(repeatedForeignMessageExtension)); + Assert.assertEquals(2, message.getExtensionCount(repeatedImportMessageExtension )); + Assert.assertEquals(2, message.getExtensionCount(repeatedLazyMessageExtension )); + Assert.assertEquals(2, message.getExtensionCount(repeatedNestedEnumExtension )); + Assert.assertEquals(2, message.getExtensionCount(repeatedForeignEnumExtension )); + Assert.assertEquals(2, message.getExtensionCount(repeatedImportEnumExtension )); + + Assert.assertEquals(2, message.getExtensionCount(repeatedStringPieceExtension)); + Assert.assertEquals(2, message.getExtensionCount(repeatedCordExtension)); + + assertEqualsExactType(201 , message.getExtension(repeatedInt32Extension , 0)); + assertEqualsExactType(202L , message.getExtension(repeatedInt64Extension , 0)); + assertEqualsExactType(203 , message.getExtension(repeatedUint32Extension , 0)); + assertEqualsExactType(204L , message.getExtension(repeatedUint64Extension , 0)); + assertEqualsExactType(205 , message.getExtension(repeatedSint32Extension , 0)); + assertEqualsExactType(206L , message.getExtension(repeatedSint64Extension , 0)); + assertEqualsExactType(207 , message.getExtension(repeatedFixed32Extension , 0)); + assertEqualsExactType(208L , message.getExtension(repeatedFixed64Extension , 0)); + assertEqualsExactType(209 , message.getExtension(repeatedSfixed32Extension, 0)); + assertEqualsExactType(210L , message.getExtension(repeatedSfixed64Extension, 0)); + assertEqualsExactType(211F , message.getExtension(repeatedFloatExtension , 0)); + assertEqualsExactType(212D , message.getExtension(repeatedDoubleExtension , 0)); + assertEqualsExactType(true , message.getExtension(repeatedBoolExtension , 0)); + assertEqualsExactType("215", message.getExtension(repeatedStringExtension , 0)); + assertEqualsExactType(toBytes("216"), message.getExtension(repeatedBytesExtension, 0)); + + assertEqualsExactType(217, message.getExtension(repeatedGroupExtension , 0).getA()); + assertEqualsExactType(218, message.getExtension(repeatedNestedMessageExtension , 0).getBb()); + assertEqualsExactType(219, message.getExtension(repeatedForeignMessageExtension, 0).getC()); + assertEqualsExactType(220, message.getExtension(repeatedImportMessageExtension , 0).getD()); + assertEqualsExactType(227, message.getExtension(repeatedLazyMessageExtension , 0).getBb()); + + assertEqualsExactType(TestAllTypes.NestedEnum.BAR, + message.getExtension(repeatedNestedEnumExtension, 0)); + assertEqualsExactType(ForeignEnum.FOREIGN_BAR, + message.getExtension(repeatedForeignEnumExtension, 0)); + assertEqualsExactType(ImportEnum.IMPORT_BAR, + message.getExtension(repeatedImportEnumExtension, 0)); + + assertEqualsExactType("224", message.getExtension(repeatedStringPieceExtension, 0)); + assertEqualsExactType("225", message.getExtension(repeatedCordExtension, 0)); + + // Actually verify the second (modified) elements now. + assertEqualsExactType(501 , message.getExtension(repeatedInt32Extension , 1)); + assertEqualsExactType(502L , message.getExtension(repeatedInt64Extension , 1)); + assertEqualsExactType(503 , message.getExtension(repeatedUint32Extension , 1)); + assertEqualsExactType(504L , message.getExtension(repeatedUint64Extension , 1)); + assertEqualsExactType(505 , message.getExtension(repeatedSint32Extension , 1)); + assertEqualsExactType(506L , message.getExtension(repeatedSint64Extension , 1)); + assertEqualsExactType(507 , message.getExtension(repeatedFixed32Extension , 1)); + assertEqualsExactType(508L , message.getExtension(repeatedFixed64Extension , 1)); + assertEqualsExactType(509 , message.getExtension(repeatedSfixed32Extension, 1)); + assertEqualsExactType(510L , message.getExtension(repeatedSfixed64Extension, 1)); + assertEqualsExactType(511F , message.getExtension(repeatedFloatExtension , 1)); + assertEqualsExactType(512D , message.getExtension(repeatedDoubleExtension , 1)); + assertEqualsExactType(true , message.getExtension(repeatedBoolExtension , 1)); + assertEqualsExactType("515", message.getExtension(repeatedStringExtension , 1)); + assertEqualsExactType(toBytes("516"), message.getExtension(repeatedBytesExtension, 1)); + + assertEqualsExactType(517, message.getExtension(repeatedGroupExtension , 1).getA()); + assertEqualsExactType(518, message.getExtension(repeatedNestedMessageExtension , 1).getBb()); + assertEqualsExactType(519, message.getExtension(repeatedForeignMessageExtension, 1).getC()); + assertEqualsExactType(520, message.getExtension(repeatedImportMessageExtension , 1).getD()); + assertEqualsExactType(527, message.getExtension(repeatedLazyMessageExtension , 1).getBb()); + + assertEqualsExactType(TestAllTypes.NestedEnum.FOO, + message.getExtension(repeatedNestedEnumExtension, 1)); + assertEqualsExactType(ForeignEnum.FOREIGN_FOO, + message.getExtension(repeatedForeignEnumExtension, 1)); + assertEqualsExactType(ImportEnum.IMPORT_FOO, + message.getExtension(repeatedImportEnumExtension, 1)); + + assertEqualsExactType("524", message.getExtension(repeatedStringPieceExtension, 1)); + assertEqualsExactType("525", message.getExtension(repeatedCordExtension, 1)); + } + + public static void setPackedExtensions(TestPackedExtensions.Builder message) { + message.addExtension(packedInt32Extension , 601); + message.addExtension(packedInt64Extension , 602L); + message.addExtension(packedUint32Extension , 603); + message.addExtension(packedUint64Extension , 604L); + message.addExtension(packedSint32Extension , 605); + message.addExtension(packedSint64Extension , 606L); + message.addExtension(packedFixed32Extension , 607); + message.addExtension(packedFixed64Extension , 608L); + message.addExtension(packedSfixed32Extension, 609); + message.addExtension(packedSfixed64Extension, 610L); + message.addExtension(packedFloatExtension , 611F); + message.addExtension(packedDoubleExtension , 612D); + message.addExtension(packedBoolExtension , true); + message.addExtension(packedEnumExtension, ForeignEnum.FOREIGN_BAR); + // Add a second one of each field. + message.addExtension(packedInt32Extension , 701); + message.addExtension(packedInt64Extension , 702L); + message.addExtension(packedUint32Extension , 703); + message.addExtension(packedUint64Extension , 704L); + message.addExtension(packedSint32Extension , 705); + message.addExtension(packedSint64Extension , 706L); + message.addExtension(packedFixed32Extension , 707); + message.addExtension(packedFixed64Extension , 708L); + message.addExtension(packedSfixed32Extension, 709); + message.addExtension(packedSfixed64Extension, 710L); + message.addExtension(packedFloatExtension , 711F); + message.addExtension(packedDoubleExtension , 712D); + message.addExtension(packedBoolExtension , false); + message.addExtension(packedEnumExtension, ForeignEnum.FOREIGN_BAZ); + } + + public static void assertPackedExtensionsSet(TestPackedExtensions message) { + Assert.assertEquals(2, message.getExtensionCount(packedInt32Extension )); + Assert.assertEquals(2, message.getExtensionCount(packedInt64Extension )); + Assert.assertEquals(2, message.getExtensionCount(packedUint32Extension )); + Assert.assertEquals(2, message.getExtensionCount(packedUint64Extension )); + Assert.assertEquals(2, message.getExtensionCount(packedSint32Extension )); + Assert.assertEquals(2, message.getExtensionCount(packedSint64Extension )); + Assert.assertEquals(2, message.getExtensionCount(packedFixed32Extension )); + Assert.assertEquals(2, message.getExtensionCount(packedFixed64Extension )); + Assert.assertEquals(2, message.getExtensionCount(packedSfixed32Extension)); + Assert.assertEquals(2, message.getExtensionCount(packedSfixed64Extension)); + Assert.assertEquals(2, message.getExtensionCount(packedFloatExtension )); + Assert.assertEquals(2, message.getExtensionCount(packedDoubleExtension )); + Assert.assertEquals(2, message.getExtensionCount(packedBoolExtension )); + Assert.assertEquals(2, message.getExtensionCount(packedEnumExtension)); + assertEqualsExactType(601 , message.getExtension(packedInt32Extension , 0)); + assertEqualsExactType(602L , message.getExtension(packedInt64Extension , 0)); + assertEqualsExactType(603 , message.getExtension(packedUint32Extension , 0)); + assertEqualsExactType(604L , message.getExtension(packedUint64Extension , 0)); + assertEqualsExactType(605 , message.getExtension(packedSint32Extension , 0)); + assertEqualsExactType(606L , message.getExtension(packedSint64Extension , 0)); + assertEqualsExactType(607 , message.getExtension(packedFixed32Extension , 0)); + assertEqualsExactType(608L , message.getExtension(packedFixed64Extension , 0)); + assertEqualsExactType(609 , message.getExtension(packedSfixed32Extension, 0)); + assertEqualsExactType(610L , message.getExtension(packedSfixed64Extension, 0)); + assertEqualsExactType(611F , message.getExtension(packedFloatExtension , 0)); + assertEqualsExactType(612D , message.getExtension(packedDoubleExtension , 0)); + assertEqualsExactType(true , message.getExtension(packedBoolExtension , 0)); + assertEqualsExactType(ForeignEnum.FOREIGN_BAR, + message.getExtension(packedEnumExtension, 0)); + assertEqualsExactType(701 , message.getExtension(packedInt32Extension , 1)); + assertEqualsExactType(702L , message.getExtension(packedInt64Extension , 1)); + assertEqualsExactType(703 , message.getExtension(packedUint32Extension , 1)); + assertEqualsExactType(704L , message.getExtension(packedUint64Extension , 1)); + assertEqualsExactType(705 , message.getExtension(packedSint32Extension , 1)); + assertEqualsExactType(706L , message.getExtension(packedSint64Extension , 1)); + assertEqualsExactType(707 , message.getExtension(packedFixed32Extension , 1)); + assertEqualsExactType(708L , message.getExtension(packedFixed64Extension , 1)); + assertEqualsExactType(709 , message.getExtension(packedSfixed32Extension, 1)); + assertEqualsExactType(710L , message.getExtension(packedSfixed64Extension, 1)); + assertEqualsExactType(711F , message.getExtension(packedFloatExtension , 1)); + assertEqualsExactType(712D , message.getExtension(packedDoubleExtension , 1)); + assertEqualsExactType(false, message.getExtension(packedBoolExtension , 1)); + assertEqualsExactType(ForeignEnum.FOREIGN_BAZ, + message.getExtension(packedEnumExtension, 1)); + } + + // =================================================================== + // Lite extensions + + /** + * Set every field of {@code message} to the values expected by + * {@code assertAllExtensionsSet()}. + */ + public static void setAllExtensions(TestAllExtensionsLite.Builder message) { + message.setExtension(optionalInt32ExtensionLite , 101); + message.setExtension(optionalInt64ExtensionLite , 102L); + message.setExtension(optionalUint32ExtensionLite , 103); + message.setExtension(optionalUint64ExtensionLite , 104L); + message.setExtension(optionalSint32ExtensionLite , 105); + message.setExtension(optionalSint64ExtensionLite , 106L); + message.setExtension(optionalFixed32ExtensionLite , 107); + message.setExtension(optionalFixed64ExtensionLite , 108L); + message.setExtension(optionalSfixed32ExtensionLite, 109); + message.setExtension(optionalSfixed64ExtensionLite, 110L); + message.setExtension(optionalFloatExtensionLite , 111F); + message.setExtension(optionalDoubleExtensionLite , 112D); + message.setExtension(optionalBoolExtensionLite , true); + message.setExtension(optionalStringExtensionLite , "115"); + message.setExtension(optionalBytesExtensionLite , toBytes("116")); + + message.setExtension(optionalGroupExtensionLite, + OptionalGroup_extension_lite.newBuilder().setA(117).build()); + message.setExtension(optionalNestedMessageExtensionLite, + TestAllTypesLite.NestedMessage.newBuilder().setBb(118).build()); + message.setExtension(optionalForeignMessageExtensionLite, + ForeignMessageLite.newBuilder().setC(119).build()); + message.setExtension(optionalImportMessageExtensionLite, + ImportMessageLite.newBuilder().setD(120).build()); + message.setExtension(optionalPublicImportMessageExtensionLite, + PublicImportMessageLite.newBuilder().setE(126).build()); + message.setExtension(optionalLazyMessageExtensionLite, + TestAllTypesLite.NestedMessage.newBuilder().setBb(127).build()); + + message.setExtension(optionalNestedEnumExtensionLite, TestAllTypesLite.NestedEnum.BAZ); + message.setExtension(optionalForeignEnumExtensionLite, ForeignEnumLite.FOREIGN_LITE_BAZ); + message.setExtension(optionalImportEnumExtensionLite, ImportEnumLite.IMPORT_LITE_BAZ); + + message.setExtension(optionalStringPieceExtensionLite, "124"); + message.setExtension(optionalCordExtensionLite, "125"); + + // ----------------------------------------------------------------- + + message.addExtension(repeatedInt32ExtensionLite , 201); + message.addExtension(repeatedInt64ExtensionLite , 202L); + message.addExtension(repeatedUint32ExtensionLite , 203); + message.addExtension(repeatedUint64ExtensionLite , 204L); + message.addExtension(repeatedSint32ExtensionLite , 205); + message.addExtension(repeatedSint64ExtensionLite , 206L); + message.addExtension(repeatedFixed32ExtensionLite , 207); + message.addExtension(repeatedFixed64ExtensionLite , 208L); + message.addExtension(repeatedSfixed32ExtensionLite, 209); + message.addExtension(repeatedSfixed64ExtensionLite, 210L); + message.addExtension(repeatedFloatExtensionLite , 211F); + message.addExtension(repeatedDoubleExtensionLite , 212D); + message.addExtension(repeatedBoolExtensionLite , true); + message.addExtension(repeatedStringExtensionLite , "215"); + message.addExtension(repeatedBytesExtensionLite , toBytes("216")); + + message.addExtension(repeatedGroupExtensionLite, + RepeatedGroup_extension_lite.newBuilder().setA(217).build()); + message.addExtension(repeatedNestedMessageExtensionLite, + TestAllTypesLite.NestedMessage.newBuilder().setBb(218).build()); + message.addExtension(repeatedForeignMessageExtensionLite, + ForeignMessageLite.newBuilder().setC(219).build()); + message.addExtension(repeatedImportMessageExtensionLite, + ImportMessageLite.newBuilder().setD(220).build()); + message.addExtension(repeatedLazyMessageExtensionLite, + TestAllTypesLite.NestedMessage.newBuilder().setBb(227).build()); + + message.addExtension(repeatedNestedEnumExtensionLite, TestAllTypesLite.NestedEnum.BAR); + message.addExtension(repeatedForeignEnumExtensionLite, ForeignEnumLite.FOREIGN_LITE_BAR); + message.addExtension(repeatedImportEnumExtensionLite, ImportEnumLite.IMPORT_LITE_BAR); + + message.addExtension(repeatedStringPieceExtensionLite, "224"); + message.addExtension(repeatedCordExtensionLite, "225"); + + // Add a second one of each field. + message.addExtension(repeatedInt32ExtensionLite , 301); + message.addExtension(repeatedInt64ExtensionLite , 302L); + message.addExtension(repeatedUint32ExtensionLite , 303); + message.addExtension(repeatedUint64ExtensionLite , 304L); + message.addExtension(repeatedSint32ExtensionLite , 305); + message.addExtension(repeatedSint64ExtensionLite , 306L); + message.addExtension(repeatedFixed32ExtensionLite , 307); + message.addExtension(repeatedFixed64ExtensionLite , 308L); + message.addExtension(repeatedSfixed32ExtensionLite, 309); + message.addExtension(repeatedSfixed64ExtensionLite, 310L); + message.addExtension(repeatedFloatExtensionLite , 311F); + message.addExtension(repeatedDoubleExtensionLite , 312D); + message.addExtension(repeatedBoolExtensionLite , false); + message.addExtension(repeatedStringExtensionLite , "315"); + message.addExtension(repeatedBytesExtensionLite , toBytes("316")); + + message.addExtension(repeatedGroupExtensionLite, + RepeatedGroup_extension_lite.newBuilder().setA(317).build()); + message.addExtension(repeatedNestedMessageExtensionLite, + TestAllTypesLite.NestedMessage.newBuilder().setBb(318).build()); + message.addExtension(repeatedForeignMessageExtensionLite, + ForeignMessageLite.newBuilder().setC(319).build()); + message.addExtension(repeatedImportMessageExtensionLite, + ImportMessageLite.newBuilder().setD(320).build()); + message.addExtension(repeatedLazyMessageExtensionLite, + TestAllTypesLite.NestedMessage.newBuilder().setBb(327).build()); + + message.addExtension(repeatedNestedEnumExtensionLite, TestAllTypesLite.NestedEnum.BAZ); + message.addExtension(repeatedForeignEnumExtensionLite, ForeignEnumLite.FOREIGN_LITE_BAZ); + message.addExtension(repeatedImportEnumExtensionLite, ImportEnumLite.IMPORT_LITE_BAZ); + + message.addExtension(repeatedStringPieceExtensionLite, "324"); + message.addExtension(repeatedCordExtensionLite, "325"); + + // ----------------------------------------------------------------- + + message.setExtension(defaultInt32ExtensionLite , 401); + message.setExtension(defaultInt64ExtensionLite , 402L); + message.setExtension(defaultUint32ExtensionLite , 403); + message.setExtension(defaultUint64ExtensionLite , 404L); + message.setExtension(defaultSint32ExtensionLite , 405); + message.setExtension(defaultSint64ExtensionLite , 406L); + message.setExtension(defaultFixed32ExtensionLite , 407); + message.setExtension(defaultFixed64ExtensionLite , 408L); + message.setExtension(defaultSfixed32ExtensionLite, 409); + message.setExtension(defaultSfixed64ExtensionLite, 410L); + message.setExtension(defaultFloatExtensionLite , 411F); + message.setExtension(defaultDoubleExtensionLite , 412D); + message.setExtension(defaultBoolExtensionLite , false); + message.setExtension(defaultStringExtensionLite , "415"); + message.setExtension(defaultBytesExtensionLite , toBytes("416")); + + message.setExtension(defaultNestedEnumExtensionLite, TestAllTypesLite.NestedEnum.FOO); + message.setExtension(defaultForeignEnumExtensionLite, ForeignEnumLite.FOREIGN_LITE_FOO); + message.setExtension(defaultImportEnumExtensionLite, ImportEnumLite.IMPORT_LITE_FOO); + + message.setExtension(defaultStringPieceExtensionLite, "424"); + message.setExtension(defaultCordExtensionLite, "425"); + + message.setExtension(oneofUint32ExtensionLite, 601); + message.setExtension(oneofNestedMessageExtensionLite, + TestAllTypesLite.NestedMessage.newBuilder().setBb(602).build()); + message.setExtension(oneofStringExtensionLite, "603"); + message.setExtension(oneofBytesExtensionLite, toBytes("604")); + } + + // ------------------------------------------------------------------- + + /** + * Modify the repeated extensions of {@code message} to contain the values + * expected by {@code assertRepeatedExtensionsModified()}. + */ + public static void modifyRepeatedExtensions( + TestAllExtensionsLite.Builder message) { + message.setExtension(repeatedInt32ExtensionLite , 1, 501); + message.setExtension(repeatedInt64ExtensionLite , 1, 502L); + message.setExtension(repeatedUint32ExtensionLite , 1, 503); + message.setExtension(repeatedUint64ExtensionLite , 1, 504L); + message.setExtension(repeatedSint32ExtensionLite , 1, 505); + message.setExtension(repeatedSint64ExtensionLite , 1, 506L); + message.setExtension(repeatedFixed32ExtensionLite , 1, 507); + message.setExtension(repeatedFixed64ExtensionLite , 1, 508L); + message.setExtension(repeatedSfixed32ExtensionLite, 1, 509); + message.setExtension(repeatedSfixed64ExtensionLite, 1, 510L); + message.setExtension(repeatedFloatExtensionLite , 1, 511F); + message.setExtension(repeatedDoubleExtensionLite , 1, 512D); + message.setExtension(repeatedBoolExtensionLite , 1, true); + message.setExtension(repeatedStringExtensionLite , 1, "515"); + message.setExtension(repeatedBytesExtensionLite , 1, toBytes("516")); + + message.setExtension(repeatedGroupExtensionLite, 1, + RepeatedGroup_extension_lite.newBuilder().setA(517).build()); + message.setExtension(repeatedNestedMessageExtensionLite, 1, + TestAllTypesLite.NestedMessage.newBuilder().setBb(518).build()); + message.setExtension(repeatedForeignMessageExtensionLite, 1, + ForeignMessageLite.newBuilder().setC(519).build()); + message.setExtension(repeatedImportMessageExtensionLite, 1, + ImportMessageLite.newBuilder().setD(520).build()); + message.setExtension(repeatedLazyMessageExtensionLite, 1, + TestAllTypesLite.NestedMessage.newBuilder().setBb(527).build()); + + message.setExtension(repeatedNestedEnumExtensionLite , 1, TestAllTypesLite.NestedEnum.FOO); + message.setExtension(repeatedForeignEnumExtensionLite, 1, ForeignEnumLite.FOREIGN_LITE_FOO); + message.setExtension(repeatedImportEnumExtensionLite , 1, ImportEnumLite.IMPORT_LITE_FOO); + + message.setExtension(repeatedStringPieceExtensionLite, 1, "524"); + message.setExtension(repeatedCordExtensionLite, 1, "525"); + } + + // ------------------------------------------------------------------- + + /** + * Assert (using {@code junit.framework.Assert}} that all extensions of + * {@code message} are set to the values assigned by {@code setAllExtensions}. + */ + public static void assertAllExtensionsSet( + TestAllExtensionsLiteOrBuilder message) { + Assert.assertTrue(message.hasExtension(optionalInt32ExtensionLite )); + Assert.assertTrue(message.hasExtension(optionalInt64ExtensionLite )); + Assert.assertTrue(message.hasExtension(optionalUint32ExtensionLite )); + Assert.assertTrue(message.hasExtension(optionalUint64ExtensionLite )); + Assert.assertTrue(message.hasExtension(optionalSint32ExtensionLite )); + Assert.assertTrue(message.hasExtension(optionalSint64ExtensionLite )); + Assert.assertTrue(message.hasExtension(optionalFixed32ExtensionLite )); + Assert.assertTrue(message.hasExtension(optionalFixed64ExtensionLite )); + Assert.assertTrue(message.hasExtension(optionalSfixed32ExtensionLite)); + Assert.assertTrue(message.hasExtension(optionalSfixed64ExtensionLite)); + Assert.assertTrue(message.hasExtension(optionalFloatExtensionLite )); + Assert.assertTrue(message.hasExtension(optionalDoubleExtensionLite )); + Assert.assertTrue(message.hasExtension(optionalBoolExtensionLite )); + Assert.assertTrue(message.hasExtension(optionalStringExtensionLite )); + Assert.assertTrue(message.hasExtension(optionalBytesExtensionLite )); + + Assert.assertTrue(message.hasExtension(optionalGroupExtensionLite )); + Assert.assertTrue(message.hasExtension(optionalNestedMessageExtensionLite )); + Assert.assertTrue(message.hasExtension(optionalForeignMessageExtensionLite)); + Assert.assertTrue(message.hasExtension(optionalImportMessageExtensionLite )); + + Assert.assertTrue(message.getExtension(optionalGroupExtensionLite ).hasA()); + Assert.assertTrue(message.getExtension(optionalNestedMessageExtensionLite ).hasBb()); + Assert.assertTrue(message.getExtension(optionalForeignMessageExtensionLite).hasC()); + Assert.assertTrue(message.getExtension(optionalImportMessageExtensionLite ).hasD()); + + Assert.assertTrue(message.hasExtension(optionalNestedEnumExtensionLite )); + Assert.assertTrue(message.hasExtension(optionalForeignEnumExtensionLite)); + Assert.assertTrue(message.hasExtension(optionalImportEnumExtensionLite )); + + Assert.assertTrue(message.hasExtension(optionalStringPieceExtensionLite)); + Assert.assertTrue(message.hasExtension(optionalCordExtensionLite)); + + assertEqualsExactType(101 , message.getExtension(optionalInt32ExtensionLite )); + assertEqualsExactType(102L , message.getExtension(optionalInt64ExtensionLite )); + assertEqualsExactType(103 , message.getExtension(optionalUint32ExtensionLite )); + assertEqualsExactType(104L , message.getExtension(optionalUint64ExtensionLite )); + assertEqualsExactType(105 , message.getExtension(optionalSint32ExtensionLite )); + assertEqualsExactType(106L , message.getExtension(optionalSint64ExtensionLite )); + assertEqualsExactType(107 , message.getExtension(optionalFixed32ExtensionLite )); + assertEqualsExactType(108L , message.getExtension(optionalFixed64ExtensionLite )); + assertEqualsExactType(109 , message.getExtension(optionalSfixed32ExtensionLite)); + assertEqualsExactType(110L , message.getExtension(optionalSfixed64ExtensionLite)); + assertEqualsExactType(111F , message.getExtension(optionalFloatExtensionLite )); + assertEqualsExactType(112D , message.getExtension(optionalDoubleExtensionLite )); + assertEqualsExactType(true , message.getExtension(optionalBoolExtensionLite )); + assertEqualsExactType("115", message.getExtension(optionalStringExtensionLite )); + assertEqualsExactType(toBytes("116"), message.getExtension(optionalBytesExtensionLite)); + + assertEqualsExactType(117, message.getExtension(optionalGroupExtensionLite ).getA()); + assertEqualsExactType(118, message.getExtension(optionalNestedMessageExtensionLite ).getBb()); + assertEqualsExactType(119, message.getExtension(optionalForeignMessageExtensionLite).getC()); + assertEqualsExactType(120, message.getExtension(optionalImportMessageExtensionLite ).getD()); + assertEqualsExactType(126, message.getExtension( + optionalPublicImportMessageExtensionLite).getE()); + assertEqualsExactType(127, message.getExtension(optionalLazyMessageExtensionLite).getBb()); + + assertEqualsExactType(TestAllTypesLite.NestedEnum.BAZ, + message.getExtension(optionalNestedEnumExtensionLite)); + assertEqualsExactType(ForeignEnumLite.FOREIGN_LITE_BAZ, + message.getExtension(optionalForeignEnumExtensionLite)); + assertEqualsExactType(ImportEnumLite.IMPORT_LITE_BAZ, + message.getExtension(optionalImportEnumExtensionLite)); + + assertEqualsExactType("124", message.getExtension(optionalStringPieceExtensionLite)); + assertEqualsExactType("125", message.getExtension(optionalCordExtensionLite)); + + // ----------------------------------------------------------------- + + Assert.assertEquals(2, message.getExtensionCount(repeatedInt32ExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(repeatedInt64ExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(repeatedUint32ExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(repeatedUint64ExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(repeatedSint32ExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(repeatedSint64ExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(repeatedFixed32ExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(repeatedFixed64ExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(repeatedSfixed32ExtensionLite)); + Assert.assertEquals(2, message.getExtensionCount(repeatedSfixed64ExtensionLite)); + Assert.assertEquals(2, message.getExtensionCount(repeatedFloatExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(repeatedDoubleExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(repeatedBoolExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(repeatedStringExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(repeatedBytesExtensionLite )); + + Assert.assertEquals(2, message.getExtensionCount(repeatedGroupExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(repeatedNestedMessageExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(repeatedForeignMessageExtensionLite)); + Assert.assertEquals(2, message.getExtensionCount(repeatedImportMessageExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(repeatedLazyMessageExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(repeatedNestedEnumExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(repeatedForeignEnumExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(repeatedImportEnumExtensionLite )); + + Assert.assertEquals(2, message.getExtensionCount(repeatedStringPieceExtensionLite)); + Assert.assertEquals(2, message.getExtensionCount(repeatedCordExtensionLite)); + + assertEqualsExactType(201 , message.getExtension(repeatedInt32ExtensionLite , 0)); + assertEqualsExactType(202L , message.getExtension(repeatedInt64ExtensionLite , 0)); + assertEqualsExactType(203 , message.getExtension(repeatedUint32ExtensionLite , 0)); + assertEqualsExactType(204L , message.getExtension(repeatedUint64ExtensionLite , 0)); + assertEqualsExactType(205 , message.getExtension(repeatedSint32ExtensionLite , 0)); + assertEqualsExactType(206L , message.getExtension(repeatedSint64ExtensionLite , 0)); + assertEqualsExactType(207 , message.getExtension(repeatedFixed32ExtensionLite , 0)); + assertEqualsExactType(208L , message.getExtension(repeatedFixed64ExtensionLite , 0)); + assertEqualsExactType(209 , message.getExtension(repeatedSfixed32ExtensionLite, 0)); + assertEqualsExactType(210L , message.getExtension(repeatedSfixed64ExtensionLite, 0)); + assertEqualsExactType(211F , message.getExtension(repeatedFloatExtensionLite , 0)); + assertEqualsExactType(212D , message.getExtension(repeatedDoubleExtensionLite , 0)); + assertEqualsExactType(true , message.getExtension(repeatedBoolExtensionLite , 0)); + assertEqualsExactType("215", message.getExtension(repeatedStringExtensionLite , 0)); + assertEqualsExactType(toBytes("216"), message.getExtension(repeatedBytesExtensionLite, 0)); + + assertEqualsExactType(217, message.getExtension(repeatedGroupExtensionLite ,0).getA()); + assertEqualsExactType(218, message.getExtension(repeatedNestedMessageExtensionLite ,0).getBb()); + assertEqualsExactType(219, message.getExtension(repeatedForeignMessageExtensionLite,0).getC()); + assertEqualsExactType(220, message.getExtension(repeatedImportMessageExtensionLite ,0).getD()); + assertEqualsExactType(227, message.getExtension(repeatedLazyMessageExtensionLite ,0).getBb()); + + assertEqualsExactType(TestAllTypesLite.NestedEnum.BAR, + message.getExtension(repeatedNestedEnumExtensionLite, 0)); + assertEqualsExactType(ForeignEnumLite.FOREIGN_LITE_BAR, + message.getExtension(repeatedForeignEnumExtensionLite, 0)); + assertEqualsExactType(ImportEnumLite.IMPORT_LITE_BAR, + message.getExtension(repeatedImportEnumExtensionLite, 0)); + + assertEqualsExactType("224", message.getExtension(repeatedStringPieceExtensionLite, 0)); + assertEqualsExactType("225", message.getExtension(repeatedCordExtensionLite, 0)); + + assertEqualsExactType(301 , message.getExtension(repeatedInt32ExtensionLite , 1)); + assertEqualsExactType(302L , message.getExtension(repeatedInt64ExtensionLite , 1)); + assertEqualsExactType(303 , message.getExtension(repeatedUint32ExtensionLite , 1)); + assertEqualsExactType(304L , message.getExtension(repeatedUint64ExtensionLite , 1)); + assertEqualsExactType(305 , message.getExtension(repeatedSint32ExtensionLite , 1)); + assertEqualsExactType(306L , message.getExtension(repeatedSint64ExtensionLite , 1)); + assertEqualsExactType(307 , message.getExtension(repeatedFixed32ExtensionLite , 1)); + assertEqualsExactType(308L , message.getExtension(repeatedFixed64ExtensionLite , 1)); + assertEqualsExactType(309 , message.getExtension(repeatedSfixed32ExtensionLite, 1)); + assertEqualsExactType(310L , message.getExtension(repeatedSfixed64ExtensionLite, 1)); + assertEqualsExactType(311F , message.getExtension(repeatedFloatExtensionLite , 1)); + assertEqualsExactType(312D , message.getExtension(repeatedDoubleExtensionLite , 1)); + assertEqualsExactType(false, message.getExtension(repeatedBoolExtensionLite , 1)); + assertEqualsExactType("315", message.getExtension(repeatedStringExtensionLite , 1)); + assertEqualsExactType(toBytes("316"), message.getExtension(repeatedBytesExtensionLite, 1)); + + assertEqualsExactType(317, message.getExtension(repeatedGroupExtensionLite ,1).getA()); + assertEqualsExactType(318, message.getExtension(repeatedNestedMessageExtensionLite ,1).getBb()); + assertEqualsExactType(319, message.getExtension(repeatedForeignMessageExtensionLite,1).getC()); + assertEqualsExactType(320, message.getExtension(repeatedImportMessageExtensionLite ,1).getD()); + assertEqualsExactType(327, message.getExtension(repeatedLazyMessageExtensionLite ,1).getBb()); + + assertEqualsExactType(TestAllTypesLite.NestedEnum.BAZ, + message.getExtension(repeatedNestedEnumExtensionLite, 1)); + assertEqualsExactType(ForeignEnumLite.FOREIGN_LITE_BAZ, + message.getExtension(repeatedForeignEnumExtensionLite, 1)); + assertEqualsExactType(ImportEnumLite.IMPORT_LITE_BAZ, + message.getExtension(repeatedImportEnumExtensionLite, 1)); + + assertEqualsExactType("324", message.getExtension(repeatedStringPieceExtensionLite, 1)); + assertEqualsExactType("325", message.getExtension(repeatedCordExtensionLite, 1)); + + // ----------------------------------------------------------------- + + Assert.assertTrue(message.hasExtension(defaultInt32ExtensionLite )); + Assert.assertTrue(message.hasExtension(defaultInt64ExtensionLite )); + Assert.assertTrue(message.hasExtension(defaultUint32ExtensionLite )); + Assert.assertTrue(message.hasExtension(defaultUint64ExtensionLite )); + Assert.assertTrue(message.hasExtension(defaultSint32ExtensionLite )); + Assert.assertTrue(message.hasExtension(defaultSint64ExtensionLite )); + Assert.assertTrue(message.hasExtension(defaultFixed32ExtensionLite )); + Assert.assertTrue(message.hasExtension(defaultFixed64ExtensionLite )); + Assert.assertTrue(message.hasExtension(defaultSfixed32ExtensionLite)); + Assert.assertTrue(message.hasExtension(defaultSfixed64ExtensionLite)); + Assert.assertTrue(message.hasExtension(defaultFloatExtensionLite )); + Assert.assertTrue(message.hasExtension(defaultDoubleExtensionLite )); + Assert.assertTrue(message.hasExtension(defaultBoolExtensionLite )); + Assert.assertTrue(message.hasExtension(defaultStringExtensionLite )); + Assert.assertTrue(message.hasExtension(defaultBytesExtensionLite )); + + Assert.assertTrue(message.hasExtension(defaultNestedEnumExtensionLite )); + Assert.assertTrue(message.hasExtension(defaultForeignEnumExtensionLite)); + Assert.assertTrue(message.hasExtension(defaultImportEnumExtensionLite )); + + Assert.assertTrue(message.hasExtension(defaultStringPieceExtensionLite)); + Assert.assertTrue(message.hasExtension(defaultCordExtensionLite)); + + assertEqualsExactType(401 , message.getExtension(defaultInt32ExtensionLite )); + assertEqualsExactType(402L , message.getExtension(defaultInt64ExtensionLite )); + assertEqualsExactType(403 , message.getExtension(defaultUint32ExtensionLite )); + assertEqualsExactType(404L , message.getExtension(defaultUint64ExtensionLite )); + assertEqualsExactType(405 , message.getExtension(defaultSint32ExtensionLite )); + assertEqualsExactType(406L , message.getExtension(defaultSint64ExtensionLite )); + assertEqualsExactType(407 , message.getExtension(defaultFixed32ExtensionLite )); + assertEqualsExactType(408L , message.getExtension(defaultFixed64ExtensionLite )); + assertEqualsExactType(409 , message.getExtension(defaultSfixed32ExtensionLite)); + assertEqualsExactType(410L , message.getExtension(defaultSfixed64ExtensionLite)); + assertEqualsExactType(411F , message.getExtension(defaultFloatExtensionLite )); + assertEqualsExactType(412D , message.getExtension(defaultDoubleExtensionLite )); + assertEqualsExactType(false, message.getExtension(defaultBoolExtensionLite )); + assertEqualsExactType("415", message.getExtension(defaultStringExtensionLite )); + assertEqualsExactType(toBytes("416"), message.getExtension(defaultBytesExtensionLite)); + + assertEqualsExactType(TestAllTypesLite.NestedEnum.FOO, + message.getExtension(defaultNestedEnumExtensionLite )); + assertEqualsExactType(ForeignEnumLite.FOREIGN_LITE_FOO, + message.getExtension(defaultForeignEnumExtensionLite)); + assertEqualsExactType(ImportEnumLite.IMPORT_LITE_FOO, + message.getExtension(defaultImportEnumExtensionLite)); + + assertEqualsExactType("424", message.getExtension(defaultStringPieceExtensionLite)); + assertEqualsExactType("425", message.getExtension(defaultCordExtensionLite)); + + Assert.assertTrue(message.hasExtension(oneofBytesExtensionLite)); + + assertEqualsExactType(toBytes("604"), message.getExtension(oneofBytesExtensionLite)); + } + + // ------------------------------------------------------------------- + + /** + * Assert (using {@code junit.framework.Assert}} that all extensions of + * {@code message} are cleared, and that getting the extensions returns their + * default values. + */ + public static void assertExtensionsClear( + TestAllExtensionsLiteOrBuilder message) { + // hasBlah() should initially be false for all optional fields. + Assert.assertFalse(message.hasExtension(optionalInt32ExtensionLite )); + Assert.assertFalse(message.hasExtension(optionalInt64ExtensionLite )); + Assert.assertFalse(message.hasExtension(optionalUint32ExtensionLite )); + Assert.assertFalse(message.hasExtension(optionalUint64ExtensionLite )); + Assert.assertFalse(message.hasExtension(optionalSint32ExtensionLite )); + Assert.assertFalse(message.hasExtension(optionalSint64ExtensionLite )); + Assert.assertFalse(message.hasExtension(optionalFixed32ExtensionLite )); + Assert.assertFalse(message.hasExtension(optionalFixed64ExtensionLite )); + Assert.assertFalse(message.hasExtension(optionalSfixed32ExtensionLite)); + Assert.assertFalse(message.hasExtension(optionalSfixed64ExtensionLite)); + Assert.assertFalse(message.hasExtension(optionalFloatExtensionLite )); + Assert.assertFalse(message.hasExtension(optionalDoubleExtensionLite )); + Assert.assertFalse(message.hasExtension(optionalBoolExtensionLite )); + Assert.assertFalse(message.hasExtension(optionalStringExtensionLite )); + Assert.assertFalse(message.hasExtension(optionalBytesExtensionLite )); + + Assert.assertFalse(message.hasExtension(optionalGroupExtensionLite )); + Assert.assertFalse(message.hasExtension(optionalNestedMessageExtensionLite )); + Assert.assertFalse(message.hasExtension(optionalForeignMessageExtensionLite )); + Assert.assertFalse(message.hasExtension(optionalImportMessageExtensionLite )); + Assert.assertFalse(message.hasExtension(optionalPublicImportMessageExtensionLite)); + Assert.assertFalse(message.hasExtension(optionalLazyMessageExtensionLite )); + + Assert.assertFalse(message.hasExtension(optionalNestedEnumExtensionLite )); + Assert.assertFalse(message.hasExtension(optionalForeignEnumExtensionLite)); + Assert.assertFalse(message.hasExtension(optionalImportEnumExtensionLite )); + + Assert.assertFalse(message.hasExtension(optionalStringPieceExtensionLite)); + Assert.assertFalse(message.hasExtension(optionalCordExtensionLite)); + + // Optional fields without defaults are set to zero or something like it. + assertEqualsExactType(0 , message.getExtension(optionalInt32ExtensionLite )); + assertEqualsExactType(0L , message.getExtension(optionalInt64ExtensionLite )); + assertEqualsExactType(0 , message.getExtension(optionalUint32ExtensionLite )); + assertEqualsExactType(0L , message.getExtension(optionalUint64ExtensionLite )); + assertEqualsExactType(0 , message.getExtension(optionalSint32ExtensionLite )); + assertEqualsExactType(0L , message.getExtension(optionalSint64ExtensionLite )); + assertEqualsExactType(0 , message.getExtension(optionalFixed32ExtensionLite )); + assertEqualsExactType(0L , message.getExtension(optionalFixed64ExtensionLite )); + assertEqualsExactType(0 , message.getExtension(optionalSfixed32ExtensionLite)); + assertEqualsExactType(0L , message.getExtension(optionalSfixed64ExtensionLite)); + assertEqualsExactType(0F , message.getExtension(optionalFloatExtensionLite )); + assertEqualsExactType(0D , message.getExtension(optionalDoubleExtensionLite )); + assertEqualsExactType(false, message.getExtension(optionalBoolExtensionLite )); + assertEqualsExactType("" , message.getExtension(optionalStringExtensionLite )); + assertEqualsExactType(ByteString.EMPTY, message.getExtension(optionalBytesExtensionLite)); + + // Embedded messages should also be clear. + Assert.assertFalse(message.getExtension(optionalGroupExtensionLite ).hasA()); + Assert.assertFalse(message.getExtension(optionalNestedMessageExtensionLite ).hasBb()); + Assert.assertFalse(message.getExtension(optionalForeignMessageExtensionLite ).hasC()); + Assert.assertFalse(message.getExtension(optionalImportMessageExtensionLite ).hasD()); + Assert.assertFalse(message.getExtension(optionalPublicImportMessageExtensionLite).hasE()); + Assert.assertFalse(message.getExtension(optionalLazyMessageExtensionLite ).hasBb()); + + assertEqualsExactType(0, message.getExtension(optionalGroupExtensionLite ).getA()); + assertEqualsExactType(0, message.getExtension(optionalNestedMessageExtensionLite ).getBb()); + assertEqualsExactType(0, message.getExtension(optionalForeignMessageExtensionLite).getC()); + assertEqualsExactType(0, message.getExtension(optionalImportMessageExtensionLite ).getD()); + assertEqualsExactType(0, message.getExtension( + optionalPublicImportMessageExtensionLite).getE()); + assertEqualsExactType(0, message.getExtension(optionalLazyMessageExtensionLite ).getBb()); + + // Enums without defaults are set to the first value in the enum. + assertEqualsExactType(TestAllTypesLite.NestedEnum.FOO, + message.getExtension(optionalNestedEnumExtensionLite )); + assertEqualsExactType(ForeignEnumLite.FOREIGN_LITE_FOO, + message.getExtension(optionalForeignEnumExtensionLite)); + assertEqualsExactType(ImportEnumLite.IMPORT_LITE_FOO, + message.getExtension(optionalImportEnumExtensionLite)); + + assertEqualsExactType("", message.getExtension(optionalStringPieceExtensionLite)); + assertEqualsExactType("", message.getExtension(optionalCordExtensionLite)); + + // Repeated fields are empty. + Assert.assertEquals(0, message.getExtensionCount(repeatedInt32ExtensionLite )); + Assert.assertEquals(0, message.getExtensionCount(repeatedInt64ExtensionLite )); + Assert.assertEquals(0, message.getExtensionCount(repeatedUint32ExtensionLite )); + Assert.assertEquals(0, message.getExtensionCount(repeatedUint64ExtensionLite )); + Assert.assertEquals(0, message.getExtensionCount(repeatedSint32ExtensionLite )); + Assert.assertEquals(0, message.getExtensionCount(repeatedSint64ExtensionLite )); + Assert.assertEquals(0, message.getExtensionCount(repeatedFixed32ExtensionLite )); + Assert.assertEquals(0, message.getExtensionCount(repeatedFixed64ExtensionLite )); + Assert.assertEquals(0, message.getExtensionCount(repeatedSfixed32ExtensionLite)); + Assert.assertEquals(0, message.getExtensionCount(repeatedSfixed64ExtensionLite)); + Assert.assertEquals(0, message.getExtensionCount(repeatedFloatExtensionLite )); + Assert.assertEquals(0, message.getExtensionCount(repeatedDoubleExtensionLite )); + Assert.assertEquals(0, message.getExtensionCount(repeatedBoolExtensionLite )); + Assert.assertEquals(0, message.getExtensionCount(repeatedStringExtensionLite )); + Assert.assertEquals(0, message.getExtensionCount(repeatedBytesExtensionLite )); + + Assert.assertEquals(0, message.getExtensionCount(repeatedGroupExtensionLite )); + Assert.assertEquals(0, message.getExtensionCount(repeatedNestedMessageExtensionLite )); + Assert.assertEquals(0, message.getExtensionCount(repeatedForeignMessageExtensionLite)); + Assert.assertEquals(0, message.getExtensionCount(repeatedImportMessageExtensionLite )); + Assert.assertEquals(0, message.getExtensionCount(repeatedLazyMessageExtensionLite )); + Assert.assertEquals(0, message.getExtensionCount(repeatedNestedEnumExtensionLite )); + Assert.assertEquals(0, message.getExtensionCount(repeatedForeignEnumExtensionLite )); + Assert.assertEquals(0, message.getExtensionCount(repeatedImportEnumExtensionLite )); + + Assert.assertEquals(0, message.getExtensionCount(repeatedStringPieceExtensionLite)); + Assert.assertEquals(0, message.getExtensionCount(repeatedCordExtensionLite)); + + // hasBlah() should also be false for all default fields. + Assert.assertFalse(message.hasExtension(defaultInt32ExtensionLite )); + Assert.assertFalse(message.hasExtension(defaultInt64ExtensionLite )); + Assert.assertFalse(message.hasExtension(defaultUint32ExtensionLite )); + Assert.assertFalse(message.hasExtension(defaultUint64ExtensionLite )); + Assert.assertFalse(message.hasExtension(defaultSint32ExtensionLite )); + Assert.assertFalse(message.hasExtension(defaultSint64ExtensionLite )); + Assert.assertFalse(message.hasExtension(defaultFixed32ExtensionLite )); + Assert.assertFalse(message.hasExtension(defaultFixed64ExtensionLite )); + Assert.assertFalse(message.hasExtension(defaultSfixed32ExtensionLite)); + Assert.assertFalse(message.hasExtension(defaultSfixed64ExtensionLite)); + Assert.assertFalse(message.hasExtension(defaultFloatExtensionLite )); + Assert.assertFalse(message.hasExtension(defaultDoubleExtensionLite )); + Assert.assertFalse(message.hasExtension(defaultBoolExtensionLite )); + Assert.assertFalse(message.hasExtension(defaultStringExtensionLite )); + Assert.assertFalse(message.hasExtension(defaultBytesExtensionLite )); + + Assert.assertFalse(message.hasExtension(defaultNestedEnumExtensionLite )); + Assert.assertFalse(message.hasExtension(defaultForeignEnumExtensionLite)); + Assert.assertFalse(message.hasExtension(defaultImportEnumExtensionLite )); + + Assert.assertFalse(message.hasExtension(defaultStringPieceExtensionLite)); + Assert.assertFalse(message.hasExtension(defaultCordExtensionLite)); + + // Fields with defaults have their default values (duh). + assertEqualsExactType( 41 , message.getExtension(defaultInt32ExtensionLite )); + assertEqualsExactType( 42L , message.getExtension(defaultInt64ExtensionLite )); + assertEqualsExactType( 43 , message.getExtension(defaultUint32ExtensionLite )); + assertEqualsExactType( 44L , message.getExtension(defaultUint64ExtensionLite )); + assertEqualsExactType(-45 , message.getExtension(defaultSint32ExtensionLite )); + assertEqualsExactType( 46L , message.getExtension(defaultSint64ExtensionLite )); + assertEqualsExactType( 47 , message.getExtension(defaultFixed32ExtensionLite )); + assertEqualsExactType( 48L , message.getExtension(defaultFixed64ExtensionLite )); + assertEqualsExactType( 49 , message.getExtension(defaultSfixed32ExtensionLite)); + assertEqualsExactType(-50L , message.getExtension(defaultSfixed64ExtensionLite)); + assertEqualsExactType( 51.5F , message.getExtension(defaultFloatExtensionLite )); + assertEqualsExactType( 52e3D , message.getExtension(defaultDoubleExtensionLite )); + assertEqualsExactType(true , message.getExtension(defaultBoolExtensionLite )); + assertEqualsExactType("hello", message.getExtension(defaultStringExtensionLite )); + assertEqualsExactType(toBytes("world"), message.getExtension(defaultBytesExtensionLite)); + + assertEqualsExactType(TestAllTypesLite.NestedEnum.BAR, + message.getExtension(defaultNestedEnumExtensionLite )); + assertEqualsExactType(ForeignEnumLite.FOREIGN_LITE_BAR, + message.getExtension(defaultForeignEnumExtensionLite)); + assertEqualsExactType(ImportEnumLite.IMPORT_LITE_BAR, + message.getExtension(defaultImportEnumExtensionLite)); + + assertEqualsExactType("abc", message.getExtension(defaultStringPieceExtensionLite)); + assertEqualsExactType("123", message.getExtension(defaultCordExtensionLite)); + + Assert.assertFalse(message.hasExtension(oneofUint32ExtensionLite)); + Assert.assertFalse(message.hasExtension(oneofNestedMessageExtensionLite)); + Assert.assertFalse(message.hasExtension(oneofStringExtensionLite)); + Assert.assertFalse(message.hasExtension(oneofBytesExtensionLite)); + } + + // ------------------------------------------------------------------- + + /** + * Assert (using {@code junit.framework.Assert}} that all extensions of + * {@code message} are set to the values assigned by {@code setAllExtensions} + * followed by {@code modifyRepeatedExtensions}. + */ + public static void assertRepeatedExtensionsModified( + TestAllExtensionsLiteOrBuilder message) { + // ModifyRepeatedFields only sets the second repeated element of each + // field. In addition to verifying this, we also verify that the first + // element and size were *not* modified. + Assert.assertEquals(2, message.getExtensionCount(repeatedInt32ExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(repeatedInt64ExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(repeatedUint32ExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(repeatedUint64ExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(repeatedSint32ExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(repeatedSint64ExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(repeatedFixed32ExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(repeatedFixed64ExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(repeatedSfixed32ExtensionLite)); + Assert.assertEquals(2, message.getExtensionCount(repeatedSfixed64ExtensionLite)); + Assert.assertEquals(2, message.getExtensionCount(repeatedFloatExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(repeatedDoubleExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(repeatedBoolExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(repeatedStringExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(repeatedBytesExtensionLite )); + + Assert.assertEquals(2, message.getExtensionCount(repeatedGroupExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(repeatedNestedMessageExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(repeatedForeignMessageExtensionLite)); + Assert.assertEquals(2, message.getExtensionCount(repeatedImportMessageExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(repeatedLazyMessageExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(repeatedNestedEnumExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(repeatedForeignEnumExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(repeatedImportEnumExtensionLite )); + + Assert.assertEquals(2, message.getExtensionCount(repeatedStringPieceExtensionLite)); + Assert.assertEquals(2, message.getExtensionCount(repeatedCordExtensionLite)); + + assertEqualsExactType(201 , message.getExtension(repeatedInt32ExtensionLite , 0)); + assertEqualsExactType(202L , message.getExtension(repeatedInt64ExtensionLite , 0)); + assertEqualsExactType(203 , message.getExtension(repeatedUint32ExtensionLite , 0)); + assertEqualsExactType(204L , message.getExtension(repeatedUint64ExtensionLite , 0)); + assertEqualsExactType(205 , message.getExtension(repeatedSint32ExtensionLite , 0)); + assertEqualsExactType(206L , message.getExtension(repeatedSint64ExtensionLite , 0)); + assertEqualsExactType(207 , message.getExtension(repeatedFixed32ExtensionLite , 0)); + assertEqualsExactType(208L , message.getExtension(repeatedFixed64ExtensionLite , 0)); + assertEqualsExactType(209 , message.getExtension(repeatedSfixed32ExtensionLite, 0)); + assertEqualsExactType(210L , message.getExtension(repeatedSfixed64ExtensionLite, 0)); + assertEqualsExactType(211F , message.getExtension(repeatedFloatExtensionLite , 0)); + assertEqualsExactType(212D , message.getExtension(repeatedDoubleExtensionLite , 0)); + assertEqualsExactType(true , message.getExtension(repeatedBoolExtensionLite , 0)); + assertEqualsExactType("215", message.getExtension(repeatedStringExtensionLite , 0)); + assertEqualsExactType(toBytes("216"), message.getExtension(repeatedBytesExtensionLite, 0)); + + assertEqualsExactType(217, message.getExtension(repeatedGroupExtensionLite ,0).getA()); + assertEqualsExactType(218, message.getExtension(repeatedNestedMessageExtensionLite ,0).getBb()); + assertEqualsExactType(219, message.getExtension(repeatedForeignMessageExtensionLite,0).getC()); + assertEqualsExactType(220, message.getExtension(repeatedImportMessageExtensionLite ,0).getD()); + assertEqualsExactType(227, message.getExtension(repeatedLazyMessageExtensionLite ,0).getBb()); + + assertEqualsExactType(TestAllTypesLite.NestedEnum.BAR, + message.getExtension(repeatedNestedEnumExtensionLite, 0)); + assertEqualsExactType(ForeignEnumLite.FOREIGN_LITE_BAR, + message.getExtension(repeatedForeignEnumExtensionLite, 0)); + assertEqualsExactType(ImportEnumLite.IMPORT_LITE_BAR, + message.getExtension(repeatedImportEnumExtensionLite, 0)); + + assertEqualsExactType("224", message.getExtension(repeatedStringPieceExtensionLite, 0)); + assertEqualsExactType("225", message.getExtension(repeatedCordExtensionLite, 0)); + + // Actually verify the second (modified) elements now. + assertEqualsExactType(501 , message.getExtension(repeatedInt32ExtensionLite , 1)); + assertEqualsExactType(502L , message.getExtension(repeatedInt64ExtensionLite , 1)); + assertEqualsExactType(503 , message.getExtension(repeatedUint32ExtensionLite , 1)); + assertEqualsExactType(504L , message.getExtension(repeatedUint64ExtensionLite , 1)); + assertEqualsExactType(505 , message.getExtension(repeatedSint32ExtensionLite , 1)); + assertEqualsExactType(506L , message.getExtension(repeatedSint64ExtensionLite , 1)); + assertEqualsExactType(507 , message.getExtension(repeatedFixed32ExtensionLite , 1)); + assertEqualsExactType(508L , message.getExtension(repeatedFixed64ExtensionLite , 1)); + assertEqualsExactType(509 , message.getExtension(repeatedSfixed32ExtensionLite, 1)); + assertEqualsExactType(510L , message.getExtension(repeatedSfixed64ExtensionLite, 1)); + assertEqualsExactType(511F , message.getExtension(repeatedFloatExtensionLite , 1)); + assertEqualsExactType(512D , message.getExtension(repeatedDoubleExtensionLite , 1)); + assertEqualsExactType(true , message.getExtension(repeatedBoolExtensionLite , 1)); + assertEqualsExactType("515", message.getExtension(repeatedStringExtensionLite , 1)); + assertEqualsExactType(toBytes("516"), message.getExtension(repeatedBytesExtensionLite, 1)); + + assertEqualsExactType(517, message.getExtension(repeatedGroupExtensionLite ,1).getA()); + assertEqualsExactType(518, message.getExtension(repeatedNestedMessageExtensionLite ,1).getBb()); + assertEqualsExactType(519, message.getExtension(repeatedForeignMessageExtensionLite,1).getC()); + assertEqualsExactType(520, message.getExtension(repeatedImportMessageExtensionLite ,1).getD()); + assertEqualsExactType(527, message.getExtension(repeatedLazyMessageExtensionLite ,1).getBb()); + + assertEqualsExactType(TestAllTypesLite.NestedEnum.FOO, + message.getExtension(repeatedNestedEnumExtensionLite, 1)); + assertEqualsExactType(ForeignEnumLite.FOREIGN_LITE_FOO, + message.getExtension(repeatedForeignEnumExtensionLite, 1)); + assertEqualsExactType(ImportEnumLite.IMPORT_LITE_FOO, + message.getExtension(repeatedImportEnumExtensionLite, 1)); + + assertEqualsExactType("524", message.getExtension(repeatedStringPieceExtensionLite, 1)); + assertEqualsExactType("525", message.getExtension(repeatedCordExtensionLite, 1)); + } + + public static void setPackedExtensions(TestPackedExtensionsLite.Builder message) { + message.addExtension(packedInt32ExtensionLite , 601); + message.addExtension(packedInt64ExtensionLite , 602L); + message.addExtension(packedUint32ExtensionLite , 603); + message.addExtension(packedUint64ExtensionLite , 604L); + message.addExtension(packedSint32ExtensionLite , 605); + message.addExtension(packedSint64ExtensionLite , 606L); + message.addExtension(packedFixed32ExtensionLite , 607); + message.addExtension(packedFixed64ExtensionLite , 608L); + message.addExtension(packedSfixed32ExtensionLite, 609); + message.addExtension(packedSfixed64ExtensionLite, 610L); + message.addExtension(packedFloatExtensionLite , 611F); + message.addExtension(packedDoubleExtensionLite , 612D); + message.addExtension(packedBoolExtensionLite , true); + message.addExtension(packedEnumExtensionLite, ForeignEnumLite.FOREIGN_LITE_BAR); + // Add a second one of each field. + message.addExtension(packedInt32ExtensionLite , 701); + message.addExtension(packedInt64ExtensionLite , 702L); + message.addExtension(packedUint32ExtensionLite , 703); + message.addExtension(packedUint64ExtensionLite , 704L); + message.addExtension(packedSint32ExtensionLite , 705); + message.addExtension(packedSint64ExtensionLite , 706L); + message.addExtension(packedFixed32ExtensionLite , 707); + message.addExtension(packedFixed64ExtensionLite , 708L); + message.addExtension(packedSfixed32ExtensionLite, 709); + message.addExtension(packedSfixed64ExtensionLite, 710L); + message.addExtension(packedFloatExtensionLite , 711F); + message.addExtension(packedDoubleExtensionLite , 712D); + message.addExtension(packedBoolExtensionLite , false); + message.addExtension(packedEnumExtensionLite, ForeignEnumLite.FOREIGN_LITE_BAZ); + } + + public static void assertPackedExtensionsSet(TestPackedExtensionsLite message) { + Assert.assertEquals(2, message.getExtensionCount(packedInt32ExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(packedInt64ExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(packedUint32ExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(packedUint64ExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(packedSint32ExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(packedSint64ExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(packedFixed32ExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(packedFixed64ExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(packedSfixed32ExtensionLite)); + Assert.assertEquals(2, message.getExtensionCount(packedSfixed64ExtensionLite)); + Assert.assertEquals(2, message.getExtensionCount(packedFloatExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(packedDoubleExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(packedBoolExtensionLite )); + Assert.assertEquals(2, message.getExtensionCount(packedEnumExtensionLite)); + assertEqualsExactType(601 , message.getExtension(packedInt32ExtensionLite , 0)); + assertEqualsExactType(602L , message.getExtension(packedInt64ExtensionLite , 0)); + assertEqualsExactType(603 , message.getExtension(packedUint32ExtensionLite , 0)); + assertEqualsExactType(604L , message.getExtension(packedUint64ExtensionLite , 0)); + assertEqualsExactType(605 , message.getExtension(packedSint32ExtensionLite , 0)); + assertEqualsExactType(606L , message.getExtension(packedSint64ExtensionLite , 0)); + assertEqualsExactType(607 , message.getExtension(packedFixed32ExtensionLite , 0)); + assertEqualsExactType(608L , message.getExtension(packedFixed64ExtensionLite , 0)); + assertEqualsExactType(609 , message.getExtension(packedSfixed32ExtensionLite, 0)); + assertEqualsExactType(610L , message.getExtension(packedSfixed64ExtensionLite, 0)); + assertEqualsExactType(611F , message.getExtension(packedFloatExtensionLite , 0)); + assertEqualsExactType(612D , message.getExtension(packedDoubleExtensionLite , 0)); + assertEqualsExactType(true , message.getExtension(packedBoolExtensionLite , 0)); + assertEqualsExactType(ForeignEnumLite.FOREIGN_LITE_BAR, + message.getExtension(packedEnumExtensionLite, 0)); + assertEqualsExactType(701 , message.getExtension(packedInt32ExtensionLite , 1)); + assertEqualsExactType(702L , message.getExtension(packedInt64ExtensionLite , 1)); + assertEqualsExactType(703 , message.getExtension(packedUint32ExtensionLite , 1)); + assertEqualsExactType(704L , message.getExtension(packedUint64ExtensionLite , 1)); + assertEqualsExactType(705 , message.getExtension(packedSint32ExtensionLite , 1)); + assertEqualsExactType(706L , message.getExtension(packedSint64ExtensionLite , 1)); + assertEqualsExactType(707 , message.getExtension(packedFixed32ExtensionLite , 1)); + assertEqualsExactType(708L , message.getExtension(packedFixed64ExtensionLite , 1)); + assertEqualsExactType(709 , message.getExtension(packedSfixed32ExtensionLite, 1)); + assertEqualsExactType(710L , message.getExtension(packedSfixed64ExtensionLite, 1)); + assertEqualsExactType(711F , message.getExtension(packedFloatExtensionLite , 1)); + assertEqualsExactType(712D , message.getExtension(packedDoubleExtensionLite , 1)); + assertEqualsExactType(false, message.getExtension(packedBoolExtensionLite , 1)); + assertEqualsExactType(ForeignEnumLite.FOREIGN_LITE_BAZ, + message.getExtension(packedEnumExtensionLite, 1)); + } + + // =================================================================== + // oneof + public static void setOneof(TestOneof2.Builder message) { + message.setFooLazyMessage( + TestOneof2.NestedMessage.newBuilder().setQuxInt(100).build()); + message.setBarString("101"); + message.setBazInt(102); + message.setBazString("103"); + } + + public static void assertOneofSet(TestOneof2 message) { + Assert.assertTrue(message.hasFooLazyMessage ()); + Assert.assertTrue(message.getFooLazyMessage().hasQuxInt()); + + Assert.assertTrue(message.hasBarString()); + Assert.assertTrue(message.hasBazInt ()); + Assert.assertTrue(message.hasBazString()); + + Assert.assertEquals(100 , message.getFooLazyMessage().getQuxInt()); + Assert.assertEquals("101", message.getBarString ()); + Assert.assertEquals(102 , message.getBazInt ()); + Assert.assertEquals("103", message.getBazString ()); + } + + public static void assertAtMostOneFieldSetOneof(TestOneof2 message) { + int count = 0; + if (message.hasFooInt()) { ++count; } + if (message.hasFooString()) { ++count; } + if (message.hasFooCord()) { ++count; } + if (message.hasFooStringPiece()) { ++count; } + if (message.hasFooBytes()) { ++count; } + if (message.hasFooEnum()) { ++count; } + if (message.hasFooMessage()) { ++count; } + if (message.hasFooGroup()) { ++count; } + if (message.hasFooLazyMessage()) { ++count; } + Assert.assertTrue(count <= 1); + + count = 0; + if (message.hasBarInt()) { ++count; } + if (message.hasBarString()) { ++count; } + if (message.hasBarCord()) { ++count; } + if (message.hasBarStringPiece()) { ++count; } + if (message.hasBarBytes()) { ++count; } + if (message.hasBarEnum()) { ++count; } + Assert.assertTrue(count <= 1); + + switch (message.getFooCase()) { + case FOO_INT: + Assert.assertTrue(message.hasFooInt()); + break; + case FOO_STRING: + Assert.assertTrue(message.hasFooString()); + break; + case FOO_CORD: + Assert.assertTrue(message.hasFooCord()); + break; + case FOO_BYTES: + Assert.assertTrue(message.hasFooBytes()); + break; + case FOO_ENUM: + Assert.assertTrue(message.hasFooEnum()); + break; + case FOO_MESSAGE: + Assert.assertTrue(message.hasFooMessage()); + break; + case FOOGROUP: + Assert.assertTrue(message.hasFooGroup()); + break; + case FOO_LAZY_MESSAGE: + Assert.assertTrue(message.hasFooLazyMessage()); + break; + case FOO_NOT_SET: + break; + } + } + + // ================================================================= + + /** + * Performs the same things that the methods of {@code TestUtil} do, but + * via the reflection interface. This is its own class because it needs + * to know what descriptor to use. + */ + public static class ReflectionTester { + private final Descriptors.Descriptor baseDescriptor; + private final ExtensionRegistry extensionRegistry; + + private final Descriptors.FileDescriptor file; + private final Descriptors.FileDescriptor importFile; + private final Descriptors.FileDescriptor publicImportFile; + + private final Descriptors.Descriptor optionalGroup; + private final Descriptors.Descriptor repeatedGroup; + private final Descriptors.Descriptor nestedMessage; + private final Descriptors.Descriptor foreignMessage; + private final Descriptors.Descriptor importMessage; + private final Descriptors.Descriptor publicImportMessage; + + private final Descriptors.FieldDescriptor groupA; + private final Descriptors.FieldDescriptor repeatedGroupA; + private final Descriptors.FieldDescriptor nestedB; + private final Descriptors.FieldDescriptor foreignC; + private final Descriptors.FieldDescriptor importD; + private final Descriptors.FieldDescriptor importE; + + private final Descriptors.EnumDescriptor nestedEnum; + private final Descriptors.EnumDescriptor foreignEnum; + private final Descriptors.EnumDescriptor importEnum; + + private final Descriptors.EnumValueDescriptor nestedFoo; + private final Descriptors.EnumValueDescriptor nestedBar; + private final Descriptors.EnumValueDescriptor nestedBaz; + private final Descriptors.EnumValueDescriptor foreignFoo; + private final Descriptors.EnumValueDescriptor foreignBar; + private final Descriptors.EnumValueDescriptor foreignBaz; + private final Descriptors.EnumValueDescriptor importFoo; + private final Descriptors.EnumValueDescriptor importBar; + private final Descriptors.EnumValueDescriptor importBaz; + + /** + * Construct a {@code ReflectionTester} that will expect messages using + * the given descriptor. + * + * Normally {@code baseDescriptor} should be a descriptor for the type + * {@code TestAllTypes}, defined in + * {@code google/protobuf/unittest.proto}. However, if + * {@code extensionRegistry} is non-null, then {@code baseDescriptor} should + * be for {@code TestAllExtensions} instead, and instead of reading and + * writing normal fields, the tester will read and write extensions. + * All of {@code TestAllExtensions}' extensions must be registered in the + * registry. + */ + public ReflectionTester(Descriptors.Descriptor baseDescriptor, + ExtensionRegistry extensionRegistry) { + this.baseDescriptor = baseDescriptor; + this.extensionRegistry = extensionRegistry; + + this.file = baseDescriptor.getFile(); + Assert.assertEquals(1, file.getDependencies().size()); + this.importFile = file.getDependencies().get(0); + this.publicImportFile = importFile.getDependencies().get(0); + + Descriptors.Descriptor testAllTypes; + if (baseDescriptor.getName() == "TestAllTypes") { + testAllTypes = baseDescriptor; + } else { + testAllTypes = file.findMessageTypeByName("TestAllTypes"); + Assert.assertNotNull(testAllTypes); + } + + if (extensionRegistry == null) { + // Use testAllTypes, rather than baseDescriptor, to allow + // initialization using TestPackedTypes descriptors. These objects + // won't be used by the methods for packed fields. + this.optionalGroup = + testAllTypes.findNestedTypeByName("OptionalGroup"); + this.repeatedGroup = + testAllTypes.findNestedTypeByName("RepeatedGroup"); + } else { + this.optionalGroup = + file.findMessageTypeByName("OptionalGroup_extension"); + this.repeatedGroup = + file.findMessageTypeByName("RepeatedGroup_extension"); + } + this.nestedMessage = testAllTypes.findNestedTypeByName("NestedMessage"); + this.foreignMessage = file.findMessageTypeByName("ForeignMessage"); + this.importMessage = importFile.findMessageTypeByName("ImportMessage"); + this.publicImportMessage = publicImportFile.findMessageTypeByName( + "PublicImportMessage"); + + this.nestedEnum = testAllTypes.findEnumTypeByName("NestedEnum"); + this.foreignEnum = file.findEnumTypeByName("ForeignEnum"); + this.importEnum = importFile.findEnumTypeByName("ImportEnum"); + + Assert.assertNotNull(optionalGroup ); + Assert.assertNotNull(repeatedGroup ); + Assert.assertNotNull(nestedMessage ); + Assert.assertNotNull(foreignMessage); + Assert.assertNotNull(importMessage ); + Assert.assertNotNull(nestedEnum ); + Assert.assertNotNull(foreignEnum ); + Assert.assertNotNull(importEnum ); + + this.nestedB = nestedMessage .findFieldByName("bb"); + this.foreignC = foreignMessage.findFieldByName("c"); + this.importD = importMessage .findFieldByName("d"); + this.importE = publicImportMessage.findFieldByName("e"); + this.nestedFoo = nestedEnum.findValueByName("FOO"); + this.nestedBar = nestedEnum.findValueByName("BAR"); + this.nestedBaz = nestedEnum.findValueByName("BAZ"); + this.foreignFoo = foreignEnum.findValueByName("FOREIGN_FOO"); + this.foreignBar = foreignEnum.findValueByName("FOREIGN_BAR"); + this.foreignBaz = foreignEnum.findValueByName("FOREIGN_BAZ"); + this.importFoo = importEnum.findValueByName("IMPORT_FOO"); + this.importBar = importEnum.findValueByName("IMPORT_BAR"); + this.importBaz = importEnum.findValueByName("IMPORT_BAZ"); + + this.groupA = optionalGroup.findFieldByName("a"); + this.repeatedGroupA = repeatedGroup.findFieldByName("a"); + + Assert.assertNotNull(groupA ); + Assert.assertNotNull(repeatedGroupA); + Assert.assertNotNull(nestedB ); + Assert.assertNotNull(foreignC ); + Assert.assertNotNull(importD ); + Assert.assertNotNull(importE ); + Assert.assertNotNull(nestedFoo ); + Assert.assertNotNull(nestedBar ); + Assert.assertNotNull(nestedBaz ); + Assert.assertNotNull(foreignFoo ); + Assert.assertNotNull(foreignBar ); + Assert.assertNotNull(foreignBaz ); + Assert.assertNotNull(importFoo ); + Assert.assertNotNull(importBar ); + Assert.assertNotNull(importBaz ); + } + + /** + * Shorthand to get a FieldDescriptor for a field of unittest::TestAllTypes. + */ + private Descriptors.FieldDescriptor f(String name) { + Descriptors.FieldDescriptor result; + if (extensionRegistry == null) { + result = baseDescriptor.findFieldByName(name); + } else { + result = file.findExtensionByName(name + "_extension"); + } + Assert.assertNotNull(result); + return result; + } + + /** + * Calls {@code parent.newBuilderForField()} or uses the + * {@code ExtensionRegistry} to find an appropriate builder, depending + * on what type is being tested. + */ + private Message.Builder newBuilderForField( + Message.Builder parent, Descriptors.FieldDescriptor field) { + if (extensionRegistry == null) { + return parent.newBuilderForField(field); + } else { + ExtensionRegistry.ExtensionInfo extension = + extensionRegistry.findImmutableExtensionByNumber( + field.getContainingType(), field.getNumber()); + Assert.assertNotNull(extension); + Assert.assertNotNull(extension.defaultInstance); + return extension.defaultInstance.newBuilderForType(); + } + } + + // ------------------------------------------------------------------- + + /** + * Set every field of {@code message} to the values expected by + * {@code assertAllFieldsSet()}, using the {@link Message.Builder} + * reflection interface. + */ + void setAllFieldsViaReflection(Message.Builder message) { + message.setField(f("optional_int32" ), 101 ); + message.setField(f("optional_int64" ), 102L); + message.setField(f("optional_uint32" ), 103 ); + message.setField(f("optional_uint64" ), 104L); + message.setField(f("optional_sint32" ), 105 ); + message.setField(f("optional_sint64" ), 106L); + message.setField(f("optional_fixed32" ), 107 ); + message.setField(f("optional_fixed64" ), 108L); + message.setField(f("optional_sfixed32"), 109 ); + message.setField(f("optional_sfixed64"), 110L); + message.setField(f("optional_float" ), 111F); + message.setField(f("optional_double" ), 112D); + message.setField(f("optional_bool" ), true); + message.setField(f("optional_string" ), "115"); + message.setField(f("optional_bytes" ), toBytes("116")); + + message.setField(f("optionalgroup"), + newBuilderForField(message, f("optionalgroup")) + .setField(groupA, 117).build()); + message.setField(f("optional_nested_message"), + newBuilderForField(message, f("optional_nested_message")) + .setField(nestedB, 118).build()); + message.setField(f("optional_foreign_message"), + newBuilderForField(message, f("optional_foreign_message")) + .setField(foreignC, 119).build()); + message.setField(f("optional_import_message"), + newBuilderForField(message, f("optional_import_message")) + .setField(importD, 120).build()); + message.setField(f("optional_public_import_message"), + newBuilderForField(message, f("optional_public_import_message")) + .setField(importE, 126).build()); + message.setField(f("optional_lazy_message"), + newBuilderForField(message, f("optional_lazy_message")) + .setField(nestedB, 127).build()); + + message.setField(f("optional_nested_enum" ), nestedBaz); + message.setField(f("optional_foreign_enum"), foreignBaz); + message.setField(f("optional_import_enum" ), importBaz); + + message.setField(f("optional_string_piece" ), "124"); + message.setField(f("optional_cord" ), "125"); + + // ----------------------------------------------------------------- + + message.addRepeatedField(f("repeated_int32" ), 201 ); + message.addRepeatedField(f("repeated_int64" ), 202L); + message.addRepeatedField(f("repeated_uint32" ), 203 ); + message.addRepeatedField(f("repeated_uint64" ), 204L); + message.addRepeatedField(f("repeated_sint32" ), 205 ); + message.addRepeatedField(f("repeated_sint64" ), 206L); + message.addRepeatedField(f("repeated_fixed32" ), 207 ); + message.addRepeatedField(f("repeated_fixed64" ), 208L); + message.addRepeatedField(f("repeated_sfixed32"), 209 ); + message.addRepeatedField(f("repeated_sfixed64"), 210L); + message.addRepeatedField(f("repeated_float" ), 211F); + message.addRepeatedField(f("repeated_double" ), 212D); + message.addRepeatedField(f("repeated_bool" ), true); + message.addRepeatedField(f("repeated_string" ), "215"); + message.addRepeatedField(f("repeated_bytes" ), toBytes("216")); + + message.addRepeatedField(f("repeatedgroup"), + newBuilderForField(message, f("repeatedgroup")) + .setField(repeatedGroupA, 217).build()); + message.addRepeatedField(f("repeated_nested_message"), + newBuilderForField(message, f("repeated_nested_message")) + .setField(nestedB, 218).build()); + message.addRepeatedField(f("repeated_foreign_message"), + newBuilderForField(message, f("repeated_foreign_message")) + .setField(foreignC, 219).build()); + message.addRepeatedField(f("repeated_import_message"), + newBuilderForField(message, f("repeated_import_message")) + .setField(importD, 220).build()); + message.addRepeatedField(f("repeated_lazy_message"), + newBuilderForField(message, f("repeated_lazy_message")) + .setField(nestedB, 227).build()); + + message.addRepeatedField(f("repeated_nested_enum" ), nestedBar); + message.addRepeatedField(f("repeated_foreign_enum"), foreignBar); + message.addRepeatedField(f("repeated_import_enum" ), importBar); + + message.addRepeatedField(f("repeated_string_piece" ), "224"); + message.addRepeatedField(f("repeated_cord" ), "225"); + + // Add a second one of each field. + message.addRepeatedField(f("repeated_int32" ), 301 ); + message.addRepeatedField(f("repeated_int64" ), 302L); + message.addRepeatedField(f("repeated_uint32" ), 303 ); + message.addRepeatedField(f("repeated_uint64" ), 304L); + message.addRepeatedField(f("repeated_sint32" ), 305 ); + message.addRepeatedField(f("repeated_sint64" ), 306L); + message.addRepeatedField(f("repeated_fixed32" ), 307 ); + message.addRepeatedField(f("repeated_fixed64" ), 308L); + message.addRepeatedField(f("repeated_sfixed32"), 309 ); + message.addRepeatedField(f("repeated_sfixed64"), 310L); + message.addRepeatedField(f("repeated_float" ), 311F); + message.addRepeatedField(f("repeated_double" ), 312D); + message.addRepeatedField(f("repeated_bool" ), false); + message.addRepeatedField(f("repeated_string" ), "315"); + message.addRepeatedField(f("repeated_bytes" ), toBytes("316")); + + message.addRepeatedField(f("repeatedgroup"), + newBuilderForField(message, f("repeatedgroup")) + .setField(repeatedGroupA, 317).build()); + message.addRepeatedField(f("repeated_nested_message"), + newBuilderForField(message, f("repeated_nested_message")) + .setField(nestedB, 318).build()); + message.addRepeatedField(f("repeated_foreign_message"), + newBuilderForField(message, f("repeated_foreign_message")) + .setField(foreignC, 319).build()); + message.addRepeatedField(f("repeated_import_message"), + newBuilderForField(message, f("repeated_import_message")) + .setField(importD, 320).build()); + message.addRepeatedField(f("repeated_lazy_message"), + newBuilderForField(message, f("repeated_lazy_message")) + .setField(nestedB, 327).build()); + + message.addRepeatedField(f("repeated_nested_enum" ), nestedBaz); + message.addRepeatedField(f("repeated_foreign_enum"), foreignBaz); + message.addRepeatedField(f("repeated_import_enum" ), importBaz); + + message.addRepeatedField(f("repeated_string_piece" ), "324"); + message.addRepeatedField(f("repeated_cord" ), "325"); + + // ----------------------------------------------------------------- + + message.setField(f("default_int32" ), 401 ); + message.setField(f("default_int64" ), 402L); + message.setField(f("default_uint32" ), 403 ); + message.setField(f("default_uint64" ), 404L); + message.setField(f("default_sint32" ), 405 ); + message.setField(f("default_sint64" ), 406L); + message.setField(f("default_fixed32" ), 407 ); + message.setField(f("default_fixed64" ), 408L); + message.setField(f("default_sfixed32"), 409 ); + message.setField(f("default_sfixed64"), 410L); + message.setField(f("default_float" ), 411F); + message.setField(f("default_double" ), 412D); + message.setField(f("default_bool" ), false); + message.setField(f("default_string" ), "415"); + message.setField(f("default_bytes" ), toBytes("416")); + + message.setField(f("default_nested_enum" ), nestedFoo); + message.setField(f("default_foreign_enum"), foreignFoo); + message.setField(f("default_import_enum" ), importFoo); + + message.setField(f("default_string_piece" ), "424"); + message.setField(f("default_cord" ), "425"); + + message.setField(f("oneof_uint32" ), 601); + message.setField(f("oneof_nested_message"), + newBuilderForField(message, f("oneof_nested_message")) + .setField(nestedB, 602).build()); + message.setField(f("oneof_string" ), "603"); + message.setField(f("oneof_bytes" ), toBytes("604")); + } + + // ------------------------------------------------------------------- + + /** + * Modify the repeated fields of {@code message} to contain the values + * expected by {@code assertRepeatedFieldsModified()}, using the + * {@link Message.Builder} reflection interface. + */ + void modifyRepeatedFieldsViaReflection(Message.Builder message) { + message.setRepeatedField(f("repeated_int32" ), 1, 501 ); + message.setRepeatedField(f("repeated_int64" ), 1, 502L); + message.setRepeatedField(f("repeated_uint32" ), 1, 503 ); + message.setRepeatedField(f("repeated_uint64" ), 1, 504L); + message.setRepeatedField(f("repeated_sint32" ), 1, 505 ); + message.setRepeatedField(f("repeated_sint64" ), 1, 506L); + message.setRepeatedField(f("repeated_fixed32" ), 1, 507 ); + message.setRepeatedField(f("repeated_fixed64" ), 1, 508L); + message.setRepeatedField(f("repeated_sfixed32"), 1, 509 ); + message.setRepeatedField(f("repeated_sfixed64"), 1, 510L); + message.setRepeatedField(f("repeated_float" ), 1, 511F); + message.setRepeatedField(f("repeated_double" ), 1, 512D); + message.setRepeatedField(f("repeated_bool" ), 1, true); + message.setRepeatedField(f("repeated_string" ), 1, "515"); + message.setRepeatedField(f("repeated_bytes" ), 1, toBytes("516")); + + message.setRepeatedField(f("repeatedgroup"), 1, + newBuilderForField(message, f("repeatedgroup")) + .setField(repeatedGroupA, 517).build()); + message.setRepeatedField(f("repeated_nested_message"), 1, + newBuilderForField(message, f("repeated_nested_message")) + .setField(nestedB, 518).build()); + message.setRepeatedField(f("repeated_foreign_message"), 1, + newBuilderForField(message, f("repeated_foreign_message")) + .setField(foreignC, 519).build()); + message.setRepeatedField(f("repeated_import_message"), 1, + newBuilderForField(message, f("repeated_import_message")) + .setField(importD, 520).build()); + message.setRepeatedField(f("repeated_lazy_message"), 1, + newBuilderForField(message, f("repeated_lazy_message")) + .setField(nestedB, 527).build()); + + message.setRepeatedField(f("repeated_nested_enum" ), 1, nestedFoo); + message.setRepeatedField(f("repeated_foreign_enum"), 1, foreignFoo); + message.setRepeatedField(f("repeated_import_enum" ), 1, importFoo); + + message.setRepeatedField(f("repeated_string_piece"), 1, "524"); + message.setRepeatedField(f("repeated_cord"), 1, "525"); + } + + // ------------------------------------------------------------------- + + /** + * Assert (using {@code junit.framework.Assert}} that all fields of + * {@code message} are set to the values assigned by {@code setAllFields}, + * using the {@link Message} reflection interface. + */ + public void assertAllFieldsSetViaReflection(MessageOrBuilder message) { + Assert.assertTrue(message.hasField(f("optional_int32" ))); + Assert.assertTrue(message.hasField(f("optional_int64" ))); + Assert.assertTrue(message.hasField(f("optional_uint32" ))); + Assert.assertTrue(message.hasField(f("optional_uint64" ))); + Assert.assertTrue(message.hasField(f("optional_sint32" ))); + Assert.assertTrue(message.hasField(f("optional_sint64" ))); + Assert.assertTrue(message.hasField(f("optional_fixed32" ))); + Assert.assertTrue(message.hasField(f("optional_fixed64" ))); + Assert.assertTrue(message.hasField(f("optional_sfixed32"))); + Assert.assertTrue(message.hasField(f("optional_sfixed64"))); + Assert.assertTrue(message.hasField(f("optional_float" ))); + Assert.assertTrue(message.hasField(f("optional_double" ))); + Assert.assertTrue(message.hasField(f("optional_bool" ))); + Assert.assertTrue(message.hasField(f("optional_string" ))); + Assert.assertTrue(message.hasField(f("optional_bytes" ))); + + Assert.assertTrue(message.hasField(f("optionalgroup" ))); + Assert.assertTrue(message.hasField(f("optional_nested_message" ))); + Assert.assertTrue(message.hasField(f("optional_foreign_message"))); + Assert.assertTrue(message.hasField(f("optional_import_message" ))); + + Assert.assertTrue( + ((Message)message.getField(f("optionalgroup"))).hasField(groupA)); + Assert.assertTrue( + ((Message)message.getField(f("optional_nested_message"))) + .hasField(nestedB)); + Assert.assertTrue( + ((Message)message.getField(f("optional_foreign_message"))) + .hasField(foreignC)); + Assert.assertTrue( + ((Message)message.getField(f("optional_import_message"))) + .hasField(importD)); + + Assert.assertTrue(message.hasField(f("optional_nested_enum" ))); + Assert.assertTrue(message.hasField(f("optional_foreign_enum"))); + Assert.assertTrue(message.hasField(f("optional_import_enum" ))); + + Assert.assertTrue(message.hasField(f("optional_string_piece"))); + Assert.assertTrue(message.hasField(f("optional_cord"))); + + Assert.assertEquals(101 , message.getField(f("optional_int32" ))); + Assert.assertEquals(102L , message.getField(f("optional_int64" ))); + Assert.assertEquals(103 , message.getField(f("optional_uint32" ))); + Assert.assertEquals(104L , message.getField(f("optional_uint64" ))); + Assert.assertEquals(105 , message.getField(f("optional_sint32" ))); + Assert.assertEquals(106L , message.getField(f("optional_sint64" ))); + Assert.assertEquals(107 , message.getField(f("optional_fixed32" ))); + Assert.assertEquals(108L , message.getField(f("optional_fixed64" ))); + Assert.assertEquals(109 , message.getField(f("optional_sfixed32"))); + Assert.assertEquals(110L , message.getField(f("optional_sfixed64"))); + Assert.assertEquals(111F , message.getField(f("optional_float" ))); + Assert.assertEquals(112D , message.getField(f("optional_double" ))); + Assert.assertEquals(true , message.getField(f("optional_bool" ))); + Assert.assertEquals("115", message.getField(f("optional_string" ))); + Assert.assertEquals(toBytes("116"), message.getField(f("optional_bytes"))); + + Assert.assertEquals(117, + ((Message)message.getField(f("optionalgroup"))).getField(groupA)); + Assert.assertEquals(118, + ((Message)message.getField(f("optional_nested_message"))) + .getField(nestedB)); + Assert.assertEquals(119, + ((Message)message.getField(f("optional_foreign_message"))) + .getField(foreignC)); + Assert.assertEquals(120, + ((Message)message.getField(f("optional_import_message"))) + .getField(importD)); + Assert.assertEquals(126, + ((Message)message.getField(f("optional_public_import_message"))) + .getField(importE)); + Assert.assertEquals(127, + ((Message)message.getField(f("optional_lazy_message"))) + .getField(nestedB)); + + Assert.assertEquals( nestedBaz, message.getField(f("optional_nested_enum" ))); + Assert.assertEquals(foreignBaz, message.getField(f("optional_foreign_enum"))); + Assert.assertEquals( importBaz, message.getField(f("optional_import_enum" ))); + + Assert.assertEquals("124", message.getField(f("optional_string_piece"))); + Assert.assertEquals("125", message.getField(f("optional_cord"))); + + // ----------------------------------------------------------------- + + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_int32" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_int64" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_uint32" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_uint64" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_sint32" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_sint64" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_fixed32" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_fixed64" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_sfixed32"))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_sfixed64"))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_float" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_double" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_bool" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_string" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_bytes" ))); + + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeatedgroup" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_nested_message" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_foreign_message"))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_import_message" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_lazy_message" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_nested_enum" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_foreign_enum" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_import_enum" ))); + + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_string_piece"))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_cord"))); + + Assert.assertEquals(201 , message.getRepeatedField(f("repeated_int32" ), 0)); + Assert.assertEquals(202L , message.getRepeatedField(f("repeated_int64" ), 0)); + Assert.assertEquals(203 , message.getRepeatedField(f("repeated_uint32" ), 0)); + Assert.assertEquals(204L , message.getRepeatedField(f("repeated_uint64" ), 0)); + Assert.assertEquals(205 , message.getRepeatedField(f("repeated_sint32" ), 0)); + Assert.assertEquals(206L , message.getRepeatedField(f("repeated_sint64" ), 0)); + Assert.assertEquals(207 , message.getRepeatedField(f("repeated_fixed32" ), 0)); + Assert.assertEquals(208L , message.getRepeatedField(f("repeated_fixed64" ), 0)); + Assert.assertEquals(209 , message.getRepeatedField(f("repeated_sfixed32"), 0)); + Assert.assertEquals(210L , message.getRepeatedField(f("repeated_sfixed64"), 0)); + Assert.assertEquals(211F , message.getRepeatedField(f("repeated_float" ), 0)); + Assert.assertEquals(212D , message.getRepeatedField(f("repeated_double" ), 0)); + Assert.assertEquals(true , message.getRepeatedField(f("repeated_bool" ), 0)); + Assert.assertEquals("215", message.getRepeatedField(f("repeated_string" ), 0)); + Assert.assertEquals(toBytes("216"), message.getRepeatedField(f("repeated_bytes"), 0)); + + Assert.assertEquals(217, + ((Message)message.getRepeatedField(f("repeatedgroup"), 0)) + .getField(repeatedGroupA)); + Assert.assertEquals(218, + ((Message)message.getRepeatedField(f("repeated_nested_message"), 0)) + .getField(nestedB)); + Assert.assertEquals(219, + ((Message)message.getRepeatedField(f("repeated_foreign_message"), 0)) + .getField(foreignC)); + Assert.assertEquals(220, + ((Message)message.getRepeatedField(f("repeated_import_message"), 0)) + .getField(importD)); + Assert.assertEquals(227, + ((Message)message.getRepeatedField(f("repeated_lazy_message"), 0)) + .getField(nestedB)); + + Assert.assertEquals( nestedBar, message.getRepeatedField(f("repeated_nested_enum" ),0)); + Assert.assertEquals(foreignBar, message.getRepeatedField(f("repeated_foreign_enum"),0)); + Assert.assertEquals( importBar, message.getRepeatedField(f("repeated_import_enum" ),0)); + + Assert.assertEquals("224", message.getRepeatedField(f("repeated_string_piece"), 0)); + Assert.assertEquals("225", message.getRepeatedField(f("repeated_cord"), 0)); + + Assert.assertEquals(301 , message.getRepeatedField(f("repeated_int32" ), 1)); + Assert.assertEquals(302L , message.getRepeatedField(f("repeated_int64" ), 1)); + Assert.assertEquals(303 , message.getRepeatedField(f("repeated_uint32" ), 1)); + Assert.assertEquals(304L , message.getRepeatedField(f("repeated_uint64" ), 1)); + Assert.assertEquals(305 , message.getRepeatedField(f("repeated_sint32" ), 1)); + Assert.assertEquals(306L , message.getRepeatedField(f("repeated_sint64" ), 1)); + Assert.assertEquals(307 , message.getRepeatedField(f("repeated_fixed32" ), 1)); + Assert.assertEquals(308L , message.getRepeatedField(f("repeated_fixed64" ), 1)); + Assert.assertEquals(309 , message.getRepeatedField(f("repeated_sfixed32"), 1)); + Assert.assertEquals(310L , message.getRepeatedField(f("repeated_sfixed64"), 1)); + Assert.assertEquals(311F , message.getRepeatedField(f("repeated_float" ), 1)); + Assert.assertEquals(312D , message.getRepeatedField(f("repeated_double" ), 1)); + Assert.assertEquals(false, message.getRepeatedField(f("repeated_bool" ), 1)); + Assert.assertEquals("315", message.getRepeatedField(f("repeated_string" ), 1)); + Assert.assertEquals(toBytes("316"), message.getRepeatedField(f("repeated_bytes"), 1)); + + Assert.assertEquals(317, + ((Message)message.getRepeatedField(f("repeatedgroup"), 1)) + .getField(repeatedGroupA)); + Assert.assertEquals(318, + ((Message)message.getRepeatedField(f("repeated_nested_message"), 1)) + .getField(nestedB)); + Assert.assertEquals(319, + ((Message)message.getRepeatedField(f("repeated_foreign_message"), 1)) + .getField(foreignC)); + Assert.assertEquals(320, + ((Message)message.getRepeatedField(f("repeated_import_message"), 1)) + .getField(importD)); + Assert.assertEquals(327, + ((Message)message.getRepeatedField(f("repeated_lazy_message"), 1)) + .getField(nestedB)); + + Assert.assertEquals( nestedBaz, message.getRepeatedField(f("repeated_nested_enum" ),1)); + Assert.assertEquals(foreignBaz, message.getRepeatedField(f("repeated_foreign_enum"),1)); + Assert.assertEquals( importBaz, message.getRepeatedField(f("repeated_import_enum" ),1)); + + Assert.assertEquals("324", message.getRepeatedField(f("repeated_string_piece"), 1)); + Assert.assertEquals("325", message.getRepeatedField(f("repeated_cord"), 1)); + + // ----------------------------------------------------------------- + + Assert.assertTrue(message.hasField(f("default_int32" ))); + Assert.assertTrue(message.hasField(f("default_int64" ))); + Assert.assertTrue(message.hasField(f("default_uint32" ))); + Assert.assertTrue(message.hasField(f("default_uint64" ))); + Assert.assertTrue(message.hasField(f("default_sint32" ))); + Assert.assertTrue(message.hasField(f("default_sint64" ))); + Assert.assertTrue(message.hasField(f("default_fixed32" ))); + Assert.assertTrue(message.hasField(f("default_fixed64" ))); + Assert.assertTrue(message.hasField(f("default_sfixed32"))); + Assert.assertTrue(message.hasField(f("default_sfixed64"))); + Assert.assertTrue(message.hasField(f("default_float" ))); + Assert.assertTrue(message.hasField(f("default_double" ))); + Assert.assertTrue(message.hasField(f("default_bool" ))); + Assert.assertTrue(message.hasField(f("default_string" ))); + Assert.assertTrue(message.hasField(f("default_bytes" ))); + + Assert.assertTrue(message.hasField(f("default_nested_enum" ))); + Assert.assertTrue(message.hasField(f("default_foreign_enum"))); + Assert.assertTrue(message.hasField(f("default_import_enum" ))); + + Assert.assertTrue(message.hasField(f("default_string_piece"))); + Assert.assertTrue(message.hasField(f("default_cord"))); + + Assert.assertEquals(401 , message.getField(f("default_int32" ))); + Assert.assertEquals(402L , message.getField(f("default_int64" ))); + Assert.assertEquals(403 , message.getField(f("default_uint32" ))); + Assert.assertEquals(404L , message.getField(f("default_uint64" ))); + Assert.assertEquals(405 , message.getField(f("default_sint32" ))); + Assert.assertEquals(406L , message.getField(f("default_sint64" ))); + Assert.assertEquals(407 , message.getField(f("default_fixed32" ))); + Assert.assertEquals(408L , message.getField(f("default_fixed64" ))); + Assert.assertEquals(409 , message.getField(f("default_sfixed32"))); + Assert.assertEquals(410L , message.getField(f("default_sfixed64"))); + Assert.assertEquals(411F , message.getField(f("default_float" ))); + Assert.assertEquals(412D , message.getField(f("default_double" ))); + Assert.assertEquals(false, message.getField(f("default_bool" ))); + Assert.assertEquals("415", message.getField(f("default_string" ))); + Assert.assertEquals(toBytes("416"), message.getField(f("default_bytes"))); + + Assert.assertEquals( nestedFoo, message.getField(f("default_nested_enum" ))); + Assert.assertEquals(foreignFoo, message.getField(f("default_foreign_enum"))); + Assert.assertEquals( importFoo, message.getField(f("default_import_enum" ))); + + Assert.assertEquals("424", message.getField(f("default_string_piece"))); + Assert.assertEquals("425", message.getField(f("default_cord"))); + + Assert.assertTrue(message.hasField(f("oneof_bytes"))); + Assert.assertEquals(toBytes("604"), message.getField(f("oneof_bytes"))); + + if (extensionRegistry == null) { + Assert.assertFalse(message.hasField(f("oneof_uint32"))); + Assert.assertFalse(message.hasField(f("oneof_nested_message"))); + Assert.assertFalse(message.hasField(f("oneof_string"))); + } else { + Assert.assertTrue(message.hasField(f("oneof_uint32"))); + Assert.assertTrue(message.hasField(f("oneof_nested_message"))); + Assert.assertTrue(message.hasField(f("oneof_string"))); + Assert.assertEquals(601, message.getField(f("oneof_uint32"))); + Assert.assertEquals(602, + ((MessageOrBuilder) message.getField(f("oneof_nested_message"))) + .getField(nestedB)); + Assert.assertEquals("603", message.getField(f("oneof_string"))); + } + } + + // ------------------------------------------------------------------- + + /** + * Assert (using {@code junit.framework.Assert}} that all fields of + * {@code message} are cleared, and that getting the fields returns their + * default values, using the {@link Message} reflection interface. + */ + public void assertClearViaReflection(MessageOrBuilder message) { + // has_blah() should initially be false for all optional fields. + Assert.assertFalse(message.hasField(f("optional_int32" ))); + Assert.assertFalse(message.hasField(f("optional_int64" ))); + Assert.assertFalse(message.hasField(f("optional_uint32" ))); + Assert.assertFalse(message.hasField(f("optional_uint64" ))); + Assert.assertFalse(message.hasField(f("optional_sint32" ))); + Assert.assertFalse(message.hasField(f("optional_sint64" ))); + Assert.assertFalse(message.hasField(f("optional_fixed32" ))); + Assert.assertFalse(message.hasField(f("optional_fixed64" ))); + Assert.assertFalse(message.hasField(f("optional_sfixed32"))); + Assert.assertFalse(message.hasField(f("optional_sfixed64"))); + Assert.assertFalse(message.hasField(f("optional_float" ))); + Assert.assertFalse(message.hasField(f("optional_double" ))); + Assert.assertFalse(message.hasField(f("optional_bool" ))); + Assert.assertFalse(message.hasField(f("optional_string" ))); + Assert.assertFalse(message.hasField(f("optional_bytes" ))); + + Assert.assertFalse(message.hasField(f("optionalgroup" ))); + Assert.assertFalse(message.hasField(f("optional_nested_message" ))); + Assert.assertFalse(message.hasField(f("optional_foreign_message"))); + Assert.assertFalse(message.hasField(f("optional_import_message" ))); + + Assert.assertFalse(message.hasField(f("optional_nested_enum" ))); + Assert.assertFalse(message.hasField(f("optional_foreign_enum"))); + Assert.assertFalse(message.hasField(f("optional_import_enum" ))); + + Assert.assertFalse(message.hasField(f("optional_string_piece"))); + Assert.assertFalse(message.hasField(f("optional_cord"))); + + // Optional fields without defaults are set to zero or something like it. + Assert.assertEquals(0 , message.getField(f("optional_int32" ))); + Assert.assertEquals(0L , message.getField(f("optional_int64" ))); + Assert.assertEquals(0 , message.getField(f("optional_uint32" ))); + Assert.assertEquals(0L , message.getField(f("optional_uint64" ))); + Assert.assertEquals(0 , message.getField(f("optional_sint32" ))); + Assert.assertEquals(0L , message.getField(f("optional_sint64" ))); + Assert.assertEquals(0 , message.getField(f("optional_fixed32" ))); + Assert.assertEquals(0L , message.getField(f("optional_fixed64" ))); + Assert.assertEquals(0 , message.getField(f("optional_sfixed32"))); + Assert.assertEquals(0L , message.getField(f("optional_sfixed64"))); + Assert.assertEquals(0F , message.getField(f("optional_float" ))); + Assert.assertEquals(0D , message.getField(f("optional_double" ))); + Assert.assertEquals(false, message.getField(f("optional_bool" ))); + Assert.assertEquals("" , message.getField(f("optional_string" ))); + Assert.assertEquals(ByteString.EMPTY, message.getField(f("optional_bytes"))); + + // Embedded messages should also be clear. + Assert.assertFalse( + ((Message)message.getField(f("optionalgroup"))).hasField(groupA)); + Assert.assertFalse( + ((Message)message.getField(f("optional_nested_message"))) + .hasField(nestedB)); + Assert.assertFalse( + ((Message)message.getField(f("optional_foreign_message"))) + .hasField(foreignC)); + Assert.assertFalse( + ((Message)message.getField(f("optional_import_message"))) + .hasField(importD)); + Assert.assertFalse( + ((Message)message.getField(f("optional_public_import_message"))) + .hasField(importE)); + Assert.assertFalse( + ((Message)message.getField(f("optional_lazy_message"))) + .hasField(nestedB)); + + Assert.assertEquals(0, + ((Message)message.getField(f("optionalgroup"))).getField(groupA)); + Assert.assertEquals(0, + ((Message)message.getField(f("optional_nested_message"))) + .getField(nestedB)); + Assert.assertEquals(0, + ((Message)message.getField(f("optional_foreign_message"))) + .getField(foreignC)); + Assert.assertEquals(0, + ((Message)message.getField(f("optional_import_message"))) + .getField(importD)); + Assert.assertEquals(0, + ((Message)message.getField(f("optional_public_import_message"))) + .getField(importE)); + Assert.assertEquals(0, + ((Message)message.getField(f("optional_lazy_message"))) + .getField(nestedB)); + + // Enums without defaults are set to the first value in the enum. + Assert.assertEquals( nestedFoo, message.getField(f("optional_nested_enum" ))); + Assert.assertEquals(foreignFoo, message.getField(f("optional_foreign_enum"))); + Assert.assertEquals( importFoo, message.getField(f("optional_import_enum" ))); + + Assert.assertEquals("", message.getField(f("optional_string_piece"))); + Assert.assertEquals("", message.getField(f("optional_cord"))); + + // Repeated fields are empty. + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_int32" ))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_int64" ))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_uint32" ))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_uint64" ))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_sint32" ))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_sint64" ))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_fixed32" ))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_fixed64" ))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_sfixed32"))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_sfixed64"))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_float" ))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_double" ))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_bool" ))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_string" ))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_bytes" ))); + + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeatedgroup" ))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_nested_message" ))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_foreign_message"))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_import_message" ))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_lazy_message" ))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_nested_enum" ))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_foreign_enum" ))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_import_enum" ))); + + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_string_piece"))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_cord"))); + + // has_blah() should also be false for all default fields. + Assert.assertFalse(message.hasField(f("default_int32" ))); + Assert.assertFalse(message.hasField(f("default_int64" ))); + Assert.assertFalse(message.hasField(f("default_uint32" ))); + Assert.assertFalse(message.hasField(f("default_uint64" ))); + Assert.assertFalse(message.hasField(f("default_sint32" ))); + Assert.assertFalse(message.hasField(f("default_sint64" ))); + Assert.assertFalse(message.hasField(f("default_fixed32" ))); + Assert.assertFalse(message.hasField(f("default_fixed64" ))); + Assert.assertFalse(message.hasField(f("default_sfixed32"))); + Assert.assertFalse(message.hasField(f("default_sfixed64"))); + Assert.assertFalse(message.hasField(f("default_float" ))); + Assert.assertFalse(message.hasField(f("default_double" ))); + Assert.assertFalse(message.hasField(f("default_bool" ))); + Assert.assertFalse(message.hasField(f("default_string" ))); + Assert.assertFalse(message.hasField(f("default_bytes" ))); + + Assert.assertFalse(message.hasField(f("default_nested_enum" ))); + Assert.assertFalse(message.hasField(f("default_foreign_enum"))); + Assert.assertFalse(message.hasField(f("default_import_enum" ))); + + Assert.assertFalse(message.hasField(f("default_string_piece" ))); + Assert.assertFalse(message.hasField(f("default_cord" ))); + + // Fields with defaults have their default values (duh). + Assert.assertEquals( 41 , message.getField(f("default_int32" ))); + Assert.assertEquals( 42L , message.getField(f("default_int64" ))); + Assert.assertEquals( 43 , message.getField(f("default_uint32" ))); + Assert.assertEquals( 44L , message.getField(f("default_uint64" ))); + Assert.assertEquals(-45 , message.getField(f("default_sint32" ))); + Assert.assertEquals( 46L , message.getField(f("default_sint64" ))); + Assert.assertEquals( 47 , message.getField(f("default_fixed32" ))); + Assert.assertEquals( 48L , message.getField(f("default_fixed64" ))); + Assert.assertEquals( 49 , message.getField(f("default_sfixed32"))); + Assert.assertEquals(-50L , message.getField(f("default_sfixed64"))); + Assert.assertEquals( 51.5F , message.getField(f("default_float" ))); + Assert.assertEquals( 52e3D , message.getField(f("default_double" ))); + Assert.assertEquals(true , message.getField(f("default_bool" ))); + Assert.assertEquals("hello", message.getField(f("default_string" ))); + Assert.assertEquals(toBytes("world"), message.getField(f("default_bytes"))); + + Assert.assertEquals( nestedBar, message.getField(f("default_nested_enum" ))); + Assert.assertEquals(foreignBar, message.getField(f("default_foreign_enum"))); + Assert.assertEquals( importBar, message.getField(f("default_import_enum" ))); + + Assert.assertEquals("abc", message.getField(f("default_string_piece"))); + Assert.assertEquals("123", message.getField(f("default_cord"))); + + Assert.assertFalse(message.hasField(f("oneof_uint32"))); + Assert.assertFalse(message.hasField(f("oneof_nested_message"))); + Assert.assertFalse(message.hasField(f("oneof_string"))); + Assert.assertFalse(message.hasField(f("oneof_bytes"))); + + Assert.assertEquals(0, message.getField(f("oneof_uint32"))); + Assert.assertEquals("", message.getField(f("oneof_string"))); + Assert.assertEquals(toBytes(""), message.getField(f("oneof_bytes"))); + } + + + // --------------------------------------------------------------- + + public void assertRepeatedFieldsModifiedViaReflection( + MessageOrBuilder message) { + // ModifyRepeatedFields only sets the second repeated element of each + // field. In addition to verifying this, we also verify that the first + // element and size were *not* modified. + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_int32" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_int64" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_uint32" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_uint64" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_sint32" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_sint64" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_fixed32" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_fixed64" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_sfixed32"))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_sfixed64"))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_float" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_double" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_bool" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_string" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_bytes" ))); + + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeatedgroup" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_nested_message" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_foreign_message"))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_import_message" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_lazy_message" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_nested_enum" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_foreign_enum" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_import_enum" ))); + + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_string_piece"))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_cord"))); + + Assert.assertEquals(201 , message.getRepeatedField(f("repeated_int32" ), 0)); + Assert.assertEquals(202L , message.getRepeatedField(f("repeated_int64" ), 0)); + Assert.assertEquals(203 , message.getRepeatedField(f("repeated_uint32" ), 0)); + Assert.assertEquals(204L , message.getRepeatedField(f("repeated_uint64" ), 0)); + Assert.assertEquals(205 , message.getRepeatedField(f("repeated_sint32" ), 0)); + Assert.assertEquals(206L , message.getRepeatedField(f("repeated_sint64" ), 0)); + Assert.assertEquals(207 , message.getRepeatedField(f("repeated_fixed32" ), 0)); + Assert.assertEquals(208L , message.getRepeatedField(f("repeated_fixed64" ), 0)); + Assert.assertEquals(209 , message.getRepeatedField(f("repeated_sfixed32"), 0)); + Assert.assertEquals(210L , message.getRepeatedField(f("repeated_sfixed64"), 0)); + Assert.assertEquals(211F , message.getRepeatedField(f("repeated_float" ), 0)); + Assert.assertEquals(212D , message.getRepeatedField(f("repeated_double" ), 0)); + Assert.assertEquals(true , message.getRepeatedField(f("repeated_bool" ), 0)); + Assert.assertEquals("215", message.getRepeatedField(f("repeated_string" ), 0)); + Assert.assertEquals(toBytes("216"), message.getRepeatedField(f("repeated_bytes"), 0)); + + Assert.assertEquals(217, + ((Message)message.getRepeatedField(f("repeatedgroup"), 0)) + .getField(repeatedGroupA)); + Assert.assertEquals(218, + ((Message)message.getRepeatedField(f("repeated_nested_message"), 0)) + .getField(nestedB)); + Assert.assertEquals(219, + ((Message)message.getRepeatedField(f("repeated_foreign_message"), 0)) + .getField(foreignC)); + Assert.assertEquals(220, + ((Message)message.getRepeatedField(f("repeated_import_message"), 0)) + .getField(importD)); + Assert.assertEquals(227, + ((Message)message.getRepeatedField(f("repeated_lazy_message"), 0)) + .getField(nestedB)); + + Assert.assertEquals( nestedBar, message.getRepeatedField(f("repeated_nested_enum" ),0)); + Assert.assertEquals(foreignBar, message.getRepeatedField(f("repeated_foreign_enum"),0)); + Assert.assertEquals( importBar, message.getRepeatedField(f("repeated_import_enum" ),0)); + + Assert.assertEquals("224", message.getRepeatedField(f("repeated_string_piece"), 0)); + Assert.assertEquals("225", message.getRepeatedField(f("repeated_cord"), 0)); + + Assert.assertEquals(501 , message.getRepeatedField(f("repeated_int32" ), 1)); + Assert.assertEquals(502L , message.getRepeatedField(f("repeated_int64" ), 1)); + Assert.assertEquals(503 , message.getRepeatedField(f("repeated_uint32" ), 1)); + Assert.assertEquals(504L , message.getRepeatedField(f("repeated_uint64" ), 1)); + Assert.assertEquals(505 , message.getRepeatedField(f("repeated_sint32" ), 1)); + Assert.assertEquals(506L , message.getRepeatedField(f("repeated_sint64" ), 1)); + Assert.assertEquals(507 , message.getRepeatedField(f("repeated_fixed32" ), 1)); + Assert.assertEquals(508L , message.getRepeatedField(f("repeated_fixed64" ), 1)); + Assert.assertEquals(509 , message.getRepeatedField(f("repeated_sfixed32"), 1)); + Assert.assertEquals(510L , message.getRepeatedField(f("repeated_sfixed64"), 1)); + Assert.assertEquals(511F , message.getRepeatedField(f("repeated_float" ), 1)); + Assert.assertEquals(512D , message.getRepeatedField(f("repeated_double" ), 1)); + Assert.assertEquals(true , message.getRepeatedField(f("repeated_bool" ), 1)); + Assert.assertEquals("515", message.getRepeatedField(f("repeated_string" ), 1)); + Assert.assertEquals(toBytes("516"), message.getRepeatedField(f("repeated_bytes"), 1)); + + Assert.assertEquals(517, + ((Message)message.getRepeatedField(f("repeatedgroup"), 1)) + .getField(repeatedGroupA)); + Assert.assertEquals(518, + ((Message)message.getRepeatedField(f("repeated_nested_message"), 1)) + .getField(nestedB)); + Assert.assertEquals(519, + ((Message)message.getRepeatedField(f("repeated_foreign_message"), 1)) + .getField(foreignC)); + Assert.assertEquals(520, + ((Message)message.getRepeatedField(f("repeated_import_message"), 1)) + .getField(importD)); + Assert.assertEquals(527, + ((Message)message.getRepeatedField(f("repeated_lazy_message"), 1)) + .getField(nestedB)); + + Assert.assertEquals( nestedFoo, message.getRepeatedField(f("repeated_nested_enum" ),1)); + Assert.assertEquals(foreignFoo, message.getRepeatedField(f("repeated_foreign_enum"),1)); + Assert.assertEquals( importFoo, message.getRepeatedField(f("repeated_import_enum" ),1)); + + Assert.assertEquals("524", message.getRepeatedField(f("repeated_string_piece"), 1)); + Assert.assertEquals("525", message.getRepeatedField(f("repeated_cord"), 1)); + } + + public void setPackedFieldsViaReflection(Message.Builder message) { + message.addRepeatedField(f("packed_int32" ), 601 ); + message.addRepeatedField(f("packed_int64" ), 602L); + message.addRepeatedField(f("packed_uint32" ), 603 ); + message.addRepeatedField(f("packed_uint64" ), 604L); + message.addRepeatedField(f("packed_sint32" ), 605 ); + message.addRepeatedField(f("packed_sint64" ), 606L); + message.addRepeatedField(f("packed_fixed32" ), 607 ); + message.addRepeatedField(f("packed_fixed64" ), 608L); + message.addRepeatedField(f("packed_sfixed32"), 609 ); + message.addRepeatedField(f("packed_sfixed64"), 610L); + message.addRepeatedField(f("packed_float" ), 611F); + message.addRepeatedField(f("packed_double" ), 612D); + message.addRepeatedField(f("packed_bool" ), true); + message.addRepeatedField(f("packed_enum" ), foreignBar); + // Add a second one of each field. + message.addRepeatedField(f("packed_int32" ), 701 ); + message.addRepeatedField(f("packed_int64" ), 702L); + message.addRepeatedField(f("packed_uint32" ), 703 ); + message.addRepeatedField(f("packed_uint64" ), 704L); + message.addRepeatedField(f("packed_sint32" ), 705 ); + message.addRepeatedField(f("packed_sint64" ), 706L); + message.addRepeatedField(f("packed_fixed32" ), 707 ); + message.addRepeatedField(f("packed_fixed64" ), 708L); + message.addRepeatedField(f("packed_sfixed32"), 709 ); + message.addRepeatedField(f("packed_sfixed64"), 710L); + message.addRepeatedField(f("packed_float" ), 711F); + message.addRepeatedField(f("packed_double" ), 712D); + message.addRepeatedField(f("packed_bool" ), false); + message.addRepeatedField(f("packed_enum" ), foreignBaz); + } + + public void assertPackedFieldsSetViaReflection(MessageOrBuilder message) { + Assert.assertEquals(2, message.getRepeatedFieldCount(f("packed_int32" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("packed_int64" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("packed_uint32" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("packed_uint64" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("packed_sint32" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("packed_sint64" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("packed_fixed32" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("packed_fixed64" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("packed_sfixed32"))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("packed_sfixed64"))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("packed_float" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("packed_double" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("packed_bool" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("packed_enum" ))); + Assert.assertEquals(601 , message.getRepeatedField(f("packed_int32" ), 0)); + Assert.assertEquals(602L , message.getRepeatedField(f("packed_int64" ), 0)); + Assert.assertEquals(603 , message.getRepeatedField(f("packed_uint32" ), 0)); + Assert.assertEquals(604L , message.getRepeatedField(f("packed_uint64" ), 0)); + Assert.assertEquals(605 , message.getRepeatedField(f("packed_sint32" ), 0)); + Assert.assertEquals(606L , message.getRepeatedField(f("packed_sint64" ), 0)); + Assert.assertEquals(607 , message.getRepeatedField(f("packed_fixed32" ), 0)); + Assert.assertEquals(608L , message.getRepeatedField(f("packed_fixed64" ), 0)); + Assert.assertEquals(609 , message.getRepeatedField(f("packed_sfixed32"), 0)); + Assert.assertEquals(610L , message.getRepeatedField(f("packed_sfixed64"), 0)); + Assert.assertEquals(611F , message.getRepeatedField(f("packed_float" ), 0)); + Assert.assertEquals(612D , message.getRepeatedField(f("packed_double" ), 0)); + Assert.assertEquals(true , message.getRepeatedField(f("packed_bool" ), 0)); + Assert.assertEquals(foreignBar, message.getRepeatedField(f("packed_enum" ),0)); + Assert.assertEquals(701 , message.getRepeatedField(f("packed_int32" ), 1)); + Assert.assertEquals(702L , message.getRepeatedField(f("packed_int64" ), 1)); + Assert.assertEquals(703 , message.getRepeatedField(f("packed_uint32" ), 1)); + Assert.assertEquals(704L , message.getRepeatedField(f("packed_uint64" ), 1)); + Assert.assertEquals(705 , message.getRepeatedField(f("packed_sint32" ), 1)); + Assert.assertEquals(706L , message.getRepeatedField(f("packed_sint64" ), 1)); + Assert.assertEquals(707 , message.getRepeatedField(f("packed_fixed32" ), 1)); + Assert.assertEquals(708L , message.getRepeatedField(f("packed_fixed64" ), 1)); + Assert.assertEquals(709 , message.getRepeatedField(f("packed_sfixed32"), 1)); + Assert.assertEquals(710L , message.getRepeatedField(f("packed_sfixed64"), 1)); + Assert.assertEquals(711F , message.getRepeatedField(f("packed_float" ), 1)); + Assert.assertEquals(712D , message.getRepeatedField(f("packed_double" ), 1)); + Assert.assertEquals(false, message.getRepeatedField(f("packed_bool" ), 1)); + Assert.assertEquals(foreignBaz, message.getRepeatedField(f("packed_enum" ),1)); + } + + /** + * Verifies that the reflection setters for the given.Builder object throw a + * NullPointerException if they are passed a null value. Uses Assert to throw an + * appropriate assertion failure, if the condition is not verified. + */ + public void assertReflectionSettersRejectNull(Message.Builder builder) + throws Exception { + try { + builder.setField(f("optional_string"), null); + Assert.fail("Exception was not thrown"); + } catch (NullPointerException e) { + // We expect this exception. + } + try { + builder.setField(f("optional_bytes"), null); + Assert.fail("Exception was not thrown"); + } catch (NullPointerException e) { + // We expect this exception. + } + try { + builder.setField(f("optional_nested_enum"), null); + Assert.fail("Exception was not thrown"); + } catch (NullPointerException e) { + // We expect this exception. + } + try { + builder.setField(f("optional_nested_message"), + (TestAllTypes.NestedMessage) null); + Assert.fail("Exception was not thrown"); + } catch (NullPointerException e) { + // We expect this exception. + } + try { + builder.setField(f("optional_nested_message"), + (TestAllTypes.NestedMessage.Builder) null); + Assert.fail("Exception was not thrown"); + } catch (NullPointerException e) { + // We expect this exception. + } + + try { + builder.addRepeatedField(f("repeated_string"), null); + Assert.fail("Exception was not thrown"); + } catch (NullPointerException e) { + // We expect this exception. + } + try { + builder.addRepeatedField(f("repeated_bytes"), null); + Assert.fail("Exception was not thrown"); + } catch (NullPointerException e) { + // We expect this exception. + } + try { + builder.addRepeatedField(f("repeated_nested_enum"), null); + Assert.fail("Exception was not thrown"); + } catch (NullPointerException e) { + // We expect this exception. + } + try { + builder.addRepeatedField(f("repeated_nested_message"), null); + Assert.fail("Exception was not thrown"); + } catch (NullPointerException e) { + // We expect this exception. + } + } + + /** + * Verifies that the reflection repeated setters for the given Builder object throw a + * NullPointerException if they are passed a null value. Uses Assert to throw an appropriate + * assertion failure, if the condition is not verified. + */ + public void assertReflectionRepeatedSettersRejectNull(Message.Builder builder) + throws Exception { + builder.addRepeatedField(f("repeated_string"), "one"); + try { + builder.setRepeatedField(f("repeated_string"), 0, null); + Assert.fail("Exception was not thrown"); + } catch (NullPointerException e) { + // We expect this exception. + } + + builder.addRepeatedField(f("repeated_bytes"), toBytes("one")); + try { + builder.setRepeatedField(f("repeated_bytes"), 0, null); + Assert.fail("Exception was not thrown"); + } catch (NullPointerException e) { + // We expect this exception. + } + + builder.addRepeatedField(f("repeated_nested_enum"), nestedBaz); + try { + builder.setRepeatedField(f("repeated_nested_enum"), 0, null); + Assert.fail("Exception was not thrown"); + } catch (NullPointerException e) { + // We expect this exception. + } + + builder.addRepeatedField( + f("repeated_nested_message"), + TestAllTypes.NestedMessage.newBuilder().setBb(218).build()); + try { + builder.setRepeatedField(f("repeated_nested_message"), 0, null); + Assert.fail("Exception was not thrown"); + } catch (NullPointerException e) { + // We expect this exception. + } + } + } + + /** + * @param filePath The path relative to + * {@link #getTestDataDir}. + */ + public static String readTextFromFile(String filePath) { + return readBytesFromFile(filePath).toStringUtf8(); + } + + private static File getTestDataDir() { + // Search each parent directory looking for "src/google/protobuf". + File ancestor = new File("."); + try { + ancestor = ancestor.getCanonicalFile(); + } catch (IOException e) { + throw new RuntimeException( + "Couldn't get canonical name of working directory.", e); + } + while (ancestor != null && ancestor.exists()) { + if (new File(ancestor, "src/google/protobuf").exists()) { + return new File(ancestor, "src/google/protobuf/testdata"); + } + ancestor = ancestor.getParentFile(); + } + + throw new RuntimeException( + "Could not find golden files. This test must be run from within the " + + "protobuf source package so that it can read test data files from the " + + "C++ source tree."); + } + + /** + * @param filename The path relative to + * {@link #getTestDataDir}. + */ + public static ByteString readBytesFromFile(String filename) { + File fullPath = new File(getTestDataDir(), filename); + try { + RandomAccessFile file = new RandomAccessFile(fullPath, "r"); + byte[] content = new byte[(int) file.length()]; + file.readFully(content); + return ByteString.copyFrom(content); + } catch (IOException e) { + // Throw a RuntimeException here so that we can call this function from + // static initializers. + throw new IllegalArgumentException( + "Couldn't read file: " + fullPath.getPath(), e); + } + } + + /** + * Get the bytes of the "golden message". This is a serialized TestAllTypes + * with all fields set as they would be by + * {@link #setAllFields(TestAllTypes.Builder)}, but it is loaded from a file + * on disk rather than generated dynamically. The file is actually generated + * by C++ code, so testing against it verifies compatibility with C++. + */ + public static ByteString getGoldenMessage() { + if (goldenMessage == null) { + goldenMessage = readBytesFromFile("golden_message_oneof_implemented"); + } + return goldenMessage; + } + private static ByteString goldenMessage = null; + + /** + * Get the bytes of the "golden packed fields message". This is a serialized + * TestPackedTypes with all fields set as they would be by + * {@link #setPackedFields(TestPackedTypes.Builder)}, but it is loaded from a + * file on disk rather than generated dynamically. The file is actually + * generated by C++ code, so testing against it verifies compatibility with + * C++. + */ + public static ByteString getGoldenPackedFieldsMessage() { + if (goldenPackedFieldsMessage == null) { + goldenPackedFieldsMessage = + readBytesFromFile("golden_packed_fields_message"); + } + return goldenPackedFieldsMessage; + } + private static ByteString goldenPackedFieldsMessage = null; + + /** + * Mock implementation of {@link GeneratedMessage.BuilderParent} for testing. + * + * @author jonp@google.com (Jon Perlow) + */ + public static class MockBuilderParent + implements GeneratedMessage.BuilderParent { + + private int invalidations; + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public void markDirty() { + invalidations++; + } + + public int getInvalidationCount() { + return invalidations; + } + } +} diff --git a/java/core/src/test/java/com/google/protobuf/TextFormatTest.java b/java/core/src/test/java/com/google/protobuf/TextFormatTest.java new file mode 100644 index 00000000..1df4fad7 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/TextFormatTest.java @@ -0,0 +1,1021 @@ +// 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 com.google.protobuf.TextFormat.Parser.SingularOverwritePolicy; +import protobuf_unittest.UnittestMset.TestMessageSetExtension1; +import protobuf_unittest.UnittestMset.TestMessageSetExtension2; +import protobuf_unittest.UnittestProto.OneString; +import protobuf_unittest.UnittestProto.TestAllExtensions; +import protobuf_unittest.UnittestProto.TestAllTypes; +import protobuf_unittest.UnittestProto.TestAllTypes.NestedMessage; +import protobuf_unittest.UnittestProto.TestEmptyMessage; +import protobuf_unittest.UnittestProto.TestOneof2; +import proto2_wireformat_unittest.UnittestMsetWireFormat.TestMessageSet; + +import junit.framework.TestCase; + +import java.io.StringReader; + +/** + * Test case for {@link TextFormat}. + * + * TODO(wenboz): ExtensionTest and rest of text_format_unittest.cc. + * + * @author wenboz@google.com (Wenbo Zhu) + */ +public class TextFormatTest extends TestCase { + + // A basic string with different escapable characters for testing. + private final static String kEscapeTestString = + "\"A string with ' characters \n and \r newlines and \t tabs and \001 " + + "slashes \\"; + + // A representation of the above string with all the characters escaped. + private final static String kEscapeTestStringEscaped = + "\\\"A string with \\' characters \\n and \\r newlines " + + "and \\t tabs and \\001 slashes \\\\"; + + private static String allFieldsSetText = TestUtil.readTextFromFile( + "text_format_unittest_data_oneof_implemented.txt"); + private static String allExtensionsSetText = TestUtil.readTextFromFile( + "text_format_unittest_extensions_data.txt"); + + private static String exoticText = + "repeated_int32: -1\n" + + "repeated_int32: -2147483648\n" + + "repeated_int64: -1,\n" + + "repeated_int64: -9223372036854775808\n" + + "repeated_uint32: 4294967295\n" + + "repeated_uint32: 2147483648\n" + + "repeated_uint64: 18446744073709551615\n" + + "repeated_uint64: 9223372036854775808\n" + + "repeated_double: 123.0\n" + + "repeated_double: 123.5\n" + + "repeated_double: 0.125\n" + + "repeated_double: .125\n" + + "repeated_double: -.125\n" + + "repeated_double: 1.23E17\n" + + "repeated_double: 1.23E+17\n" + + "repeated_double: -1.23e-17\n" + + "repeated_double: .23e+17\n" + + "repeated_double: -.23E17\n" + + "repeated_double: 1.235E22\n" + + "repeated_double: 1.235E-18\n" + + "repeated_double: 123.456789\n" + + "repeated_double: Infinity\n" + + "repeated_double: -Infinity\n" + + "repeated_double: NaN\n" + + "repeated_string: \"\\000\\001\\a\\b\\f\\n\\r\\t\\v\\\\\\'\\\"" + + "\\341\\210\\264\"\n" + + "repeated_bytes: \"\\000\\001\\a\\b\\f\\n\\r\\t\\v\\\\\\'\\\"\\376\"\n"; + + private static String canonicalExoticText = + exoticText.replace(": .", ": 0.").replace(": -.", ": -0.") // short-form double + .replace("23e", "23E").replace("E+", "E").replace("0.23E17", "2.3E16").replace(",", ""); + + private String messageSetText = + "[protobuf_unittest.TestMessageSetExtension1] {\n" + + " i: 123\n" + + "}\n" + + "[protobuf_unittest.TestMessageSetExtension2] {\n" + + " str: \"foo\"\n" + + "}\n"; + + private String messageSetTextWithRepeatedExtension = + "[protobuf_unittest.TestMessageSetExtension1] {\n" + + " i: 123\n" + + "}\n" + + "[protobuf_unittest.TestMessageSetExtension1] {\n" + + " i: 456\n" + + "}\n"; + + + private final TextFormat.Parser parserWithOverwriteForbidden = + TextFormat.Parser.newBuilder() + .setSingularOverwritePolicy( + SingularOverwritePolicy.FORBID_SINGULAR_OVERWRITES) + .build(); + + private final TextFormat.Parser defaultParser = + TextFormat.Parser.newBuilder().build(); + + /** Print TestAllTypes and compare with golden file. */ + public void testPrintMessage() throws Exception { + String javaText = TextFormat.printToString(TestUtil.getAllSet()); + + // Java likes to add a trailing ".0" to floats and doubles. C printf + // (with %g format) does not. Our golden files are used for both + // C++ and Java TextFormat classes, so we need to conform. + javaText = javaText.replace(".0\n", "\n"); + + assertEquals(allFieldsSetText, javaText); + } + + /** Print TestAllTypes as Builder and compare with golden file. */ + public void testPrintMessageBuilder() throws Exception { + String javaText = TextFormat.printToString(TestUtil.getAllSetBuilder()); + + // Java likes to add a trailing ".0" to floats and doubles. C printf + // (with %g format) does not. Our golden files are used for both + // C++ and Java TextFormat classes, so we need to conform. + javaText = javaText.replace(".0\n", "\n"); + + assertEquals(allFieldsSetText, javaText); + } + + /** Print TestAllExtensions and compare with golden file. */ + public void testPrintExtensions() throws Exception { + String javaText = TextFormat.printToString(TestUtil.getAllExtensionsSet()); + + // Java likes to add a trailing ".0" to floats and doubles. C printf + // (with %g format) does not. Our golden files are used for both + // C++ and Java TextFormat classes, so we need to conform. + javaText = javaText.replace(".0\n", "\n"); + + assertEquals(allExtensionsSetText, javaText); + } + + // Creates an example unknown field set. + private UnknownFieldSet makeUnknownFieldSet() { + return UnknownFieldSet.newBuilder() + .addField(5, + UnknownFieldSet.Field.newBuilder() + .addVarint(1) + .addFixed32(2) + .addFixed64(3) + .addLengthDelimited(ByteString.copyFromUtf8("4")) + .addGroup( + UnknownFieldSet.newBuilder() + .addField(10, + UnknownFieldSet.Field.newBuilder() + .addVarint(5) + .build()) + .build()) + .build()) + .addField(8, + UnknownFieldSet.Field.newBuilder() + .addVarint(1) + .addVarint(2) + .addVarint(3) + .build()) + .addField(15, + UnknownFieldSet.Field.newBuilder() + .addVarint(0xABCDEF1234567890L) + .addFixed32(0xABCD1234) + .addFixed64(0xABCDEF1234567890L) + .build()) + .build(); + } + + public void testPrintUnknownFields() throws Exception { + // Test printing of unknown fields in a message. + + TestEmptyMessage message = + TestEmptyMessage.newBuilder() + .setUnknownFields(makeUnknownFieldSet()) + .build(); + + assertEquals( + "5: 1\n" + + "5: 0x00000002\n" + + "5: 0x0000000000000003\n" + + "5: \"4\"\n" + + "5 {\n" + + " 10: 5\n" + + "}\n" + + "8: 1\n" + + "8: 2\n" + + "8: 3\n" + + "15: 12379813812177893520\n" + + "15: 0xabcd1234\n" + + "15: 0xabcdef1234567890\n", + TextFormat.printToString(message)); + } + + public void testPrintField() throws Exception { + final FieldDescriptor dataField = + OneString.getDescriptor().findFieldByName("data"); + assertEquals( + "data: \"test data\"\n", + TextFormat.printFieldToString(dataField, "test data")); + + final FieldDescriptor optionalField = + TestAllTypes.getDescriptor().findFieldByName("optional_nested_message"); + final Object value = NestedMessage.newBuilder().setBb(42).build(); + + assertEquals( + "optional_nested_message {\n bb: 42\n}\n", + TextFormat.printFieldToString(optionalField, value)); + } + + /** + * Helper to construct a ByteString from a String containing only 8-bit + * characters. The characters are converted directly to bytes, *not* + * encoded using UTF-8. + */ + private ByteString bytes(String str) { + return ByteString.copyFrom(str.getBytes(Internal.ISO_8859_1)); + } + + /** + * Helper to construct a ByteString from a bunch of bytes. The inputs are + * actually ints so that I can use hex notation and not get stupid errors + * about precision. + */ + private ByteString bytes(int... bytesAsInts) { + byte[] bytes = new byte[bytesAsInts.length]; + for (int i = 0; i < bytesAsInts.length; i++) { + bytes[i] = (byte) bytesAsInts[i]; + } + return ByteString.copyFrom(bytes); + } + + public void testPrintExotic() throws Exception { + Message message = TestAllTypes.newBuilder() + // Signed vs. unsigned numbers. + .addRepeatedInt32 (-1) + .addRepeatedUint32(-1) + .addRepeatedInt64 (-1) + .addRepeatedUint64(-1) + + .addRepeatedInt32 (1 << 31) + .addRepeatedUint32(1 << 31) + .addRepeatedInt64 (1L << 63) + .addRepeatedUint64(1L << 63) + + // Floats of various precisions and exponents. + .addRepeatedDouble(123) + .addRepeatedDouble(123.5) + .addRepeatedDouble(0.125) + .addRepeatedDouble(.125) + .addRepeatedDouble(-.125) + .addRepeatedDouble(123e15) + .addRepeatedDouble(123e15) + .addRepeatedDouble(-1.23e-17) + .addRepeatedDouble(.23e17) + .addRepeatedDouble(-23e15) + .addRepeatedDouble(123.5e20) + .addRepeatedDouble(123.5e-20) + .addRepeatedDouble(123.456789) + .addRepeatedDouble(Double.POSITIVE_INFINITY) + .addRepeatedDouble(Double.NEGATIVE_INFINITY) + .addRepeatedDouble(Double.NaN) + + // Strings and bytes that needing escaping. + .addRepeatedString("\0\001\007\b\f\n\r\t\013\\\'\"\u1234") + .addRepeatedBytes(bytes("\0\001\007\b\f\n\r\t\013\\\'\"\u00fe")) + .build(); + + assertEquals(canonicalExoticText, message.toString()); + } + + public void testPrintMessageSet() throws Exception { + TestMessageSet messageSet = + TestMessageSet.newBuilder() + .setExtension( + TestMessageSetExtension1.messageSetExtension, + TestMessageSetExtension1.newBuilder().setI(123).build()) + .setExtension( + TestMessageSetExtension2.messageSetExtension, + TestMessageSetExtension2.newBuilder().setStr("foo").build()) + .build(); + + assertEquals(messageSetText, messageSet.toString()); + } + + // ================================================================= + + public void testParse() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TextFormat.merge(allFieldsSetText, builder); + TestUtil.assertAllFieldsSet(builder.build()); + } + + public void testParseReader() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TextFormat.merge(new StringReader(allFieldsSetText), builder); + TestUtil.assertAllFieldsSet(builder.build()); + } + + public void testParseExtensions() throws Exception { + TestAllExtensions.Builder builder = TestAllExtensions.newBuilder(); + TextFormat.merge(allExtensionsSetText, + TestUtil.getExtensionRegistry(), + builder); + TestUtil.assertAllExtensionsSet(builder.build()); + } + + public void testParseCompatibility() throws Exception { + String original = "repeated_float: inf\n" + + "repeated_float: -inf\n" + + "repeated_float: nan\n" + + "repeated_float: inff\n" + + "repeated_float: -inff\n" + + "repeated_float: nanf\n" + + "repeated_float: 1.0f\n" + + "repeated_float: infinityf\n" + + "repeated_float: -Infinityf\n" + + "repeated_double: infinity\n" + + "repeated_double: -infinity\n" + + "repeated_double: nan\n"; + String canonical = "repeated_float: Infinity\n" + + "repeated_float: -Infinity\n" + + "repeated_float: NaN\n" + + "repeated_float: Infinity\n" + + "repeated_float: -Infinity\n" + + "repeated_float: NaN\n" + + "repeated_float: 1.0\n" + + "repeated_float: Infinity\n" + + "repeated_float: -Infinity\n" + + "repeated_double: Infinity\n" + + "repeated_double: -Infinity\n" + + "repeated_double: NaN\n"; + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TextFormat.merge(original, builder); + assertEquals(canonical, builder.build().toString()); + } + + public void testParseExotic() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TextFormat.merge(exoticText, builder); + + // Too lazy to check things individually. Don't try to debug this + // if testPrintExotic() is failing. + assertEquals(canonicalExoticText, builder.build().toString()); + } + + public void testParseMessageSet() throws Exception { + ExtensionRegistry extensionRegistry = ExtensionRegistry.newInstance(); + extensionRegistry.add(TestMessageSetExtension1.messageSetExtension); + extensionRegistry.add(TestMessageSetExtension2.messageSetExtension); + + TestMessageSet.Builder builder = TestMessageSet.newBuilder(); + TextFormat.merge(messageSetText, extensionRegistry, builder); + TestMessageSet messageSet = builder.build(); + + assertTrue(messageSet.hasExtension( + TestMessageSetExtension1.messageSetExtension)); + assertEquals(123, messageSet.getExtension( + TestMessageSetExtension1.messageSetExtension).getI()); + assertTrue(messageSet.hasExtension( + TestMessageSetExtension2.messageSetExtension)); + assertEquals("foo", messageSet.getExtension( + TestMessageSetExtension2.messageSetExtension).getStr()); + + builder = TestMessageSet.newBuilder(); + TextFormat.merge(messageSetTextWithRepeatedExtension, extensionRegistry, + builder); + messageSet = builder.build(); + assertEquals(456, messageSet.getExtension( + TestMessageSetExtension1.messageSetExtension).getI()); + } + + public void testParseMessageSetWithOverwriteForbidden() throws Exception { + ExtensionRegistry extensionRegistry = ExtensionRegistry.newInstance(); + extensionRegistry.add(TestMessageSetExtension1.messageSetExtension); + extensionRegistry.add(TestMessageSetExtension2.messageSetExtension); + + TestMessageSet.Builder builder = TestMessageSet.newBuilder(); + parserWithOverwriteForbidden.merge( + messageSetText, extensionRegistry, builder); + TestMessageSet messageSet = builder.build(); + assertEquals(123, messageSet.getExtension( + TestMessageSetExtension1.messageSetExtension).getI()); + assertEquals("foo", messageSet.getExtension( + TestMessageSetExtension2.messageSetExtension).getStr()); + + builder = TestMessageSet.newBuilder(); + try { + parserWithOverwriteForbidden.merge( + messageSetTextWithRepeatedExtension, extensionRegistry, builder); + fail("expected parse exception"); + } catch (TextFormat.ParseException e) { + assertEquals("6:1: Non-repeated field " + + "\"protobuf_unittest.TestMessageSetExtension1.message_set_extension\"" + + " cannot be overwritten.", + e.getMessage()); + } + } + + public void testParseNumericEnum() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TextFormat.merge("optional_nested_enum: 2", builder); + assertEquals(TestAllTypes.NestedEnum.BAR, builder.getOptionalNestedEnum()); + } + + public void testParseAngleBrackets() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TextFormat.merge("OptionalGroup: < a: 1 >", builder); + assertTrue(builder.hasOptionalGroup()); + assertEquals(1, builder.getOptionalGroup().getA()); + } + + public void testParseComment() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TextFormat.merge( + "# this is a comment\n" + + "optional_int32: 1 # another comment\n" + + "optional_int64: 2\n" + + "# EOF comment", builder); + assertEquals(1, builder.getOptionalInt32()); + assertEquals(2, builder.getOptionalInt64()); + } + + private void assertParseError(String error, String text) { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + try { + TextFormat.merge(text, TestUtil.getExtensionRegistry(), builder); + fail("Expected parse exception."); + } catch (TextFormat.ParseException e) { + assertEquals(error, e.getMessage()); + } + } + + + private void assertParseErrorWithOverwriteForbidden(String error, + String text) { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + try { + parserWithOverwriteForbidden.merge( + text, TestUtil.getExtensionRegistry(), builder); + fail("Expected parse exception."); + } catch (TextFormat.ParseException e) { + assertEquals(error, e.getMessage()); + } + } + + private TestAllTypes assertParseSuccessWithOverwriteForbidden( + String text) throws TextFormat.ParseException { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + parserWithOverwriteForbidden.merge( + text, TestUtil.getExtensionRegistry(), builder); + return builder.build(); + } + + public void testParseErrors() throws Exception { + assertParseError( + "1:16: Expected \":\".", + "optional_int32 123"); + assertParseError( + "1:23: Expected identifier. Found '?'", + "optional_nested_enum: ?"); + assertParseError( + "1:18: Couldn't parse integer: Number must be positive: -1", + "optional_uint32: -1"); + assertParseError( + "1:17: Couldn't parse integer: Number out of range for 32-bit signed " + + "integer: 82301481290849012385230157", + "optional_int32: 82301481290849012385230157"); + assertParseError( + "1:16: Expected \"true\" or \"false\".", + "optional_bool: maybe"); + assertParseError( + "1:16: Expected \"true\" or \"false\".", + "optional_bool: 2"); + assertParseError( + "1:18: Expected string.", + "optional_string: 123"); + assertParseError( + "1:18: String missing ending quote.", + "optional_string: \"ueoauaoe"); + assertParseError( + "1:18: String missing ending quote.", + "optional_string: \"ueoauaoe\n" + + "optional_int32: 123"); + assertParseError( + "1:18: Invalid escape sequence: '\\z'", + "optional_string: \"\\z\""); + assertParseError( + "1:18: String missing ending quote.", + "optional_string: \"ueoauaoe\n" + + "optional_int32: 123"); + assertParseError( + "1:2: Extension \"nosuchext\" not found in the ExtensionRegistry.", + "[nosuchext]: 123"); + assertParseError( + "1:20: Extension \"protobuf_unittest.optional_int32_extension\" does " + + "not extend message type \"protobuf_unittest.TestAllTypes\".", + "[protobuf_unittest.optional_int32_extension]: 123"); + assertParseError( + "1:1: Message type \"protobuf_unittest.TestAllTypes\" has no field " + + "named \"nosuchfield\".", + "nosuchfield: 123"); + assertParseError( + "1:21: Expected \">\".", + "OptionalGroup < a: 1"); + assertParseError( + "1:23: Enum type \"protobuf_unittest.TestAllTypes.NestedEnum\" has no " + + "value named \"NO_SUCH_VALUE\".", + "optional_nested_enum: NO_SUCH_VALUE"); + assertParseError( + "1:23: Enum type \"protobuf_unittest.TestAllTypes.NestedEnum\" has no " + + "value with number 123.", + "optional_nested_enum: 123"); + + // Delimiters must match. + assertParseError( + "1:22: Expected identifier. Found '}'", + "OptionalGroup < a: 1 }"); + assertParseError( + "1:22: Expected identifier. Found '>'", + "OptionalGroup { a: 1 >"); + } + + // ================================================================= + + public void testEscape() throws Exception { + // Escape sequences. + 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\\\'\"", + TextFormat.unescapeText("\\000\\001\\a\\b\\f\\n\\r\\t\\v\\\\\\'\\\"")); + assertEquals(kEscapeTestStringEscaped, + TextFormat.escapeText(kEscapeTestString)); + assertEquals(kEscapeTestString, + TextFormat.unescapeText(kEscapeTestStringEscaped)); + + // Unicode handling. + assertEquals("\\341\\210\\264", TextFormat.escapeText("\u1234")); + assertEquals("\\341\\210\\264", + TextFormat.escapeBytes(bytes(0xe1, 0x88, 0xb4))); + assertEquals("\u1234", TextFormat.unescapeText("\\341\\210\\264")); + assertEquals(bytes(0xe1, 0x88, 0xb4), + TextFormat.unescapeBytes("\\341\\210\\264")); + assertEquals("\u1234", TextFormat.unescapeText("\\xe1\\x88\\xb4")); + assertEquals(bytes(0xe1, 0x88, 0xb4), + TextFormat.unescapeBytes("\\xe1\\x88\\xb4")); + + // Handling of strings with unescaped Unicode characters > 255. + final String zh = "\u9999\u6e2f\u4e0a\u6d77\ud84f\udf80\u8c50\u9280\u884c"; + ByteString zhByteString = ByteString.copyFromUtf8(zh); + assertEquals(zhByteString, TextFormat.unescapeBytes(zh)); + + // Errors. + try { + TextFormat.unescapeText("\\x"); + fail("Should have thrown an exception."); + } catch (TextFormat.InvalidEscapeSequenceException e) { + // success + } + + try { + TextFormat.unescapeText("\\z"); + fail("Should have thrown an exception."); + } catch (TextFormat.InvalidEscapeSequenceException e) { + // success + } + + try { + TextFormat.unescapeText("\\"); + fail("Should have thrown an exception."); + } catch (TextFormat.InvalidEscapeSequenceException e) { + // success + } + } + + public void testParseInteger() throws Exception { + assertEquals( 0, TextFormat.parseInt32( "0")); + assertEquals( 1, TextFormat.parseInt32( "1")); + assertEquals( -1, TextFormat.parseInt32( "-1")); + assertEquals( 12345, TextFormat.parseInt32( "12345")); + assertEquals( -12345, TextFormat.parseInt32( "-12345")); + assertEquals( 2147483647, TextFormat.parseInt32( "2147483647")); + assertEquals(-2147483648, TextFormat.parseInt32("-2147483648")); + + assertEquals( 0, TextFormat.parseUInt32( "0")); + assertEquals( 1, TextFormat.parseUInt32( "1")); + assertEquals( 12345, TextFormat.parseUInt32( "12345")); + assertEquals( 2147483647, TextFormat.parseUInt32("2147483647")); + assertEquals((int) 2147483648L, TextFormat.parseUInt32("2147483648")); + assertEquals((int) 4294967295L, TextFormat.parseUInt32("4294967295")); + + assertEquals( 0L, TextFormat.parseInt64( "0")); + assertEquals( 1L, TextFormat.parseInt64( "1")); + assertEquals( -1L, TextFormat.parseInt64( "-1")); + assertEquals( 12345L, TextFormat.parseInt64( "12345")); + assertEquals( -12345L, TextFormat.parseInt64( "-12345")); + assertEquals( 2147483647L, TextFormat.parseInt64( "2147483647")); + assertEquals(-2147483648L, TextFormat.parseInt64("-2147483648")); + assertEquals( 4294967295L, TextFormat.parseInt64( "4294967295")); + assertEquals( 4294967296L, TextFormat.parseInt64( "4294967296")); + assertEquals(9223372036854775807L, + TextFormat.parseInt64("9223372036854775807")); + assertEquals(-9223372036854775808L, + TextFormat.parseInt64("-9223372036854775808")); + + assertEquals( 0L, TextFormat.parseUInt64( "0")); + assertEquals( 1L, TextFormat.parseUInt64( "1")); + assertEquals( 12345L, TextFormat.parseUInt64( "12345")); + assertEquals( 2147483647L, TextFormat.parseUInt64( "2147483647")); + assertEquals( 4294967295L, TextFormat.parseUInt64( "4294967295")); + assertEquals( 4294967296L, TextFormat.parseUInt64( "4294967296")); + assertEquals(9223372036854775807L, + TextFormat.parseUInt64("9223372036854775807")); + assertEquals(-9223372036854775808L, + TextFormat.parseUInt64("9223372036854775808")); + assertEquals(-1L, TextFormat.parseUInt64("18446744073709551615")); + + // Hex + assertEquals(0x1234abcd, TextFormat.parseInt32("0x1234abcd")); + assertEquals(-0x1234abcd, TextFormat.parseInt32("-0x1234abcd")); + assertEquals(-1, TextFormat.parseUInt64("0xffffffffffffffff")); + assertEquals(0x7fffffffffffffffL, + TextFormat.parseInt64("0x7fffffffffffffff")); + + // Octal + assertEquals(01234567, TextFormat.parseInt32("01234567")); + + // Out-of-range + try { + TextFormat.parseInt32("2147483648"); + fail("Should have thrown an exception."); + } catch (NumberFormatException e) { + // success + } + + try { + TextFormat.parseInt32("-2147483649"); + fail("Should have thrown an exception."); + } catch (NumberFormatException e) { + // success + } + + try { + TextFormat.parseUInt32("4294967296"); + fail("Should have thrown an exception."); + } catch (NumberFormatException e) { + // success + } + + try { + TextFormat.parseUInt32("-1"); + fail("Should have thrown an exception."); + } catch (NumberFormatException e) { + // success + } + + try { + TextFormat.parseInt64("9223372036854775808"); + fail("Should have thrown an exception."); + } catch (NumberFormatException e) { + // success + } + + try { + TextFormat.parseInt64("-9223372036854775809"); + fail("Should have thrown an exception."); + } catch (NumberFormatException e) { + // success + } + + try { + TextFormat.parseUInt64("18446744073709551616"); + fail("Should have thrown an exception."); + } catch (NumberFormatException e) { + // success + } + + try { + TextFormat.parseUInt64("-1"); + fail("Should have thrown an exception."); + } catch (NumberFormatException e) { + // success + } + + // Not a number. + try { + TextFormat.parseInt32("abcd"); + fail("Should have thrown an exception."); + } catch (NumberFormatException e) { + // success + } + } + + public void testParseString() throws Exception { + final String zh = "\u9999\u6e2f\u4e0a\u6d77\ud84f\udf80\u8c50\u9280\u884c"; + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TextFormat.merge("optional_string: \"" + zh + "\"", builder); + assertEquals(zh, builder.getOptionalString()); + } + + public void testParseLongString() throws Exception { + String longText = + "123456789012345678901234567890123456789012345678901234567890" + + "123456789012345678901234567890123456789012345678901234567890" + + "123456789012345678901234567890123456789012345678901234567890" + + "123456789012345678901234567890123456789012345678901234567890" + + "123456789012345678901234567890123456789012345678901234567890" + + "123456789012345678901234567890123456789012345678901234567890" + + "123456789012345678901234567890123456789012345678901234567890" + + "123456789012345678901234567890123456789012345678901234567890" + + "123456789012345678901234567890123456789012345678901234567890" + + "123456789012345678901234567890123456789012345678901234567890" + + "123456789012345678901234567890123456789012345678901234567890" + + "123456789012345678901234567890123456789012345678901234567890" + + "123456789012345678901234567890123456789012345678901234567890" + + "123456789012345678901234567890123456789012345678901234567890" + + "123456789012345678901234567890123456789012345678901234567890" + + "123456789012345678901234567890123456789012345678901234567890" + + "123456789012345678901234567890123456789012345678901234567890" + + "123456789012345678901234567890123456789012345678901234567890" + + "123456789012345678901234567890123456789012345678901234567890" + + "123456789012345678901234567890123456789012345678901234567890"; + + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TextFormat.merge("optional_string: \"" + longText + "\"", builder); + assertEquals(longText, builder.getOptionalString()); + } + + public void testParseBoolean() throws Exception { + String goodText = + "repeated_bool: t repeated_bool : 0\n" + + "repeated_bool :f repeated_bool:1"; + String goodTextCanonical = + "repeated_bool: true\n" + + "repeated_bool: false\n" + + "repeated_bool: false\n" + + "repeated_bool: true\n"; + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TextFormat.merge(goodText, builder); + assertEquals(goodTextCanonical, builder.build().toString()); + + try { + TestAllTypes.Builder badBuilder = TestAllTypes.newBuilder(); + TextFormat.merge("optional_bool:2", badBuilder); + fail("Should have thrown an exception."); + } catch (TextFormat.ParseException e) { + // success + } + try { + TestAllTypes.Builder badBuilder = TestAllTypes.newBuilder(); + TextFormat.merge("optional_bool: foo", badBuilder); + fail("Should have thrown an exception."); + } catch (TextFormat.ParseException e) { + // success + } + } + + public void testParseAdjacentStringLiterals() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TextFormat.merge("optional_string: \"foo\" 'corge' \"grault\"", builder); + assertEquals("foocorgegrault", builder.getOptionalString()); + } + + public void testPrintFieldValue() throws Exception { + assertPrintFieldValue("\"Hello\"", "Hello", "repeated_string"); + assertPrintFieldValue("123.0", 123f, "repeated_float"); + assertPrintFieldValue("123.0", 123d, "repeated_double"); + assertPrintFieldValue("123", 123, "repeated_int32"); + assertPrintFieldValue("123", 123L, "repeated_int64"); + assertPrintFieldValue("true", true, "repeated_bool"); + assertPrintFieldValue("4294967295", 0xFFFFFFFF, "repeated_uint32"); + assertPrintFieldValue("18446744073709551615", 0xFFFFFFFFFFFFFFFFL, + "repeated_uint64"); + assertPrintFieldValue("\"\\001\\002\\003\"", + ByteString.copyFrom(new byte[] {1, 2, 3}), "repeated_bytes"); + } + + private void assertPrintFieldValue(String expect, Object value, + String fieldName) throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + StringBuilder sb = new StringBuilder(); + TextFormat.printFieldValue( + TestAllTypes.getDescriptor().findFieldByName(fieldName), + value, sb); + assertEquals(expect, sb.toString()); + } + + public void testShortDebugString() { + assertEquals("optional_nested_message { bb: 42 } repeated_int32: 1" + + " repeated_uint32: 2", + TextFormat.shortDebugString(TestAllTypes.newBuilder() + .addRepeatedInt32(1) + .addRepeatedUint32(2) + .setOptionalNestedMessage( + NestedMessage.newBuilder().setBb(42).build()) + .build())); + } + + public void testShortDebugString_field() { + final FieldDescriptor dataField = + OneString.getDescriptor().findFieldByName("data"); + assertEquals( + "data: \"test data\"", + TextFormat.shortDebugString(dataField, "test data")); + + final FieldDescriptor optionalField = + TestAllTypes.getDescriptor().findFieldByName("optional_nested_message"); + final Object value = NestedMessage.newBuilder().setBb(42).build(); + + assertEquals( + "optional_nested_message { bb: 42 }", + TextFormat.shortDebugString(optionalField, value)); + } + + public void testShortDebugString_unknown() { + assertEquals("5: 1 5: 0x00000002 5: 0x0000000000000003 5: \"4\" 5 { 10: 5 }" + + " 8: 1 8: 2 8: 3 15: 12379813812177893520 15: 0xabcd1234 15:" + + " 0xabcdef1234567890", + TextFormat.shortDebugString(makeUnknownFieldSet())); + } + + public void testPrintToUnicodeString() throws Exception { + assertEquals( + "optional_string: \"abc\u3042efg\"\n" + + "optional_bytes: \"\\343\\201\\202\"\n" + + "repeated_string: \"\u3093XYZ\"\n", + TextFormat.printToUnicodeString(TestAllTypes.newBuilder() + .setOptionalString("abc\u3042efg") + .setOptionalBytes(bytes(0xe3, 0x81, 0x82)) + .addRepeatedString("\u3093XYZ") + .build())); + + // Double quotes and backslashes should be escaped + assertEquals( + "optional_string: \"a\\\\bc\\\"ef\\\"g\"\n", + TextFormat.printToUnicodeString(TestAllTypes.newBuilder() + .setOptionalString("a\\bc\"ef\"g") + .build())); + + // Test escaping roundtrip + TestAllTypes message = TestAllTypes.newBuilder() + .setOptionalString("a\\bc\\\"ef\"g") + .build(); + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TextFormat.merge(TextFormat.printToUnicodeString(message), builder); + assertEquals(message.getOptionalString(), builder.getOptionalString()); + } + + public void testPrintToUnicodeStringWithNewlines() throws Exception { + // No newlines at start and end + assertEquals("optional_string: \"test newlines\\n\\nin\\nstring\"\n", + TextFormat.printToUnicodeString(TestAllTypes.newBuilder() + .setOptionalString("test newlines\n\nin\nstring") + .build())); + + // Newlines at start and end + assertEquals("optional_string: \"\\ntest\\nnewlines\\n\\nin\\nstring\\n\"\n", + TextFormat.printToUnicodeString(TestAllTypes.newBuilder() + .setOptionalString("\ntest\nnewlines\n\nin\nstring\n") + .build())); + + // Strings with 0, 1 and 2 newlines. + assertEquals("optional_string: \"\"\n", + TextFormat.printToUnicodeString(TestAllTypes.newBuilder() + .setOptionalString("") + .build())); + assertEquals("optional_string: \"\\n\"\n", + TextFormat.printToUnicodeString(TestAllTypes.newBuilder() + .setOptionalString("\n") + .build())); + assertEquals("optional_string: \"\\n\\n\"\n", + TextFormat.printToUnicodeString(TestAllTypes.newBuilder() + .setOptionalString("\n\n") + .build())); + + // Test escaping roundtrip + TestAllTypes message = TestAllTypes.newBuilder() + .setOptionalString("\ntest\nnewlines\n\nin\nstring\n") + .build(); + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TextFormat.merge(TextFormat.printToUnicodeString(message), builder); + assertEquals(message.getOptionalString(), builder.getOptionalString()); + } + + public void testPrintToUnicodeString_unknown() { + assertEquals( + "1: \"\\343\\201\\202\"\n", + TextFormat.printToUnicodeString(UnknownFieldSet.newBuilder() + .addField(1, + UnknownFieldSet.Field.newBuilder() + .addLengthDelimited(bytes(0xe3, 0x81, 0x82)).build()) + .build())); + } + + + public void testParseNonRepeatedFields() throws Exception { + assertParseSuccessWithOverwriteForbidden( + "repeated_int32: 1\n" + + "repeated_int32: 2\n"); + assertParseSuccessWithOverwriteForbidden( + "RepeatedGroup { a: 1 }\n" + + "RepeatedGroup { a: 2 }\n"); + assertParseSuccessWithOverwriteForbidden( + "repeated_nested_message { bb: 1 }\n" + + "repeated_nested_message { bb: 2 }\n"); + assertParseErrorWithOverwriteForbidden( + "3:17: Non-repeated field " + + "\"protobuf_unittest.TestAllTypes.optional_int32\" " + + "cannot be overwritten.", + "optional_int32: 1\n" + + "optional_bool: true\n" + + "optional_int32: 1\n"); + assertParseErrorWithOverwriteForbidden( + "2:17: Non-repeated field " + + "\"protobuf_unittest.TestAllTypes.optionalgroup\" " + + "cannot be overwritten.", + "OptionalGroup { a: 1 }\n" + + "OptionalGroup { }\n"); + assertParseErrorWithOverwriteForbidden( + "2:33: Non-repeated field " + + "\"protobuf_unittest.TestAllTypes.optional_nested_message\" " + + "cannot be overwritten.", + "optional_nested_message { }\n" + + "optional_nested_message { bb: 3 }\n"); + assertParseErrorWithOverwriteForbidden( + "2:16: Non-repeated field " + + "\"protobuf_unittest.TestAllTypes.default_int32\" " + + "cannot be overwritten.", + "default_int32: 41\n" + // the default value + "default_int32: 41\n"); + assertParseErrorWithOverwriteForbidden( + "2:17: Non-repeated field " + + "\"protobuf_unittest.TestAllTypes.default_string\" " + + "cannot be overwritten.", + "default_string: \"zxcv\"\n" + + "default_string: \"asdf\"\n"); + } + + public void testParseShortRepeatedFormOfRepeatedFields() throws Exception { + assertParseSuccessWithOverwriteForbidden("repeated_foreign_enum: [FOREIGN_FOO, FOREIGN_BAR]"); + assertParseSuccessWithOverwriteForbidden("repeated_int32: [ 1, 2 ]\n"); + assertParseSuccessWithOverwriteForbidden("RepeatedGroup [{ a: 1 },{ a: 2 }]\n"); + assertParseSuccessWithOverwriteForbidden("repeated_nested_message [{ bb: 1 }, { bb: 2 }]\n"); + } + + public void testParseShortRepeatedFormOfNonRepeatedFields() throws Exception { + assertParseErrorWithOverwriteForbidden( + "1:17: Couldn't parse integer: For input string: \"[\"", + "optional_int32: [1]\n"); + } + + // ======================================================================= + // test oneof + + public void testOneofTextFormat() throws Exception { + TestOneof2.Builder builder = TestOneof2.newBuilder(); + TestUtil.setOneof(builder); + TestOneof2 message = builder.build(); + TestOneof2.Builder dest = TestOneof2.newBuilder(); + TextFormat.merge(TextFormat.printToUnicodeString(message), dest); + TestUtil.assertOneofSet(dest.build()); + } + + public void testOneofOverwriteForbidden() throws Exception { + String input = "foo_string: \"stringvalue\" foo_int: 123"; + TestOneof2.Builder builder = TestOneof2.newBuilder(); + try { + parserWithOverwriteForbidden.merge( + input, TestUtil.getExtensionRegistry(), builder); + fail("Expected parse exception."); + } catch (TextFormat.ParseException e) { + assertEquals("1:36: Field \"protobuf_unittest.TestOneof2.foo_int\"" + + " is specified along with field \"protobuf_unittest.TestOneof2.foo_string\"," + + " another member of oneof \"foo\".", e.getMessage()); + } + } + + public void testOneofOverwriteAllowed() throws Exception { + String input = "foo_string: \"stringvalue\" foo_int: 123"; + TestOneof2.Builder builder = TestOneof2.newBuilder(); + defaultParser.merge(input, TestUtil.getExtensionRegistry(), builder); + // Only the last value sticks. + TestOneof2 oneof = builder.build(); + assertFalse(oneof.hasFooString()); + assertTrue(oneof.hasFooInt()); + } +} diff --git a/java/core/src/test/java/com/google/protobuf/UnknownEnumValueTest.java b/java/core/src/test/java/com/google/protobuf/UnknownEnumValueTest.java new file mode 100644 index 00000000..8f45976f --- /dev/null +++ b/java/core/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/core/src/test/java/com/google/protobuf/UnknownFieldSetLiteTest.java b/java/core/src/test/java/com/google/protobuf/UnknownFieldSetLiteTest.java new file mode 100644 index 00000000..dc987379 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/UnknownFieldSetLiteTest.java @@ -0,0 +1,368 @@ +// 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.UnittestLite.TestAllExtensionsLite; +import com.google.protobuf.UnittestLite.TestAllTypesLite; +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; + +/** + * 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 testBuilderReuse() throws IOException { + UnknownFieldSetLite.Builder builder = UnknownFieldSetLite.newBuilder(); + builder.mergeVarintField(10, 2); + builder.build(); + + try { + builder.build(); + fail(); + } catch (UnsupportedOperationException e) { + // Expected. + } + + try { + builder.mergeFieldFrom(0, CodedInputStream.newInstance(new byte[0])); + fail(); + } catch (UnsupportedOperationException e) { + // Expected. + } + + try { + builder.mergeVarintField(5, 1); + fail(); + } catch (UnsupportedOperationException e) { + // Expected. + } + } + + public void testBuilderReuse_empty() { + UnknownFieldSetLite.Builder builder = UnknownFieldSetLite.newBuilder(); + builder.build(); + builder.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 instance = UnknownFieldSetLite.newInstance(); + instance.mergeFieldFrom(input.readTag(), input); + + assertEquals(foo.toByteString(), toByteString(instance)); + } + + public void testSerializedSize() throws IOException { + Foo foo = Foo.newBuilder() + .setValue(2) + .build(); + + CodedInputStream input = CodedInputStream.newInstance(foo.toByteArray()); + + UnknownFieldSetLite instance = UnknownFieldSetLite.newInstance(); + instance.mergeFieldFrom(input.readTag(), input); + + assertEquals(foo.toByteString().size(), instance.getSerializedSize()); + } + + public void testMergeVarintField() throws IOException { + UnknownFieldSetLite unknownFields = UnknownFieldSetLite.newInstance(); + unknownFields.mergeVarintField(10, 2); + + 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()); + assertTrue(input.isAtEnd()); + } + + public void testMergeVarintField_negative() throws IOException { + UnknownFieldSetLite builder = UnknownFieldSetLite.newInstance(); + builder.mergeVarintField(10, -6); + + CodedInputStream input = + CodedInputStream.newInstance(toByteString(builder).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 unknownFields1 = UnknownFieldSetLite.newInstance(); + unknownFields1.mergeVarintField(10, 2); + + UnknownFieldSetLite unknownFields2 = UnknownFieldSetLite.newInstance(); + unknownFields2.mergeVarintField(10, 2); + + assertEquals(unknownFields1, unknownFields2); + assertEquals(unknownFields1.hashCode(), unknownFields2.hashCode()); + assertFalse(unknownFields1.equals(UnknownFieldSetLite.getDefaultInstance())); + assertFalse(unknownFields1.hashCode() == UnknownFieldSetLite.getDefaultInstance().hashCode()); + } + + public void testMutableCopyOf() throws IOException { + UnknownFieldSetLite unknownFields = UnknownFieldSetLite.newInstance(); + unknownFields.mergeVarintField(10, 2); + unknownFields = UnknownFieldSetLite.mutableCopyOf(unknownFields, unknownFields); + unknownFields.checkMutable(); + + 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 testMutableCopyOf_empty() { + UnknownFieldSetLite unknownFields = UnknownFieldSetLite.mutableCopyOf( + UnknownFieldSetLite.getDefaultInstance(), UnknownFieldSetLite.getDefaultInstance()); + unknownFields.checkMutable(); + + assertEquals(0, unknownFields.getSerializedSize()); + assertEquals(ByteString.EMPTY, toByteString(unknownFields)); + } + + 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() throws Exception { + try { + Foo.parseFrom("this is a malformed protocol buffer".getBytes(Internal.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. + } + } + + public void testMakeImmutable() throws Exception { + UnknownFieldSetLite unknownFields = UnknownFieldSetLite.newInstance(); + unknownFields.makeImmutable(); + + try { + unknownFields.mergeVarintField(1, 1); + fail(); + } catch (UnsupportedOperationException expected) {} + + try { + unknownFields.mergeLengthDelimitedField(2, ByteString.copyFromUtf8("hello")); + fail(); + } catch (UnsupportedOperationException expected) {} + + try { + unknownFields.mergeFieldFrom(1, CodedInputStream.newInstance(new byte[0])); + fail(); + } catch (UnsupportedOperationException expected) {} + } + + public void testEndToEnd() throws Exception { + TestAllTypesLite testAllTypes = TestAllTypesLite.getDefaultInstance(); + try { + testAllTypes.unknownFields.checkMutable(); + fail(); + } catch (UnsupportedOperationException expected) {} + + testAllTypes = TestAllTypesLite.parseFrom(new byte[0]); + try { + testAllTypes.unknownFields.checkMutable(); + fail(); + } catch (UnsupportedOperationException expected) {} + + testAllTypes = TestAllTypesLite.newBuilder().build(); + try { + testAllTypes.unknownFields.checkMutable(); + fail(); + } catch (UnsupportedOperationException expected) {} + + testAllTypes = TestAllTypesLite.newBuilder() + .setDefaultBool(true) + .build(); + try { + testAllTypes.unknownFields.checkMutable(); + fail(); + } catch (UnsupportedOperationException expected) {} + + TestAllExtensionsLite testAllExtensions = TestAllExtensionsLite.newBuilder() + .mergeFrom(TestAllExtensionsLite.newBuilder() + .setExtension(UnittestLite.optionalInt32ExtensionLite, 2) + .build().toByteArray()) + .build(); + try { + testAllExtensions.unknownFields.checkMutable(); + fail(); + } catch (UnsupportedOperationException 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/core/src/test/java/com/google/protobuf/UnknownFieldSetTest.java b/java/core/src/test/java/com/google/protobuf/UnknownFieldSetTest.java new file mode 100644 index 00000000..8c9dcafe --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/UnknownFieldSetTest.java @@ -0,0 +1,653 @@ +// 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.UnittestProto; +import protobuf_unittest.UnittestProto.ForeignEnum; +import protobuf_unittest.UnittestProto.TestAllExtensions; +import protobuf_unittest.UnittestProto.TestAllTypes; +import protobuf_unittest.UnittestProto.TestEmptyMessage; +import protobuf_unittest.UnittestProto.TestEmptyMessageWithExtensions; +import protobuf_unittest.UnittestProto.TestPackedExtensions; +import protobuf_unittest.UnittestProto.TestPackedTypes; + +import junit.framework.TestCase; + +import java.util.Arrays; +import java.util.Map; + +/** + * Tests related to unknown field handling. + * + * @author kenton@google.com (Kenton Varda) + */ +public class UnknownFieldSetTest extends TestCase { + public void setUp() throws Exception { + descriptor = TestAllTypes.getDescriptor(); + allFields = TestUtil.getAllSet(); + allFieldsData = allFields.toByteString(); + emptyMessage = TestEmptyMessage.parseFrom(allFieldsData); + unknownFields = emptyMessage.getUnknownFields(); + } + + UnknownFieldSet.Field getField(String name) { + Descriptors.FieldDescriptor field = descriptor.findFieldByName(name); + assertNotNull(field); + return unknownFields.getField(field.getNumber()); + } + + // Constructs a protocol buffer which contains fields with all the same + // numbers as allFieldsData except that each field is some other wire + // type. + ByteString getBizarroData() throws Exception { + UnknownFieldSet.Builder bizarroFields = UnknownFieldSet.newBuilder(); + + UnknownFieldSet.Field varintField = + UnknownFieldSet.Field.newBuilder().addVarint(1).build(); + UnknownFieldSet.Field fixed32Field = + UnknownFieldSet.Field.newBuilder().addFixed32(1).build(); + + for (Map.Entry<Integer, UnknownFieldSet.Field> entry : + unknownFields.asMap().entrySet()) { + if (entry.getValue().getVarintList().isEmpty()) { + // Original field is not a varint, so use a varint. + bizarroFields.addField(entry.getKey(), varintField); + } else { + // Original field *is* a varint, so use something else. + bizarroFields.addField(entry.getKey(), fixed32Field); + } + } + + return bizarroFields.build().toByteString(); + } + + Descriptors.Descriptor descriptor; + TestAllTypes allFields; + ByteString allFieldsData; + + // An empty message that has been parsed from allFieldsData. So, it has + // unknown fields of every type. + TestEmptyMessage emptyMessage; + UnknownFieldSet unknownFields; + + // ================================================================= + + public void testVarint() throws Exception { + UnknownFieldSet.Field field = getField("optional_int32"); + assertEquals(1, field.getVarintList().size()); + assertEquals(allFields.getOptionalInt32(), + (long) field.getVarintList().get(0)); + } + + public void testFixed32() throws Exception { + UnknownFieldSet.Field field = getField("optional_fixed32"); + assertEquals(1, field.getFixed32List().size()); + assertEquals(allFields.getOptionalFixed32(), + (int) field.getFixed32List().get(0)); + } + + public void testFixed64() throws Exception { + UnknownFieldSet.Field field = getField("optional_fixed64"); + assertEquals(1, field.getFixed64List().size()); + assertEquals(allFields.getOptionalFixed64(), + (long) field.getFixed64List().get(0)); + } + + public void testLengthDelimited() throws Exception { + UnknownFieldSet.Field field = getField("optional_bytes"); + assertEquals(1, field.getLengthDelimitedList().size()); + assertEquals(allFields.getOptionalBytes(), + field.getLengthDelimitedList().get(0)); + } + + public void testGroup() throws Exception { + Descriptors.FieldDescriptor nestedFieldDescriptor = + TestAllTypes.OptionalGroup.getDescriptor().findFieldByName("a"); + assertNotNull(nestedFieldDescriptor); + + UnknownFieldSet.Field field = getField("optionalgroup"); + assertEquals(1, field.getGroupList().size()); + + UnknownFieldSet group = field.getGroupList().get(0); + assertEquals(1, group.asMap().size()); + assertTrue(group.hasField(nestedFieldDescriptor.getNumber())); + + UnknownFieldSet.Field nestedField = + group.getField(nestedFieldDescriptor.getNumber()); + assertEquals(1, nestedField.getVarintList().size()); + assertEquals(allFields.getOptionalGroup().getA(), + (long) nestedField.getVarintList().get(0)); + } + + public void testSerialize() throws Exception { + // Check that serializing the UnknownFieldSet produces the original data + // again. + ByteString data = emptyMessage.toByteString(); + assertEquals(allFieldsData, data); + } + + public void testCopyFrom() throws Exception { + TestEmptyMessage message = + TestEmptyMessage.newBuilder().mergeFrom(emptyMessage).build(); + + assertEquals(emptyMessage.toString(), message.toString()); + } + + public void testMergeFrom() throws Exception { + TestEmptyMessage source = + TestEmptyMessage.newBuilder() + .setUnknownFields( + UnknownFieldSet.newBuilder() + .addField(2, + UnknownFieldSet.Field.newBuilder() + .addVarint(2).build()) + .addField(3, + UnknownFieldSet.Field.newBuilder() + .addVarint(4).build()) + .build()) + .build(); + TestEmptyMessage destination = + TestEmptyMessage.newBuilder() + .setUnknownFields( + UnknownFieldSet.newBuilder() + .addField(1, + UnknownFieldSet.Field.newBuilder() + .addVarint(1).build()) + .addField(3, + UnknownFieldSet.Field.newBuilder() + .addVarint(3).build()) + .build()) + .mergeFrom(source) + .build(); + + assertEquals( + "1: 1\n" + + "2: 2\n" + + "3: 3\n" + + "3: 4\n", + destination.toString()); + } + + public void testClear() throws Exception { + UnknownFieldSet fields = + UnknownFieldSet.newBuilder().mergeFrom(unknownFields).clear().build(); + assertTrue(fields.asMap().isEmpty()); + } + + public void testClearMessage() throws Exception { + TestEmptyMessage message = + TestEmptyMessage.newBuilder().mergeFrom(emptyMessage).clear().build(); + assertEquals(0, message.getSerializedSize()); + } + + public void testClearField() throws Exception { + int fieldNumber = unknownFields.asMap().keySet().iterator().next(); + UnknownFieldSet fields = + UnknownFieldSet.newBuilder().mergeFrom(unknownFields).clearField(fieldNumber).build(); + assertFalse(fields.hasField(fieldNumber)); + } + + public void testParseKnownAndUnknown() throws Exception { + // Test mixing known and unknown fields when parsing. + + UnknownFieldSet fields = + UnknownFieldSet.newBuilder(unknownFields) + .addField(123456, + UnknownFieldSet.Field.newBuilder().addVarint(654321).build()) + .build(); + + ByteString data = fields.toByteString(); + TestAllTypes destination = TestAllTypes.parseFrom(data); + + TestUtil.assertAllFieldsSet(destination); + assertEquals(1, destination.getUnknownFields().asMap().size()); + + UnknownFieldSet.Field field = + destination.getUnknownFields().getField(123456); + assertEquals(1, field.getVarintList().size()); + assertEquals(654321, (long) field.getVarintList().get(0)); + } + + public void testWrongTypeTreatedAsUnknown() throws Exception { + // Test that fields of the wrong wire type are treated like unknown fields + // when parsing. + + ByteString bizarroData = getBizarroData(); + TestAllTypes allTypesMessage = TestAllTypes.parseFrom(bizarroData); + TestEmptyMessage emptyMessage = TestEmptyMessage.parseFrom(bizarroData); + + // All fields should have been interpreted as unknown, so the debug strings + // should be the same. + assertEquals(emptyMessage.toString(), allTypesMessage.toString()); + } + + public void testUnknownExtensions() throws Exception { + // Make sure fields are properly parsed to the UnknownFieldSet even when + // they are declared as extension numbers. + + TestEmptyMessageWithExtensions message = + TestEmptyMessageWithExtensions.parseFrom(allFieldsData); + + assertEquals(unknownFields.asMap().size(), + message.getUnknownFields().asMap().size()); + assertEquals(allFieldsData, message.toByteString()); + } + + public void testWrongExtensionTypeTreatedAsUnknown() throws Exception { + // Test that fields of the wrong wire type are treated like unknown fields + // when parsing extensions. + + ByteString bizarroData = getBizarroData(); + TestAllExtensions allExtensionsMessage = + TestAllExtensions.parseFrom(bizarroData); + TestEmptyMessage emptyMessage = TestEmptyMessage.parseFrom(bizarroData); + + // All fields should have been interpreted as unknown, so the debug strings + // should be the same. + assertEquals(emptyMessage.toString(), + allExtensionsMessage.toString()); + } + + public void testParseUnknownEnumValue() throws Exception { + Descriptors.FieldDescriptor singularField = + TestAllTypes.getDescriptor().findFieldByName("optional_nested_enum"); + Descriptors.FieldDescriptor repeatedField = + TestAllTypes.getDescriptor().findFieldByName("repeated_nested_enum"); + assertNotNull(singularField); + assertNotNull(repeatedField); + + ByteString data = + UnknownFieldSet.newBuilder() + .addField(singularField.getNumber(), + UnknownFieldSet.Field.newBuilder() + .addVarint(TestAllTypes.NestedEnum.BAR.getNumber()) + .addVarint(5) // not valid + .build()) + .addField(repeatedField.getNumber(), + UnknownFieldSet.Field.newBuilder() + .addVarint(TestAllTypes.NestedEnum.FOO.getNumber()) + .addVarint(4) // not valid + .addVarint(TestAllTypes.NestedEnum.BAZ.getNumber()) + .addVarint(6) // not valid + .build()) + .build() + .toByteString(); + + { + TestAllTypes message = TestAllTypes.parseFrom(data); + assertEquals(TestAllTypes.NestedEnum.BAR, + message.getOptionalNestedEnum()); + assertEquals( + Arrays.asList(TestAllTypes.NestedEnum.FOO, TestAllTypes.NestedEnum.BAZ), + message.getRepeatedNestedEnumList()); + assertEquals(Arrays.asList(5L), + message.getUnknownFields() + .getField(singularField.getNumber()) + .getVarintList()); + assertEquals(Arrays.asList(4L, 6L), + message.getUnknownFields() + .getField(repeatedField.getNumber()) + .getVarintList()); + } + + { + TestAllExtensions message = + TestAllExtensions.parseFrom(data, TestUtil.getExtensionRegistry()); + assertEquals(TestAllTypes.NestedEnum.BAR, + message.getExtension(UnittestProto.optionalNestedEnumExtension)); + assertEquals( + Arrays.asList(TestAllTypes.NestedEnum.FOO, TestAllTypes.NestedEnum.BAZ), + message.getExtension(UnittestProto.repeatedNestedEnumExtension)); + assertEquals(Arrays.asList(5L), + message.getUnknownFields() + .getField(singularField.getNumber()) + .getVarintList()); + assertEquals(Arrays.asList(4L, 6L), + message.getUnknownFields() + .getField(repeatedField.getNumber()) + .getVarintList()); + } + } + + public void testLargeVarint() throws Exception { + ByteString data = + UnknownFieldSet.newBuilder() + .addField(1, + UnknownFieldSet.Field.newBuilder() + .addVarint(0x7FFFFFFFFFFFFFFFL) + .build()) + .build() + .toByteString(); + UnknownFieldSet parsed = UnknownFieldSet.parseFrom(data); + UnknownFieldSet.Field field = parsed.getField(1); + assertEquals(1, field.getVarintList().size()); + assertEquals(0x7FFFFFFFFFFFFFFFL, (long)field.getVarintList().get(0)); + } + + public void testEqualsAndHashCode() { + UnknownFieldSet.Field fixed32Field = + UnknownFieldSet.Field.newBuilder() + .addFixed32(1) + .build(); + UnknownFieldSet.Field fixed64Field = + UnknownFieldSet.Field.newBuilder() + .addFixed64(1) + .build(); + UnknownFieldSet.Field varIntField = + UnknownFieldSet.Field.newBuilder() + .addVarint(1) + .build(); + UnknownFieldSet.Field lengthDelimitedField = + UnknownFieldSet.Field.newBuilder() + .addLengthDelimited(ByteString.EMPTY) + .build(); + UnknownFieldSet.Field groupField = + UnknownFieldSet.Field.newBuilder() + .addGroup(unknownFields) + .build(); + + UnknownFieldSet a = + UnknownFieldSet.newBuilder() + .addField(1, fixed32Field) + .build(); + UnknownFieldSet b = + UnknownFieldSet.newBuilder() + .addField(1, fixed64Field) + .build(); + UnknownFieldSet c = + UnknownFieldSet.newBuilder() + .addField(1, varIntField) + .build(); + UnknownFieldSet d = + UnknownFieldSet.newBuilder() + .addField(1, lengthDelimitedField) + .build(); + UnknownFieldSet e = + UnknownFieldSet.newBuilder() + .addField(1, groupField) + .build(); + + checkEqualsIsConsistent(a); + checkEqualsIsConsistent(b); + checkEqualsIsConsistent(c); + checkEqualsIsConsistent(d); + checkEqualsIsConsistent(e); + + checkNotEqual(a, b); + checkNotEqual(a, c); + checkNotEqual(a, d); + checkNotEqual(a, e); + checkNotEqual(b, c); + checkNotEqual(b, d); + checkNotEqual(b, e); + checkNotEqual(c, d); + checkNotEqual(c, e); + checkNotEqual(d, e); + } + + /** + * Asserts that the given field sets are not equal and have different + * hash codes. + * + * @warning It's valid for non-equal objects to have the same hash code, so + * this test is stricter than it needs to be. However, this should happen + * relatively rarely. + */ + private void checkNotEqual(UnknownFieldSet s1, UnknownFieldSet s2) { + String equalsError = String.format("%s should not be equal to %s", s1, s2); + assertFalse(equalsError, s1.equals(s2)); + assertFalse(equalsError, s2.equals(s1)); + + assertFalse( + String.format("%s should have a different hash code from %s", s1, s2), + s1.hashCode() == s2.hashCode()); + } + + /** + * Asserts that the given field sets are equal and have identical hash codes. + */ + private void checkEqualsIsConsistent(UnknownFieldSet set) { + // Object should be equal to itself. + assertEquals(set, set); + + // Object should be equal to a copy of itself. + UnknownFieldSet copy = UnknownFieldSet.newBuilder(set).build(); + assertEquals(set, copy); + assertEquals(copy, set); + assertEquals(set.hashCode(), copy.hashCode()); + } + + // ================================================================= + + public void testSerializeLite() throws Exception { + UnittestLite.TestEmptyMessageLite emptyMessageLite = + UnittestLite.TestEmptyMessageLite.parseFrom(allFieldsData); + assertEquals(allFieldsData.size(), emptyMessageLite.getSerializedSize()); + ByteString data = emptyMessageLite.toByteString(); + TestAllTypes message = TestAllTypes.parseFrom(data); + TestUtil.assertAllFieldsSet(message); + assertEquals(allFieldsData, data); + } + + public void testAllExtensionsLite() throws Exception { + TestAllExtensions allExtensions = TestUtil.getAllExtensionsSet(); + ByteString allExtensionsData = allExtensions.toByteString(); + UnittestLite.TestEmptyMessageLite emptyMessageLite = + UnittestLite.TestEmptyMessageLite.parser().parseFrom(allExtensionsData); + ByteString data = emptyMessageLite.toByteString(); + TestAllExtensions message = + TestAllExtensions.parseFrom(data, TestUtil.getExtensionRegistry()); + TestUtil.assertAllExtensionsSet(message); + assertEquals(allExtensionsData, data); + } + + public void testAllPackedFieldsLite() throws Exception { + TestPackedTypes allPackedFields = TestUtil.getPackedSet(); + ByteString allPackedData = allPackedFields.toByteString(); + UnittestLite.TestEmptyMessageLite emptyMessageLite = + UnittestLite.TestEmptyMessageLite.parseFrom(allPackedData); + ByteString data = emptyMessageLite.toByteString(); + TestPackedTypes message = + TestPackedTypes.parseFrom(data, TestUtil.getExtensionRegistry()); + TestUtil.assertPackedFieldsSet(message); + assertEquals(allPackedData, data); + } + + public void testAllPackedExtensionsLite() throws Exception { + TestPackedExtensions allPackedExtensions = TestUtil.getPackedExtensionsSet(); + ByteString allPackedExtensionsData = allPackedExtensions.toByteString(); + UnittestLite.TestEmptyMessageLite emptyMessageLite = + UnittestLite.TestEmptyMessageLite.parseFrom(allPackedExtensionsData); + ByteString data = emptyMessageLite.toByteString(); + TestPackedExtensions message = + TestPackedExtensions.parseFrom(data, TestUtil.getExtensionRegistry()); + TestUtil.assertPackedExtensionsSet(message); + assertEquals(allPackedExtensionsData, data); + } + + public void testCopyFromLite() throws Exception { + UnittestLite.TestEmptyMessageLite emptyMessageLite = + UnittestLite.TestEmptyMessageLite.parseFrom(allFieldsData); + UnittestLite.TestEmptyMessageLite emptyMessageLite2 = + UnittestLite.TestEmptyMessageLite.newBuilder() + .mergeFrom(emptyMessageLite).build(); + assertEquals(emptyMessageLite.toByteString(), emptyMessageLite2.toByteString()); + } + + public void testMergeFromLite() throws Exception { + TestAllTypes message1 = TestAllTypes.newBuilder() + .setOptionalInt32(1) + .setOptionalString("foo") + .addRepeatedString("bar") + .setOptionalNestedEnum(TestAllTypes.NestedEnum.BAZ) + .build(); + + TestAllTypes message2 = TestAllTypes.newBuilder() + .setOptionalInt64(2) + .setOptionalString("baz") + .addRepeatedString("qux") + .setOptionalForeignEnum(ForeignEnum.FOREIGN_BAZ) + .build(); + + ByteString data1 = message1.toByteString(); + UnittestLite.TestEmptyMessageLite emptyMessageLite1 = + UnittestLite.TestEmptyMessageLite.parseFrom(data1); + ByteString data2 = message2.toByteString(); + UnittestLite.TestEmptyMessageLite emptyMessageLite2 = + UnittestLite.TestEmptyMessageLite.parseFrom(data2); + + message1 = TestAllTypes.newBuilder(message1).mergeFrom(message2).build(); + emptyMessageLite1 = UnittestLite.TestEmptyMessageLite.newBuilder(emptyMessageLite1) + .mergeFrom(emptyMessageLite2).build(); + + data1 = emptyMessageLite1.toByteString(); + message2 = TestAllTypes.parseFrom(data1); + + assertEquals(message1, message2); + } + + public void testWrongTypeTreatedAsUnknownLite() throws Exception { + // Test that fields of the wrong wire type are treated like unknown fields + // when parsing. + + ByteString bizarroData = getBizarroData(); + TestAllTypes allTypesMessage = TestAllTypes.parseFrom(bizarroData); + UnittestLite.TestEmptyMessageLite emptyMessageLite = + UnittestLite.TestEmptyMessageLite.parseFrom(bizarroData); + ByteString data = emptyMessageLite.toByteString(); + TestAllTypes allTypesMessage2 = TestAllTypes.parseFrom(data); + + assertEquals(allTypesMessage.toString(), allTypesMessage2.toString()); + } + + public void testUnknownExtensionsLite() throws Exception { + // Make sure fields are properly parsed to the UnknownFieldSet even when + // they are declared as extension numbers. + + UnittestLite.TestEmptyMessageWithExtensionsLite message = + UnittestLite.TestEmptyMessageWithExtensionsLite.parseFrom(allFieldsData); + + assertEquals(allFieldsData, message.toByteString()); + } + + public void testWrongExtensionTypeTreatedAsUnknownLite() throws Exception { + // Test that fields of the wrong wire type are treated like unknown fields + // when parsing extensions. + + ByteString bizarroData = getBizarroData(); + TestAllExtensions allExtensionsMessage = + TestAllExtensions.parseFrom(bizarroData); + UnittestLite.TestEmptyMessageLite emptyMessageLite = + UnittestLite.TestEmptyMessageLite.parseFrom(bizarroData); + + // All fields should have been interpreted as unknown, so the byte strings + // should be the same. + assertEquals(emptyMessageLite.toByteString(), + allExtensionsMessage.toByteString()); + } + + public void testParseUnknownEnumValueLite() throws Exception { + Descriptors.FieldDescriptor singularField = + TestAllTypes.getDescriptor().findFieldByName("optional_nested_enum"); + Descriptors.FieldDescriptor repeatedField = + TestAllTypes.getDescriptor().findFieldByName("repeated_nested_enum"); + assertNotNull(singularField); + assertNotNull(repeatedField); + + ByteString data = + UnknownFieldSet.newBuilder() + .addField(singularField.getNumber(), + UnknownFieldSet.Field.newBuilder() + .addVarint(TestAllTypes.NestedEnum.BAR.getNumber()) + .addVarint(5) // not valid + .build()) + .addField(repeatedField.getNumber(), + UnknownFieldSet.Field.newBuilder() + .addVarint(TestAllTypes.NestedEnum.FOO.getNumber()) + .addVarint(4) // not valid + .addVarint(TestAllTypes.NestedEnum.BAZ.getNumber()) + .addVarint(6) // not valid + .build()) + .build() + .toByteString(); + + UnittestLite.TestEmptyMessageLite emptyMessageLite = + UnittestLite.TestEmptyMessageLite.parseFrom(data); + data = emptyMessageLite.toByteString(); + + { + TestAllTypes message = TestAllTypes.parseFrom(data); + assertEquals(TestAllTypes.NestedEnum.BAR, + message.getOptionalNestedEnum()); + assertEquals( + Arrays.asList(TestAllTypes.NestedEnum.FOO, TestAllTypes.NestedEnum.BAZ), + message.getRepeatedNestedEnumList()); + assertEquals(Arrays.asList(5L), + message.getUnknownFields() + .getField(singularField.getNumber()) + .getVarintList()); + assertEquals(Arrays.asList(4L, 6L), + message.getUnknownFields() + .getField(repeatedField.getNumber()) + .getVarintList()); + } + + { + TestAllExtensions message = + TestAllExtensions.parseFrom(data, TestUtil.getExtensionRegistry()); + assertEquals(TestAllTypes.NestedEnum.BAR, + message.getExtension(UnittestProto.optionalNestedEnumExtension)); + assertEquals( + Arrays.asList(TestAllTypes.NestedEnum.FOO, TestAllTypes.NestedEnum.BAZ), + message.getExtension(UnittestProto.repeatedNestedEnumExtension)); + assertEquals(Arrays.asList(5L), + message.getUnknownFields() + .getField(singularField.getNumber()) + .getVarintList()); + assertEquals(Arrays.asList(4L, 6L), + message.getUnknownFields() + .getField(repeatedField.getNumber()) + .getVarintList()); + } + } + + public void testClearLite() throws Exception { + UnittestLite.TestEmptyMessageLite emptyMessageLite1 = + UnittestLite.TestEmptyMessageLite.parseFrom(allFieldsData); + UnittestLite.TestEmptyMessageLite emptyMessageLite2 = + UnittestLite.TestEmptyMessageLite.newBuilder() + .mergeFrom(emptyMessageLite1).clear().build(); + assertEquals(0, emptyMessageLite2.getSerializedSize()); + ByteString data = emptyMessageLite2.toByteString(); + assertEquals(0, data.size()); + } + +} diff --git a/java/core/src/test/java/com/google/protobuf/UnmodifiableLazyStringListTest.java b/java/core/src/test/java/com/google/protobuf/UnmodifiableLazyStringListTest.java new file mode 100644 index 00000000..b1c75fc3 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/UnmodifiableLazyStringListTest.java @@ -0,0 +1,227 @@ +// 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 junit.framework.TestCase; + +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +/** + * Tests for {@link UnmodifiableLazyStringList}. + * + * @author jonp@google.com (Jon Perlow) + */ +public class UnmodifiableLazyStringListTest extends TestCase { + + private static String STRING_A = "A"; + private static String STRING_B = "B"; + private static String STRING_C = "C"; + + private static ByteString BYTE_STRING_A = ByteString.copyFromUtf8("A"); + private static ByteString BYTE_STRING_B = ByteString.copyFromUtf8("B"); + private static ByteString BYTE_STRING_C = ByteString.copyFromUtf8("C"); + + public void testReadOnlyMethods() { + LazyStringArrayList rawList = createSampleList(); + UnmodifiableLazyStringList list = new UnmodifiableLazyStringList(rawList); + assertEquals(3, list.size()); + assertSame(STRING_A, list.get(0)); + assertSame(STRING_B, list.get(1)); + assertSame(STRING_C, list.get(2)); + assertEquals(BYTE_STRING_A, list.getByteString(0)); + assertEquals(BYTE_STRING_B, list.getByteString(1)); + assertEquals(BYTE_STRING_C, list.getByteString(2)); + + List<ByteString> byteStringList = list.asByteStringList(); + assertSame(list.getByteString(0), byteStringList.get(0)); + assertSame(list.getByteString(1), byteStringList.get(1)); + assertSame(list.getByteString(2), byteStringList.get(2)); + } + + public void testModifyMethods() { + LazyStringArrayList rawList = createSampleList(); + UnmodifiableLazyStringList list = new UnmodifiableLazyStringList(rawList); + + try { + list.remove(0); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + assertEquals(3, list.size()); + + try { + list.add(STRING_B); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + assertEquals(3, list.size()); + + try { + list.set(1, STRING_B); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + assertEquals(3, list.size()); + + List<ByteString> byteStringList = list.asByteStringList(); + try { + byteStringList.remove(0); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + assertEquals(3, list.size()); + assertEquals(3, byteStringList.size()); + + try { + byteStringList.add(BYTE_STRING_B); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + assertEquals(3, list.size()); + assertEquals(3, byteStringList.size()); + + try { + byteStringList.set(1, BYTE_STRING_B); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + assertEquals(3, list.size()); + assertEquals(3, byteStringList.size()); + } + + public void testIterator() { + LazyStringArrayList rawList = createSampleList(); + UnmodifiableLazyStringList list = new UnmodifiableLazyStringList(rawList); + + Iterator<String> iter = list.iterator(); + int count = 0; + while (iter.hasNext()) { + iter.next(); + count++; + try { + iter.remove(); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + } + assertEquals(3, count); + + List<ByteString> byteStringList = list.asByteStringList(); + Iterator<ByteString> byteIter = byteStringList.iterator(); + count = 0; + while (byteIter.hasNext()) { + byteIter.next(); + count++; + try { + byteIter.remove(); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + } + assertEquals(3, count); + } + + public void testListIterator() { + LazyStringArrayList rawList = createSampleList(); + UnmodifiableLazyStringList list = new UnmodifiableLazyStringList(rawList); + + ListIterator<String> iter = list.listIterator(); + int count = 0; + while (iter.hasNext()) { + iter.next(); + count++; + try { + iter.remove(); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + try { + iter.set("bar"); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + try { + iter.add("bar"); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + } + assertEquals(3, count); + + List<ByteString> byteStringList = list.asByteStringList(); + ListIterator<ByteString> byteIter = byteStringList.listIterator(); + count = 0; + while (byteIter.hasNext()) { + byteIter.next(); + count++; + try { + byteIter.remove(); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + try { + byteIter.set(BYTE_STRING_A); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + try { + byteIter.add(BYTE_STRING_A); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + } + assertEquals(3, count); + } + + private LazyStringArrayList createSampleList() { + LazyStringArrayList rawList = new LazyStringArrayList(); + rawList.add(STRING_A); + rawList.add(STRING_B); + rawList.add(STRING_C); + return rawList; + } +} diff --git a/java/core/src/test/java/com/google/protobuf/WellKnownTypesTest.java b/java/core/src/test/java/com/google/protobuf/WellKnownTypesTest.java new file mode 100644 index 00000000..982e200f --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/WellKnownTypesTest.java @@ -0,0 +1,65 @@ +// 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.test.TestWellKnownTypes; + +import junit.framework.TestCase; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This test ensures that well-known types are included in protobuf Java + * runtime library. + */ +public class WellKnownTypesTest extends TestCase { + public void testWellKnownTypes() { + // The test passes if it compiles. + TestWellKnownTypes message = TestWellKnownTypes.newBuilder().build(); + assertEquals(0, message.getAnyField().getSerializedSize()); + assertEquals(0, message.getApiField().getSerializedSize()); + assertEquals(0, message.getDurationField().getSerializedSize()); + assertEquals(0, message.getEmptyField().getSerializedSize()); + assertEquals(0, message.getFieldMaskField().getSerializedSize()); + assertEquals(0, message.getSourceContextField().getSerializedSize()); + assertEquals(0, message.getStructField().getSerializedSize()); + assertEquals(0, message.getTimestampField().getSerializedSize()); + assertEquals(0, message.getTypeField().getSerializedSize()); + assertEquals(0, message.getInt32Field().getSerializedSize()); + } +} diff --git a/java/core/src/test/java/com/google/protobuf/WireFormatTest.java b/java/core/src/test/java/com/google/protobuf/WireFormatTest.java new file mode 100644 index 00000000..0175005d --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/WireFormatTest.java @@ -0,0 +1,606 @@ +// 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 junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.util.List; + +import protobuf_unittest.UnittestProto; +import protobuf_unittest.UnittestProto.TestAllExtensions; +import protobuf_unittest.UnittestProto.TestAllTypes; +import protobuf_unittest.UnittestProto.TestFieldOrderings; +import protobuf_unittest.UnittestProto.TestOneof2; +import protobuf_unittest.UnittestProto.TestOneofBackwardsCompatible; +import protobuf_unittest.UnittestProto.TestPackedExtensions; +import protobuf_unittest.UnittestProto.TestPackedTypes; +import protobuf_unittest.UnittestMset.RawMessageSet; +import protobuf_unittest.UnittestMset.TestMessageSetExtension1; +import protobuf_unittest.UnittestMset.TestMessageSetExtension2; +import proto2_wireformat_unittest.UnittestMsetWireFormat.TestMessageSet; +import com.google.protobuf.UnittestLite.TestAllExtensionsLite; +import com.google.protobuf.UnittestLite.TestPackedExtensionsLite; + +/** + * Tests related to parsing and serialization. + * + * @author kenton@google.com (Kenton Varda) + */ +public class WireFormatTest extends TestCase { + public void testSerialization() throws Exception { + TestAllTypes message = TestUtil.getAllSet(); + + ByteString rawBytes = message.toByteString(); + assertEquals(rawBytes.size(), message.getSerializedSize()); + + TestAllTypes message2 = TestAllTypes.parseFrom(rawBytes); + + TestUtil.assertAllFieldsSet(message2); + } + + public void testSerializationPacked() throws Exception { + TestPackedTypes message = TestUtil.getPackedSet(); + + ByteString rawBytes = message.toByteString(); + assertEquals(rawBytes.size(), message.getSerializedSize()); + + TestPackedTypes message2 = TestPackedTypes.parseFrom(rawBytes); + + TestUtil.assertPackedFieldsSet(message2); + } + + public void testSerializeExtensions() throws Exception { + // TestAllTypes and TestAllExtensions should have compatible wire formats, + // so if we serialize a TestAllExtensions then parse it as TestAllTypes + // it should work. + + TestAllExtensions message = TestUtil.getAllExtensionsSet(); + ByteString rawBytes = message.toByteString(); + assertEquals(rawBytes.size(), message.getSerializedSize()); + + TestAllTypes message2 = TestAllTypes.parseFrom(rawBytes); + + TestUtil.assertAllFieldsSet(message2); + } + + public void testSerializePackedExtensions() throws Exception { + // TestPackedTypes and TestPackedExtensions should have compatible wire + // formats; check that they serialize to the same string. + TestPackedExtensions message = TestUtil.getPackedExtensionsSet(); + ByteString rawBytes = message.toByteString(); + + TestPackedTypes message2 = TestUtil.getPackedSet(); + ByteString rawBytes2 = message2.toByteString(); + + assertEquals(rawBytes, rawBytes2); + } + + public void testSerializationPackedWithoutGetSerializedSize() + throws Exception { + // Write directly to an OutputStream, without invoking getSerializedSize() + // This used to be a bug where the size of a packed field was incorrect, + // since getSerializedSize() was never invoked. + TestPackedTypes message = TestUtil.getPackedSet(); + + // Directly construct a CodedOutputStream around the actual OutputStream, + // in case writeTo(OutputStream output) invokes getSerializedSize(); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + CodedOutputStream codedOutput = CodedOutputStream.newInstance(outputStream); + + message.writeTo(codedOutput); + + codedOutput.flush(); + + TestPackedTypes message2 = TestPackedTypes.parseFrom( + outputStream.toByteArray()); + + TestUtil.assertPackedFieldsSet(message2); + } + + public void testSerializeExtensionsLite() throws Exception { + // TestAllTypes and TestAllExtensions should have compatible wire formats, + // so if we serialize a TestAllExtensions then parse it as TestAllTypes + // it should work. + + TestAllExtensionsLite message = TestUtil.getAllLiteExtensionsSet(); + ByteString rawBytes = message.toByteString(); + assertEquals(rawBytes.size(), message.getSerializedSize()); + + TestAllTypes message2 = TestAllTypes.parseFrom(rawBytes); + + TestUtil.assertAllFieldsSet(message2); + } + + public void testSerializePackedExtensionsLite() throws Exception { + // TestPackedTypes and TestPackedExtensions should have compatible wire + // formats; check that they serialize to the same string. + TestPackedExtensionsLite message = TestUtil.getLitePackedExtensionsSet(); + ByteString rawBytes = message.toByteString(); + + TestPackedTypes message2 = TestUtil.getPackedSet(); + ByteString rawBytes2 = message2.toByteString(); + + assertEquals(rawBytes, rawBytes2); + } + + public void testParseExtensions() throws Exception { + // TestAllTypes and TestAllExtensions should have compatible wire formats, + // so if we serialize a TestAllTypes then parse it as TestAllExtensions + // it should work. + + TestAllTypes message = TestUtil.getAllSet(); + ByteString rawBytes = message.toByteString(); + + ExtensionRegistry registry = TestUtil.getExtensionRegistry(); + + TestAllExtensions message2 = + TestAllExtensions.parseFrom(rawBytes, registry); + + TestUtil.assertAllExtensionsSet(message2); + } + + public void testParsePackedExtensions() throws Exception { + // Ensure that packed extensions can be properly parsed. + TestPackedExtensions message = TestUtil.getPackedExtensionsSet(); + ByteString rawBytes = message.toByteString(); + + ExtensionRegistry registry = TestUtil.getExtensionRegistry(); + + TestPackedExtensions message2 = + TestPackedExtensions.parseFrom(rawBytes, registry); + + TestUtil.assertPackedExtensionsSet(message2); + } + + public void testParseExtensionsLite() throws Exception { + // TestAllTypes and TestAllExtensions should have compatible wire formats, + // so if we serialize a TestAllTypes then parse it as TestAllExtensions + // it should work. + + TestAllTypes message = TestUtil.getAllSet(); + ByteString rawBytes = message.toByteString(); + + ExtensionRegistryLite registry_lite = TestUtil.getExtensionRegistryLite(); + + TestAllExtensionsLite message2 = + TestAllExtensionsLite.parseFrom(rawBytes, registry_lite); + + TestUtil.assertAllExtensionsSet(message2); + + // Try again using a full extension registry. + ExtensionRegistry registry = TestUtil.getExtensionRegistry(); + + TestAllExtensionsLite message3 = + TestAllExtensionsLite.parseFrom(rawBytes, registry); + + TestUtil.assertAllExtensionsSet(message3); + } + + public void testParsePackedExtensionsLite() throws Exception { + // Ensure that packed extensions can be properly parsed. + TestPackedExtensionsLite message = TestUtil.getLitePackedExtensionsSet(); + ByteString rawBytes = message.toByteString(); + + ExtensionRegistryLite registry = TestUtil.getExtensionRegistryLite(); + + TestPackedExtensionsLite message2 = + TestPackedExtensionsLite.parseFrom(rawBytes, registry); + + TestUtil.assertPackedExtensionsSet(message2); + } + + public void testExtensionsSerializedSize() throws Exception { + assertNotSame(TestUtil.getAllSet().getSerializedSize(), + TestUtil.getAllExtensionsSet().getSerializedSize()); + } + + public void testSerializeDelimited() throws Exception { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + TestUtil.getAllSet().writeDelimitedTo(output); + output.write(12); + TestUtil.getPackedSet().writeDelimitedTo(output); + output.write(34); + + ByteArrayInputStream input = new ByteArrayInputStream(output.toByteArray()); + + TestUtil.assertAllFieldsSet(TestAllTypes.parseDelimitedFrom(input)); + assertEquals(12, input.read()); + TestUtil.assertPackedFieldsSet(TestPackedTypes.parseDelimitedFrom(input)); + assertEquals(34, input.read()); + assertEquals(-1, input.read()); + + // We're at EOF, so parsing again should return null. + assertTrue(TestAllTypes.parseDelimitedFrom(input) == null); + } + + private void assertFieldsInOrder(ByteString data) throws Exception { + CodedInputStream input = data.newCodedInput(); + int previousTag = 0; + + while (true) { + int tag = input.readTag(); + if (tag == 0) { + break; + } + + assertTrue(tag > previousTag); + previousTag = tag; + input.skipField(tag); + } + } + + public void testInterleavedFieldsAndExtensions() throws Exception { + // Tests that fields are written in order even when extension ranges + // are interleaved with field numbers. + ByteString data = + TestFieldOrderings.newBuilder() + .setMyInt(1) + .setMyString("foo") + .setMyFloat(1.0F) + .setExtension(UnittestProto.myExtensionInt, 23) + .setExtension(UnittestProto.myExtensionString, "bar") + .build().toByteString(); + assertFieldsInOrder(data); + + Descriptors.Descriptor descriptor = TestFieldOrderings.getDescriptor(); + ByteString dynamic_data = + DynamicMessage.newBuilder(TestFieldOrderings.getDescriptor()) + .setField(descriptor.findFieldByName("my_int"), 1L) + .setField(descriptor.findFieldByName("my_string"), "foo") + .setField(descriptor.findFieldByName("my_float"), 1.0F) + .setField(UnittestProto.myExtensionInt.getDescriptor(), 23) + .setField(UnittestProto.myExtensionString.getDescriptor(), "bar") + .build().toByteString(); + assertFieldsInOrder(dynamic_data); + } + + private ExtensionRegistry getTestFieldOrderingsRegistry() { + ExtensionRegistry result = ExtensionRegistry.newInstance(); + result.add(UnittestProto.myExtensionInt); + result.add(UnittestProto.myExtensionString); + return result; + } + + public void testParseMultipleExtensionRanges() throws Exception { + // Make sure we can parse a message that contains multiple extensions + // ranges. + TestFieldOrderings source = + TestFieldOrderings.newBuilder() + .setMyInt(1) + .setMyString("foo") + .setMyFloat(1.0F) + .setExtension(UnittestProto.myExtensionInt, 23) + .setExtension(UnittestProto.myExtensionString, "bar") + .build(); + TestFieldOrderings dest = + TestFieldOrderings.parseFrom(source.toByteString(), + getTestFieldOrderingsRegistry()); + assertEquals(source, dest); + } + + public void testParseMultipleExtensionRangesDynamic() throws Exception { + // Same as above except with DynamicMessage. + Descriptors.Descriptor descriptor = TestFieldOrderings.getDescriptor(); + DynamicMessage source = + DynamicMessage.newBuilder(TestFieldOrderings.getDescriptor()) + .setField(descriptor.findFieldByName("my_int"), 1L) + .setField(descriptor.findFieldByName("my_string"), "foo") + .setField(descriptor.findFieldByName("my_float"), 1.0F) + .setField(UnittestProto.myExtensionInt.getDescriptor(), 23) + .setField(UnittestProto.myExtensionString.getDescriptor(), "bar") + .build(); + DynamicMessage dest = + DynamicMessage.parseFrom(descriptor, source.toByteString(), + getTestFieldOrderingsRegistry()); + assertEquals(source, dest); + } + + private static final int UNKNOWN_TYPE_ID = 1550055; + private static final int TYPE_ID_1 = + TestMessageSetExtension1.getDescriptor().getExtensions().get(0).getNumber(); + private static final int TYPE_ID_2 = + TestMessageSetExtension2.getDescriptor().getExtensions().get(0).getNumber(); + + public void testSerializeMessageSetEagerly() throws Exception { + testSerializeMessageSetWithFlag(true); + } + + public void testSerializeMessageSetNotEagerly() throws Exception { + testSerializeMessageSetWithFlag(false); + } + + private void testSerializeMessageSetWithFlag(boolean eagerParsing) + throws Exception { + ExtensionRegistryLite.setEagerlyParseMessageSets(eagerParsing); + // Set up a TestMessageSet with two known messages and an unknown one. + TestMessageSet messageSet = + TestMessageSet.newBuilder() + .setExtension( + TestMessageSetExtension1.messageSetExtension, + TestMessageSetExtension1.newBuilder().setI(123).build()) + .setExtension( + TestMessageSetExtension2.messageSetExtension, + TestMessageSetExtension2.newBuilder().setStr("foo").build()) + .setUnknownFields( + UnknownFieldSet.newBuilder() + .addField(UNKNOWN_TYPE_ID, + UnknownFieldSet.Field.newBuilder() + .addLengthDelimited(ByteString.copyFromUtf8("bar")) + .build()) + .build()) + .build(); + + ByteString data = messageSet.toByteString(); + + // Parse back using RawMessageSet and check the contents. + RawMessageSet raw = RawMessageSet.parseFrom(data); + + assertTrue(raw.getUnknownFields().asMap().isEmpty()); + + assertEquals(3, raw.getItemCount()); + assertEquals(TYPE_ID_1, raw.getItem(0).getTypeId()); + assertEquals(TYPE_ID_2, raw.getItem(1).getTypeId()); + assertEquals(UNKNOWN_TYPE_ID, raw.getItem(2).getTypeId()); + + TestMessageSetExtension1 message1 = + TestMessageSetExtension1.parseFrom( + raw.getItem(0).getMessage().toByteArray()); + assertEquals(123, message1.getI()); + + TestMessageSetExtension2 message2 = + TestMessageSetExtension2.parseFrom( + raw.getItem(1).getMessage().toByteArray()); + assertEquals("foo", message2.getStr()); + + assertEquals("bar", raw.getItem(2).getMessage().toStringUtf8()); + } + + public void testParseMessageSetEagerly() throws Exception { + testParseMessageSetWithFlag(true); + } + + public void testParseMessageSetNotEagerly()throws Exception { + testParseMessageSetWithFlag(false); + } + + private void testParseMessageSetWithFlag(boolean eagerParsing) + throws Exception { + ExtensionRegistryLite.setEagerlyParseMessageSets(eagerParsing); + ExtensionRegistry extensionRegistry = ExtensionRegistry.newInstance(); + extensionRegistry.add(TestMessageSetExtension1.messageSetExtension); + extensionRegistry.add(TestMessageSetExtension2.messageSetExtension); + + // Set up a RawMessageSet with two known messages and an unknown one. + RawMessageSet raw = + RawMessageSet.newBuilder() + .addItem( + RawMessageSet.Item.newBuilder() + .setTypeId(TYPE_ID_1) + .setMessage( + TestMessageSetExtension1.newBuilder() + .setI(123) + .build().toByteString()) + .build()) + .addItem( + RawMessageSet.Item.newBuilder() + .setTypeId(TYPE_ID_2) + .setMessage( + TestMessageSetExtension2.newBuilder() + .setStr("foo") + .build().toByteString()) + .build()) + .addItem( + RawMessageSet.Item.newBuilder() + .setTypeId(UNKNOWN_TYPE_ID) + .setMessage(ByteString.copyFromUtf8("bar")) + .build()) + .build(); + + ByteString data = raw.toByteString(); + + // Parse as a TestMessageSet and check the contents. + TestMessageSet messageSet = + TestMessageSet.parseFrom(data, extensionRegistry); + + assertEquals(123, messageSet.getExtension( + TestMessageSetExtension1.messageSetExtension).getI()); + assertEquals("foo", messageSet.getExtension( + TestMessageSetExtension2.messageSetExtension).getStr()); + + // Check for unknown field with type LENGTH_DELIMITED, + // number UNKNOWN_TYPE_ID, and contents "bar". + UnknownFieldSet unknownFields = messageSet.getUnknownFields(); + assertEquals(1, unknownFields.asMap().size()); + assertTrue(unknownFields.hasField(UNKNOWN_TYPE_ID)); + + UnknownFieldSet.Field field = unknownFields.getField(UNKNOWN_TYPE_ID); + assertEquals(1, field.getLengthDelimitedList().size()); + assertEquals("bar", field.getLengthDelimitedList().get(0).toStringUtf8()); + } + + public void testParseMessageSetExtensionEagerly() throws Exception { + testParseMessageSetExtensionWithFlag(true); + } + + public void testParseMessageSetExtensionNotEagerly() throws Exception { + testParseMessageSetExtensionWithFlag(false); + } + + private void testParseMessageSetExtensionWithFlag(boolean eagerParsing) + throws Exception { + ExtensionRegistryLite.setEagerlyParseMessageSets(eagerParsing); + ExtensionRegistry extensionRegistry = ExtensionRegistry.newInstance(); + extensionRegistry.add(TestMessageSetExtension1.messageSetExtension); + + // Set up a RawMessageSet with a known messages. + int TYPE_ID_1 = + TestMessageSetExtension1 + .getDescriptor().getExtensions().get(0).getNumber(); + RawMessageSet raw = + RawMessageSet.newBuilder() + .addItem( + RawMessageSet.Item.newBuilder() + .setTypeId(TYPE_ID_1) + .setMessage( + TestMessageSetExtension1.newBuilder() + .setI(123) + .build().toByteString()) + .build()) + .build(); + + ByteString data = raw.toByteString(); + + // Parse as a TestMessageSet and check the contents. + TestMessageSet messageSet = + TestMessageSet.parseFrom(data, extensionRegistry); + assertEquals(123, messageSet.getExtension( + TestMessageSetExtension1.messageSetExtension).getI()); + } + + public void testMergeLazyMessageSetExtensionEagerly() throws Exception { + testMergeLazyMessageSetExtensionWithFlag(true); + } + + public void testMergeLazyMessageSetExtensionNotEagerly() throws Exception { + testMergeLazyMessageSetExtensionWithFlag(false); + } + + private void testMergeLazyMessageSetExtensionWithFlag(boolean eagerParsing) + throws Exception { + ExtensionRegistryLite.setEagerlyParseMessageSets(eagerParsing); + ExtensionRegistry extensionRegistry = ExtensionRegistry.newInstance(); + extensionRegistry.add(TestMessageSetExtension1.messageSetExtension); + + // Set up a RawMessageSet with a known messages. + int TYPE_ID_1 = + TestMessageSetExtension1 + .getDescriptor().getExtensions().get(0).getNumber(); + RawMessageSet raw = + RawMessageSet.newBuilder() + .addItem( + RawMessageSet.Item.newBuilder() + .setTypeId(TYPE_ID_1) + .setMessage( + TestMessageSetExtension1.newBuilder() + .setI(123) + .build().toByteString()) + .build()) + .build(); + + ByteString data = raw.toByteString(); + + // Parse as a TestMessageSet and store value into lazy field + TestMessageSet messageSet = + TestMessageSet.parseFrom(data, extensionRegistry); + // Merge lazy field check the contents. + messageSet = + messageSet.toBuilder().mergeFrom(data, extensionRegistry).build(); + assertEquals(123, messageSet.getExtension( + TestMessageSetExtension1.messageSetExtension).getI()); + } + + public void testMergeMessageSetExtensionEagerly() throws Exception { + testMergeMessageSetExtensionWithFlag(true); + } + + public void testMergeMessageSetExtensionNotEagerly() throws Exception { + testMergeMessageSetExtensionWithFlag(false); + } + + private void testMergeMessageSetExtensionWithFlag(boolean eagerParsing) + throws Exception { + ExtensionRegistryLite.setEagerlyParseMessageSets(eagerParsing); + ExtensionRegistry extensionRegistry = ExtensionRegistry.newInstance(); + extensionRegistry.add(TestMessageSetExtension1.messageSetExtension); + + // Set up a RawMessageSet with a known messages. + int TYPE_ID_1 = + TestMessageSetExtension1 + .getDescriptor().getExtensions().get(0).getNumber(); + RawMessageSet raw = + RawMessageSet.newBuilder() + .addItem( + RawMessageSet.Item.newBuilder() + .setTypeId(TYPE_ID_1) + .setMessage( + TestMessageSetExtension1.newBuilder() + .setI(123) + .build().toByteString()) + .build()) + .build(); + + // Serialize RawMessageSet unnormally (message value before type id) + ByteString.CodedBuilder out = ByteString.newCodedBuilder( + raw.getSerializedSize()); + CodedOutputStream output = out.getCodedOutput(); + List<RawMessageSet.Item> items = raw.getItemList(); + for (int i = 0; i < items.size(); i++) { + RawMessageSet.Item item = items.get(i); + output.writeTag(1, WireFormat.WIRETYPE_START_GROUP); + output.writeBytes(3, item.getMessage()); + output.writeInt32(2, item.getTypeId()); + output.writeTag(1, WireFormat.WIRETYPE_END_GROUP); + } + ByteString data = out.build(); + + // Merge bytes into TestMessageSet and check the contents. + TestMessageSet messageSet = + TestMessageSet.newBuilder().mergeFrom(data, extensionRegistry).build(); + assertEquals(123, messageSet.getExtension( + TestMessageSetExtension1.messageSetExtension).getI()); + } + + // ================================================================ + // oneof + public void testOneofWireFormat() throws Exception { + TestOneof2.Builder builder = TestOneof2.newBuilder(); + TestUtil.setOneof(builder); + TestOneof2 message = builder.build(); + ByteString rawBytes = message.toByteString(); + + assertEquals(rawBytes.size(), message.getSerializedSize()); + + TestOneof2 message2 = TestOneof2.parseFrom(rawBytes); + TestUtil.assertOneofSet(message2); + } + + public void testOneofOnlyLastSet() throws Exception { + TestOneofBackwardsCompatible source = TestOneofBackwardsCompatible + .newBuilder().setFooInt(100).setFooString("101").build(); + + ByteString rawBytes = source.toByteString(); + TestOneof2 message = TestOneof2.parseFrom(rawBytes); + assertFalse(message.hasFooInt()); + assertTrue(message.hasFooString()); + } +} diff --git a/java/core/src/test/proto/com/google/protobuf/any_test.proto b/java/core/src/test/proto/com/google/protobuf/any_test.proto new file mode 100644 index 00000000..80173d8a --- /dev/null +++ b/java/core/src/test/proto/com/google/protobuf/any_test.proto @@ -0,0 +1,42 @@ +// 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 any_test; + +option java_package = "any_test"; +option java_outer_classname = "AnyTestProto"; + +import "google/protobuf/any.proto"; + +message TestAny { + google.protobuf.Any value = 1; +} diff --git a/java/core/src/test/proto/com/google/protobuf/field_presence_test.proto b/java/core/src/test/proto/com/google/protobuf/field_presence_test.proto new file mode 100644 index 00000000..8f3ca8c6 --- /dev/null +++ b/java/core/src/test/proto/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 { + int32 value = 1; + } + + int32 optional_int32 = 1; + string optional_string = 2; + bytes optional_bytes = 3; + NestedEnum optional_nested_enum = 4; + NestedMessage optional_nested_message = 5; + 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 { + int32 optional_int32 = 1; + string optional_string = 2; + bytes optional_bytes = 3; + TestAllTypes.NestedEnum optional_nested_enum = 4; + TestAllTypes.NestedMessage optional_nested_message = 5; + 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/core/src/test/proto/com/google/protobuf/lazy_fields_lite.proto b/java/core/src/test/proto/com/google/protobuf/lazy_fields_lite.proto new file mode 100644 index 00000000..5580f72d --- /dev/null +++ b/java/core/src/test/proto/com/google/protobuf/lazy_fields_lite.proto @@ -0,0 +1,71 @@ +// 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. + +// Author: Naoki Iwasaki (niwasaki@google.com) +// +// A proto file with lazy fields + +syntax = "proto2"; + +package protobuf_unittest; + +option optimize_for = LITE_RUNTIME; + +message LazyMessageLite { + optional int32 num = 1; + optional int32 num_with_default = 2 [default = 421]; + optional LazyInnerMessageLite inner = 3 [lazy = true]; + repeated LazyInnerMessageLite repeated_inner = 4 [lazy = true]; + + oneof oneof_field { + int32 oneof_num = 5; + LazyInnerMessageLite oneof_inner = 6 [lazy = true]; + } +} + +message LazyInnerMessageLite { + optional int32 num = 1; + optional int32 num_with_default = 2 [default = 42]; + optional LazyNestedInnerMessageLite nested = 3 [lazy = true]; + + extensions 1000 to max; +} + +message LazyExtension { + extend LazyInnerMessageLite { + optional LazyExtension extension = 1000; + } + optional string name = 1; +} + +message LazyNestedInnerMessageLite { + optional int32 num = 1; + optional int32 num_with_default = 2 [default = 4]; +} diff --git a/java/core/src/test/proto/com/google/protobuf/lite_equals_and_hash.proto b/java/core/src/test/proto/com/google/protobuf/lite_equals_and_hash.proto new file mode 100644 index 00000000..86837250 --- /dev/null +++ b/java/core/src/test/proto/com/google/protobuf/lite_equals_and_hash.proto @@ -0,0 +1,72 @@ +// 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. + +// Author: pbogle@google.com (Phil Bogle) + +syntax = "proto2"; + +package protobuf_unittest.lite_equals_and_hash; + +// This proto definition is used to test that java_generate_equals_and_hash +// works correctly with the LITE_RUNTIME. +option java_generate_equals_and_hash = true; +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; +} + +message BarPrime { + optional string name = 1; +} + +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/core/src/test/proto/com/google/protobuf/map_for_proto2_lite_test.proto b/java/core/src/test/proto/com/google/protobuf/map_for_proto2_lite_test.proto new file mode 100644 index 00000000..d5418f28 --- /dev/null +++ b/java/core/src/test/proto/com/google/protobuf/map_for_proto2_lite_test.proto @@ -0,0 +1,75 @@ +// 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, int32> int32_to_int32_field = 1; + map<int32, string> int32_to_string_field = 2; + map<int32, bytes> int32_to_bytes_field = 3; + map<int32, EnumValue> int32_to_enum_field = 4; + map<int32, MessageValue> int32_to_message_field = 5; + map<string, int32> string_to_int32_field = 6; + + message MessageWithRequiredFields { + required int32 value = 1; + } + map<int32, MessageWithRequiredFields> required_message_map = 11; +} + +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, int32> int32_to_int32_field = 4; +} + +// Test that the maps initialization code works correctly when the map field +// references the containing message. +message TestRecursiveMap { + optional int32 value = 1; + map<int32, TestRecursiveMap> recursive_map_field = 2; +} +package map_for_proto2_lite_test; +option java_package = "map_lite_test"; +option optimize_for = LITE_RUNTIME; diff --git a/java/core/src/test/proto/com/google/protobuf/map_for_proto2_test.proto b/java/core/src/test/proto/com/google/protobuf/map_for_proto2_test.proto new file mode 100644 index 00000000..a9be5166 --- /dev/null +++ b/java/core/src/test/proto/com/google/protobuf/map_for_proto2_test.proto @@ -0,0 +1,74 @@ +// 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, int32> int32_to_int32_field = 1; + map<int32, string> int32_to_string_field = 2; + map<int32, bytes> int32_to_bytes_field = 3; + map<int32, EnumValue> int32_to_enum_field = 4; + map<int32, MessageValue> int32_to_message_field = 5; + map<string, int32> string_to_int32_field = 6; + + message MessageWithRequiredFields { + required int32 value = 1; + } + map<int32, MessageWithRequiredFields> required_message_map = 11; +} + +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, int32> int32_to_int32_field = 4; +} + +// Test that the maps initialization code works correctly when the map field +// references the containing message. +message TestRecursiveMap { + optional int32 value = 1; + map<int32, TestRecursiveMap> recursive_map_field = 2; +} diff --git a/java/core/src/test/proto/com/google/protobuf/map_initialization_order_test.proto b/java/core/src/test/proto/com/google/protobuf/map_initialization_order_test.proto new file mode 100644 index 00000000..b02ac599 --- /dev/null +++ b/java/core/src/test/proto/com/google/protobuf/map_initialization_order_test.proto @@ -0,0 +1,61 @@ +// 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. + +// Regression test for a map initilaization order bug. The bug only manifests +// when: +// 1. A message contains map fields and is also extendable. +// 2. There is a file-level extension defined in the same file referencing +// the above message as the extension type. +// 3. The program executes in the following order: +// a. getDescriptor() is called on another message in the same file. +// b. use protobuf reflection to access the map field. +// The symptom is a NullPointerException being thrown. +syntax = "proto2"; + +package map_test; + +option java_package = "map_test"; +option java_outer_classname = "MapInitializationOrderTest"; +option java_multiple_files = true; + +// Mirrors the structure of +// javatests/com/google/cloud/common/logging/logging_test.proto. + +message Message1 { + map<string, bool> map_field = 1; + extensions 1000 to max; +} + +extend Message1 { + optional Message1 recursive_extension = 1001; +} + +message RedactAllTypes { +} diff --git a/java/core/src/test/proto/com/google/protobuf/map_test.proto b/java/core/src/test/proto/com/google/protobuf/map_test.proto new file mode 100644 index 00000000..2280ac03 --- /dev/null +++ b/java/core/src/test/proto/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 { + int32 value = 1; + } + enum EnumValue { + FOO = 0; + BAR = 1; + BAZ = 2; + QUX = 3; + } + + map<int32, int32> int32_to_int32_field = 1; + map<int32, string> int32_to_string_field = 2; + map<int32, bytes> int32_to_bytes_field = 3; + map<int32, EnumValue> int32_to_enum_field = 4; + map<int32, MessageValue> int32_to_message_field = 5; + map<string, int32> 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 { + TestMap optional_message = 1; +} diff --git a/java/core/src/test/proto/com/google/protobuf/multiple_files_test.proto b/java/core/src/test/proto/com/google/protobuf/multiple_files_test.proto new file mode 100644 index 00000000..92790506 --- /dev/null +++ b/java/core/src/test/proto/com/google/protobuf/multiple_files_test.proto @@ -0,0 +1,78 @@ +// 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. + +// Author: kenton@google.com (Kenton Varda) +// +// 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 +option java_generic_services = true; // auto-added + +import "google/protobuf/unittest.proto"; +import "google/protobuf/descriptor.proto"; + +package protobuf_unittest; + +option java_multiple_files = true; +option java_outer_classname = "MultipleFilesTestProto"; + + +message MessageWithNoOuter { + message NestedMessage { + optional int32 i = 1; + } + enum NestedEnum { + BAZ = 3; + } + optional NestedMessage nested = 1; + repeated TestAllTypes foreign = 2; + optional NestedEnum nested_enum = 3; + optional EnumWithNoOuter foreign_enum = 4; +} + +extend google.protobuf.EnumValueOptions { + optional int32 enum_value_option = 7654321; +} + +enum EnumWithNoOuter { + FOO = 1 [(enum_value_option) = 12345]; + BAR = 2; +} + +service ServiceWithNoOuter { + rpc Foo(MessageWithNoOuter) returns(TestAllTypes); +} + +extend TestAllExtensions { + optional int32 extension_with_outer = 1234567; +} diff --git a/java/core/src/test/proto/com/google/protobuf/nested_builders_test.proto b/java/core/src/test/proto/com/google/protobuf/nested_builders_test.proto new file mode 100644 index 00000000..a5dd66d8 --- /dev/null +++ b/java/core/src/test/proto/com/google/protobuf/nested_builders_test.proto @@ -0,0 +1,54 @@ +// 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. + +// Author: jonp@google.com (Jon Perlow) +// +syntax = "proto2"; + +package protobuf_unittest; + +option java_multiple_files = true; +option java_outer_classname = "NestedBuilders"; + + +message Vehicle { + optional Engine engine = 1; + repeated Wheel wheel = 2; +} + +message Engine { + optional int32 cylinder = 1; + optional int32 liters = 2; +} + +message Wheel { + optional int32 radius = 1; + optional int32 width = 2; +} diff --git a/java/core/src/test/proto/com/google/protobuf/nested_extension.proto b/java/core/src/test/proto/com/google/protobuf/nested_extension.proto new file mode 100644 index 00000000..704e649e --- /dev/null +++ b/java/core/src/test/proto/com/google/protobuf/nested_extension.proto @@ -0,0 +1,47 @@ +// 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. + +// Author: Darick Tong (darick@google.com) +// +// 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"; + +package protobuf_unittest; + + +message MyNestedExtension { + extend MessageToBeExtended { + optional MessageToBeExtended recursiveExtension = 2; + } +} diff --git a/java/core/src/test/proto/com/google/protobuf/nested_extension_lite.proto b/java/core/src/test/proto/com/google/protobuf/nested_extension_lite.proto new file mode 100644 index 00000000..a95c38b2 --- /dev/null +++ b/java/core/src/test/proto/com/google/protobuf/nested_extension_lite.proto @@ -0,0 +1,49 @@ +// 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. + +// Author: Darick Tong (darick@google.com) +// +// A proto file with nested extensions for a MessageLite messages. Note that +// this must be defined in a separate file to properly test the initialization +// of the outer class. + +syntax = "proto2"; + +package protobuf_unittest; + +option optimize_for = LITE_RUNTIME; + +import "com/google/protobuf/non_nested_extension_lite.proto"; + +message MyNestedExtensionLite { + extend MessageLiteToBeExtended { + optional MessageLiteToBeExtended recursiveExtensionLite = 3; + } +} diff --git a/java/core/src/test/proto/com/google/protobuf/non_nested_extension.proto b/java/core/src/test/proto/com/google/protobuf/non_nested_extension.proto new file mode 100644 index 00000000..31fac552 --- /dev/null +++ b/java/core/src/test/proto/com/google/protobuf/non_nested_extension.proto @@ -0,0 +1,50 @@ +// 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. + +// Author: Darick Tong (darick@google.com) +// +// A proto file with extensions. + +syntax = "proto2"; + +package protobuf_unittest; + + +message MessageToBeExtended { + extensions 1 to max; +} + +message MyNonNestedExtension { +} + +extend MessageToBeExtended { + optional MyNonNestedExtension nonNestedExtension = 1; +} + diff --git a/java/core/src/test/proto/com/google/protobuf/non_nested_extension_lite.proto b/java/core/src/test/proto/com/google/protobuf/non_nested_extension_lite.proto new file mode 100644 index 00000000..37c369ed --- /dev/null +++ b/java/core/src/test/proto/com/google/protobuf/non_nested_extension_lite.proto @@ -0,0 +1,51 @@ +// 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. + +// Author: Darick Tong (darick@google.com) +// +// A proto file with extensions for a MessageLite messages. + +syntax = "proto2"; + +package protobuf_unittest; + +option optimize_for = LITE_RUNTIME; + +message MessageLiteToBeExtended { + extensions 1 to max; +} + +message MyNonNestedExtensionLite { +} + +extend MessageLiteToBeExtended { + optional MyNonNestedExtensionLite nonNestedExtensionLite = 1; +} + diff --git a/java/core/src/test/proto/com/google/protobuf/outer_class_name_test.proto b/java/core/src/test/proto/com/google/protobuf/outer_class_name_test.proto new file mode 100644 index 00000000..42083681 --- /dev/null +++ b/java/core/src/test/proto/com/google/protobuf/outer_class_name_test.proto @@ -0,0 +1,40 @@ +// 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 protobuf_unittest; + + +// This message's name is the same with the default outer class name of this +// proto file. It's used to test if the compiler can avoid this conflict +// correctly. +message OuterClassNameTest { +} diff --git a/java/core/src/test/proto/com/google/protobuf/outer_class_name_test2.proto b/java/core/src/test/proto/com/google/protobuf/outer_class_name_test2.proto new file mode 100644 index 00000000..3e5956b0 --- /dev/null +++ b/java/core/src/test/proto/com/google/protobuf/outer_class_name_test2.proto @@ -0,0 +1,44 @@ +// 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 protobuf_unittest; + + +message TestMessage2 { + message NestedMessage { + // This message's name is the same with the default outer class name of this + // proto file. It's used to test if the compiler can avoid this conflict + // correctly. + message OuterClassNameTest2 { + } + } +} diff --git a/java/core/src/test/proto/com/google/protobuf/outer_class_name_test3.proto b/java/core/src/test/proto/com/google/protobuf/outer_class_name_test3.proto new file mode 100644 index 00000000..74a8ba3c --- /dev/null +++ b/java/core/src/test/proto/com/google/protobuf/outer_class_name_test3.proto @@ -0,0 +1,45 @@ +// 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 protobuf_unittest; + + +message TestMessage3 { + message NestedMessage { + // This enum's name is the same with the default outer class name of this + // proto file. It's used to test if the compiler can avoid this conflict + // correctly. + enum OuterClassNameTest3 { + DUMMY_VALUE = 1; + } + } +} diff --git a/java/core/src/test/proto/com/google/protobuf/test_bad_identifiers.proto b/java/core/src/test/proto/com/google/protobuf/test_bad_identifiers.proto new file mode 100644 index 00000000..2b1f65e4 --- /dev/null +++ b/java/core/src/test/proto/com/google/protobuf/test_bad_identifiers.proto @@ -0,0 +1,169 @@ +// 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. + +// Author: jonp@google.com (Jon Perlow) + +// 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 +option java_generic_services = true; // auto-added + +package io_protocol_tests; + +option java_package = "com.google.protobuf"; +option java_outer_classname = "TestBadIdentifiersProto"; +option java_generate_equals_and_hash = true; + +message TestMessage { + optional string cached_size = 1; + optional string serialized_size = 2; + optional string class = 3; +} + +message Descriptor { + option no_standard_descriptor_accessor = true; + optional string descriptor = 1; + message NestedDescriptor { + option no_standard_descriptor_accessor = true; + optional string descriptor = 1; + } + optional NestedDescriptor nested_descriptor = 2; + enum NestedEnum { + UNKNOWN = 0; + FOO = 1; + } +} + +message Parser { + enum ParserEnum { + UNKNOWN = 0; + PARSER = 1; + } + optional ParserEnum parser = 1; +} + +message Deprecated { + enum TestEnum { + UNKNOWN = 0; + FOO = 1; + + // Test if @Deprecated annotation conflicts with Deprecated message name. + BAR = 2 [ deprecated = true ]; + } + + optional int32 field1 = 1 [deprecated=true]; + optional TestEnum field2 = 2 [deprecated=true]; + optional TestMessage field3 = 3 [deprecated=true]; +} + +message Override { + optional int32 override = 1; +} + +message Object { + optional int32 object = 1; + optional string string_object = 2; +} + +message String { + optional string string = 1; +} + +message Integer { + optional int32 integer = 1; +} + +message Long { + optional int32 long = 1; +} + +message Float { + optional float float = 1; +} + +message Double { + optional double double = 1; +} + +service TestConflictingMethodNames { + rpc Override(TestMessage) returns (TestMessage); +} + +message TestConflictingFieldNames { + enum TestEnum { + UNKNOWN = 0; + FOO = 1; + } + message TestMessage { + } + repeated int32 int32_field = 1; + repeated TestEnum enum_field = 2; + repeated string string_field = 3; + repeated bytes bytes_field = 4; + repeated TestMessage message_field = 5; + + optional int32 int32_field_count = 11; + optional TestEnum enum_field_count = 12; + optional string string_field_count = 13; + optional bytes bytes_field_count = 14; + optional TestMessage message_field_count = 15; + + repeated int32 Int32Field = 21; // NO_PROTO3 + repeated TestEnum EnumField = 22; // NO_PROTO3 + repeated string StringField = 23; // NO_PROTO3 + repeated bytes BytesField = 24; // NO_PROTO3 + repeated TestMessage MessageField = 25; // NO_PROTO3 + + // This field conflicts with "int32_field" as they both generate + // the method getInt32FieldList(). + required int32 int32_field_list = 31; // NO_PROTO3 + + extensions 1000 to max; // NO_PROTO3 + + repeated int64 int64_field = 41; + 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; // NO_PROTO3 + optional int64 int64_field_list = 1002; // NO_PROTO3 + } // NO_PROTO3 +} + +message TestMapField { + message MapField {} + message Pair {} + message Message {} + + map<int32, int32> map_field = 1; +} diff --git a/java/core/src/test/proto/com/google/protobuf/test_check_utf8.proto b/java/core/src/test/proto/com/google/protobuf/test_check_utf8.proto new file mode 100644 index 00000000..119c1dcb --- /dev/null +++ b/java/core/src/test/proto/com/google/protobuf/test_check_utf8.proto @@ -0,0 +1,51 @@ +// 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. + +// Author: Jacob Butcher (jbaum@google.com) +// +// Test file option java_string_check_utf8. +syntax = "proto2"; + +package proto2_test_check_utf8; + +option java_outer_classname = "TestCheckUtf8"; +option java_string_check_utf8 = true; + +message StringWrapper { + required string req = 1; + optional string opt = 2; + repeated string rep = 3; +} + +message BytesWrapper { + required bytes req = 1; + optional bytes opt = 2; + repeated bytes rep = 3; +} diff --git a/java/core/src/test/proto/com/google/protobuf/test_check_utf8_size.proto b/java/core/src/test/proto/com/google/protobuf/test_check_utf8_size.proto new file mode 100644 index 00000000..f06d76d6 --- /dev/null +++ b/java/core/src/test/proto/com/google/protobuf/test_check_utf8_size.proto @@ -0,0 +1,52 @@ +// 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. + +// Author: Jacob Butcher (jbaum@google.com) +// +// Test file option java_string_check_utf8. +syntax = "proto2"; + +package proto2_test_check_utf8_size; + +option java_outer_classname = "TestCheckUtf8Size"; +option java_string_check_utf8 = true; +option optimize_for = CODE_SIZE; + +message StringWrapperSize { + required string req = 1; + optional string opt = 2; + repeated string rep = 3; +} + +message BytesWrapperSize { + required bytes req = 1; + optional bytes opt = 2; + repeated bytes rep = 3; +} diff --git a/java/core/src/test/proto/com/google/protobuf/test_custom_options.proto b/java/core/src/test/proto/com/google/protobuf/test_custom_options.proto new file mode 100644 index 00000000..f8efd455 --- /dev/null +++ b/java/core/src/test/proto/com/google/protobuf/test_custom_options.proto @@ -0,0 +1,44 @@ +// 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. + +// Author: Feng Xiao (xiaofeng@google.com) +// +// Test that custom options defined in a proto file's dependencies are properly +// initialized. +syntax = "proto2"; + +package protobuf_unittest; + + +import "google/protobuf/unittest_custom_options.proto"; + +message TestMessageWithCustomOptionsContainer { + optional TestMessageWithCustomOptions field = 1; +} diff --git a/java/core/src/test/proto/com/google/protobuf/test_extra_interfaces.proto b/java/core/src/test/proto/com/google/protobuf/test_extra_interfaces.proto new file mode 100644 index 00000000..645f57b4 --- /dev/null +++ b/java/core/src/test/proto/com/google/protobuf/test_extra_interfaces.proto @@ -0,0 +1,61 @@ +// 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. + +// Author: Darick Tong (darick@google.com) +syntax = "proto2"; + +package protobuf_unittest; + +message Proto1 { + option experimental_java_message_interface = + "com.google.protobuf.ExtraInterfaces.HasBoolValue"; + + option experimental_java_interface_extends = + "com.google.protobuf.ExtraInterfaces.HasByteValue"; + + option experimental_java_message_interface = + "com.google.protobuf.ExtraInterfaces.HasStringValue<Proto1>"; + + option experimental_java_builder_interface = + "com.google.protobuf.ExtraInterfaces.HasStringValueBuilder" + "<Proto1, Builder>"; + + optional string string_value = 1; + optional bool bool_value = 2; + optional bytes byte_value = 3; + optional int32 int_value = 4; +} + +message Proto2 { + option experimental_java_message_interface = + "com.google.protobuf.ExtraInterfaces.HasBoolValue"; + + optional bool bool_value = 1; +} |