diff options
author | Dan O'Reilly <oreilldf@gmail.com> | 2015-08-12 23:57:46 -0400 |
---|---|---|
committer | Dan O'Reilly <oreilldf@gmail.com> | 2015-08-12 23:57:46 -0400 |
commit | e47cdd5a559f488ba52756927ce68f4cf93874fa (patch) | |
tree | 8ce2723e822808baf58e96f569c86035717ea351 /java | |
parent | daeaa6a28b81195f24d89222e649d79c9555af8b (diff) | |
parent | 38a56ee4b19d72c2e9d81a08b018704d1addf561 (diff) | |
download | protobuf-e47cdd5a559f488ba52756927ce68f4cf93874fa.tar.gz protobuf-e47cdd5a559f488ba52756927ce68f4cf93874fa.tar.bz2 protobuf-e47cdd5a559f488ba52756927ce68f4cf93874fa.zip |
Merge remote-tracking branch 'upstream/master' into py2_py3_straddle
Conflicts:
python/google/protobuf/descriptor_pool.py
python/google/protobuf/internal/api_implementation_default_test.py
python/google/protobuf/internal/cpp_message.py
python/google/protobuf/internal/descriptor_database_test.py
python/google/protobuf/internal/descriptor_pool_test.py
python/google/protobuf/internal/descriptor_python_test.py
python/google/protobuf/internal/descriptor_test.py
python/google/protobuf/internal/generator_test.py
python/google/protobuf/internal/message_factory_python_test.py
python/google/protobuf/internal/message_factory_test.py
python/google/protobuf/internal/message_test.py
python/google/protobuf/internal/proto_builder_test.py
python/google/protobuf/internal/python_message.py
python/google/protobuf/internal/reflection_test.py
python/google/protobuf/internal/service_reflection_test.py
python/google/protobuf/internal/symbol_database_test.py
python/google/protobuf/internal/text_encoding_test.py
python/google/protobuf/internal/text_format_test.py
python/google/protobuf/internal/unknown_fields_test.py
python/google/protobuf/internal/wire_format_test.py
python/google/protobuf/pyext/descriptor_cpp2_test.py
python/google/protobuf/pyext/message_factory_cpp2_test.py
python/google/protobuf/pyext/reflection_cpp2_generated_test.py
python/setup.py
ruby/lib/google/protobuf/message_exts.rb
Diffstat (limited to 'java')
68 files changed, 8691 insertions, 673 deletions
diff --git a/java/README.txt b/java/README.md index 57e26a96..060d9ac6 100644 --- a/java/README.txt +++ b/java/README.md @@ -1,4 +1,8 @@ Protocol Buffers - Google's data interchange format +=================================================== + +[![Build Status](https://travis-ci.org/google/protobuf.svg?branch=master)](https://travis-ci.org/google/protobuf) + Copyright 2008 Google Inc. This directory contains the Java Protocol Buffers runtime library. @@ -50,17 +54,19 @@ http://maven.apache.org/guides/introduction/introduction-to-profiles.html) E.g. to install the lite version of the jar, you would run: - $ mvn install -P lite + $ mvn install -P lite The resulting artifact has the 'lite' classifier. To reference it for dependency resolution, you would specify it as: +``` <dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java</artifactId> <version>${version}</version> <classifier>lite</classifier> </dependency> +``` Installation - Without Maven ============================ diff --git a/java/pom.xml b/java/pom.xml index bbadc656..6877ac27 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -10,7 +10,7 @@ </parent> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java</artifactId> - <version>3.0.0-pre</version> + <version>3.0.0-alpha-4-pre</version> <packaging>bundle</packaging> <name>Protocol Buffer Java API</name> <description> @@ -81,7 +81,17 @@ <exec executable="../src/protoc"> <arg value="--java_out=target/generated-sources" /> <arg value="--proto_path=../src" /> + <arg value="../src/google/protobuf/any.proto" /> + <arg value="../src/google/protobuf/api.proto" /> <arg value="../src/google/protobuf/descriptor.proto" /> + <arg value="../src/google/protobuf/duration.proto" /> + <arg value="../src/google/protobuf/empty.proto" /> + <arg value="../src/google/protobuf/field_mask.proto" /> + <arg value="../src/google/protobuf/source_context.proto" /> + <arg value="../src/google/protobuf/struct.proto" /> + <arg value="../src/google/protobuf/timestamp.proto" /> + <arg value="../src/google/protobuf/type.proto" /> + <arg value="../src/google/protobuf/wrappers.proto" /> </exec> </tasks> <sourceRoot>target/generated-sources</sourceRoot> @@ -130,10 +140,12 @@ <arg value="../src/google/protobuf/unittest_lite_imports_nonlite.proto" /> <arg value="../src/google/protobuf/unittest_enormous_descriptor.proto" /> <arg value="../src/google/protobuf/unittest_no_generic_services.proto" /> + <arg value="../src/google/protobuf/unittest_well_known_types.proto" /> <arg value="src/test/java/com/google/protobuf/field_presence_test.proto" /> <arg value="src/test/java/com/google/protobuf/map_for_proto2_lite_test.proto" /> <arg value="src/test/java/com/google/protobuf/map_for_proto2_test.proto" /> <arg value="src/test/java/com/google/protobuf/map_test.proto" /> + <arg value="src/test/java/com/google/protobuf/map_initialization_order_test.proto" /> </exec> </tasks> <testSourceRoot>target/generated-test-sources</testSourceRoot> @@ -152,14 +164,100 @@ <instructions> <Bundle-DocURL>https://developers.google.com/protocol-buffers/</Bundle-DocURL> <Bundle-SymbolicName>com.google.protobuf</Bundle-SymbolicName> - <Export-Package>com.google.protobuf;version=3.0.0-pre</Export-Package> + <Export-Package>com.google.protobuf;version=3.0.0-alpha-3</Export-Package> </instructions> </configuration> </plugin> </plugins> + <resources> + <resource> + <directory>../src</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> + </includes> + </resource> + </resources> </build> <profiles> <profile> + <id>release</id> + <distributionManagement> + <snapshotRepository> + <id>sonatype-nexus-staging</id> + <url>https://oss.sonatype.org/content/repositories/snapshots</url> + </snapshotRepository> + <repository> + <id>sonatype-nexus-staging</id> + <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url> + </repository> + </distributionManagement> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-source-plugin</artifactId> + <version>2.2.1</version> + <executions> + <execution> + <id>attach-sources</id> + <goals> + <goal>jar-no-fork</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-javadoc-plugin</artifactId> + <version>2.9.1</version> + <executions> + <execution> + <id>attach-javadocs</id> + <goals> + <goal>jar</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-gpg-plugin</artifactId> + <version>1.5</version> + <executions> + <execution> + <id>sign-artifacts</id> + <phase>verify</phase> + <goals> + <goal>sign</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.sonatype.plugins</groupId> + <artifactId>nexus-staging-maven-plugin</artifactId> + <version>1.6.3</version> + <extensions>true</extensions> + <configuration> + <serverId>sonatype-nexus-staging</serverId> + <nexusUrl>https://oss.sonatype.org/</nexusUrl> + <autoReleaseAfterClose>false</autoReleaseAfterClose> + </configuration> + </plugin> + </plugins> + </build> + </profile> + <profile> <id>lite</id> <build> <plugins> @@ -169,25 +267,33 @@ <includes> <include>**/AbstractMessageLite.java</include> <include>**/AbstractParser.java</include> + <include>**/AbstractProtobufList.java</include> <include>**/BoundedByteString.java</include> + <include>**/BooleanArrayList.java</include> <include>**/ByteString.java</include> <include>**/CodedInputStream.java</include> <include>**/CodedOutputStream.java</include> + <include>**/DoublerrayList.java</include> <include>**/ExtensionLite.java</include> <include>**/ExtensionRegistryLite.java</include> <include>**/FieldSet.java</include> + <include>**/FloatArrayList.java</include> <include>**/GeneratedMessageLite.java</include> + <include>**/IntArrayList.java</include> <include>**/Internal.java</include> <include>**/InvalidProtocolBufferException.java</include> <include>**/LazyFieldLite.java</include> <include>**/LazyStringArrayList.java</include> <include>**/LazyStringList.java</include> <include>**/LiteralByteString.java</include> + <include>**/LongArrayList.java</include> <include>**/MapEntryLite.java</include> <include>**/MapFieldLite.java</include> <include>**/MessageLite.java</include> <include>**/MessageLiteOrBuilder.java</include> + <include>**/MutabilityOracle.java</include> <include>**/Parser.java</include> + <include>**/ProtobufArrayList.java</include> <include>**/ProtocolStringList.java</include> <include>**/RopeByteString.java</include> <include>**/SmallSortedMap.java</include> @@ -199,8 +305,14 @@ </includes> <testIncludes> <testInclude>**/*Lite.java</testInclude> + <testInclude>**/BooleanArrayListTest.java</testInclude> + <testInclude>**/DoubleArrayListTest.java</testInclude> + <testInclude>**/FloatArrayListTest.java</testInclude> + <testInclude>**/IntArrayListTest.java</testInclude> <testInclude>**/LazyMessageLiteTest.java</testInclude> <testInclude>**/LiteTest.java</testInclude> + <testInclude>**/LongArrayListTest.java</testInclude> + <testInclude>**/ProtobufArrayListTest.java</testInclude> <testInclude>**/UnknownFieldSetLiteTest.java</testInclude> </testIncludes> </configuration> diff --git a/java/src/main/java/com/google/protobuf/AbstractMessage.java b/java/src/main/java/com/google/protobuf/AbstractMessage.java index 6de4cae3..cc89173a 100644 --- a/java/src/main/java/com/google/protobuf/AbstractMessage.java +++ b/java/src/main/java/com/google/protobuf/AbstractMessage.java @@ -83,10 +83,10 @@ public abstract class AbstractMessage extends AbstractMessageLite } public void writeTo(final CodedOutputStream output) throws IOException { - MessageReflection.writeMessageTo(this, output, false); + MessageReflection.writeMessageTo(this, getAllFields(), output, false); } - private int memoizedSize = -1; + protected int memoizedSize = -1; public int getSerializedSize() { int size = memoizedSize; @@ -94,7 +94,7 @@ public abstract class AbstractMessage extends AbstractMessageLite return size; } - memoizedSize = MessageReflection.getSerializedSize(this); + memoizedSize = MessageReflection.getSerializedSize(this, getAllFields()); return memoizedSize; } diff --git a/java/src/main/java/com/google/protobuf/AbstractMessageLite.java b/java/src/main/java/com/google/protobuf/AbstractMessageLite.java index aac4fa77..12384983 100644 --- a/java/src/main/java/com/google/protobuf/AbstractMessageLite.java +++ b/java/src/main/java/com/google/protobuf/AbstractMessageLite.java @@ -31,8 +31,8 @@ package com.google.protobuf; import java.io.FilterInputStream; -import java.io.InputStream; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.util.Collection; @@ -109,6 +109,11 @@ public abstract class AbstractMessageLite implements MessageLite { } } + 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 @@ -320,12 +325,15 @@ public abstract class AbstractMessageLite implements MessageLite { * Adds the {@code values} to the {@code list}. This is a helper method * used by generated code. Users should ignore it. * - * @throws NullPointerException if 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}. + * @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. diff --git a/java/src/main/java/com/google/protobuf/AbstractProtobufList.java b/java/src/main/java/com/google/protobuf/AbstractProtobufList.java new file mode 100644 index 00000000..bb6446b2 --- /dev/null +++ b/java/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/src/main/java/com/google/protobuf/BooleanArrayList.java b/java/src/main/java/com/google/protobuf/BooleanArrayList.java new file mode 100644 index 00000000..45492d2f --- /dev/null +++ b/java/src/main/java/com/google/protobuf/BooleanArrayList.java @@ -0,0 +1,244 @@ +// 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}. + */ + BooleanArrayList() { + array = new boolean[DEFAULT_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/src/main/java/com/google/protobuf/BoundedByteString.java b/java/src/main/java/com/google/protobuf/BoundedByteString.java index 8cb6f463..b4c3fb1b 100644 --- a/java/src/main/java/com/google/protobuf/BoundedByteString.java +++ b/java/src/main/java/com/google/protobuf/BoundedByteString.java @@ -30,8 +30,8 @@ package com.google.protobuf; -import java.io.InvalidObjectException; import java.io.IOException; +import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.util.NoSuchElementException; diff --git a/java/src/main/java/com/google/protobuf/ByteString.java b/java/src/main/java/com/google/protobuf/ByteString.java index cff1ee51..1d5d4e8a 100644 --- a/java/src/main/java/com/google/protobuf/ByteString.java +++ b/java/src/main/java/com/google/protobuf/ByteString.java @@ -37,6 +37,8 @@ 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; @@ -259,6 +261,18 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { } /** + * 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}. * @@ -266,11 +280,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { * @return new {@code ByteString} */ public static ByteString copyFromUtf8(String text) { - try { - return new LiteralByteString(text.getBytes("UTF-8")); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("UTF-8 not supported?", e); - } + return new LiteralByteString(text.getBytes(Internal.UTF_8)); } // ================================================================= @@ -504,7 +514,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { /** * Internal (package private) implementation of * {@link #copyTo(byte[],int,int,int)}. - * It assumes that all error checking has already been performed and that + * It assumes that all error checking has already been performed and that * {@code numberToCopy > 0}. */ protected abstract void copyToInternal(byte[] target, int sourceOffset, @@ -543,7 +553,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { * @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. * @@ -569,7 +579,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { if (numberToWrite > 0) { writeToInternal(out, sourceOffset, numberToWrite); } - + } /** @@ -596,7 +606,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { * <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(); @@ -609,8 +619,36 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { * @return new string * @throws UnsupportedEncodingException if charset isn't recognized */ - public abstract String toString(String charsetName) - throws UnsupportedEncodingException; + public 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 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 @@ -621,11 +659,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { * @return new string using UTF-8 encoding */ public String toStringUtf8() { - try { - return toString("UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("UTF-8 not supported?", e); - } + return toString(Internal.UTF_8); } /** @@ -828,7 +862,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { flushLastBuffer(); return ByteString.copyFrom(flushedBuffers); } - + /** * Implement java.util.Arrays.copyOf() for jdk 1.5. */ diff --git a/java/src/main/java/com/google/protobuf/CodedInputStream.java b/java/src/main/java/com/google/protobuf/CodedInputStream.java index 0ca00bab..d201f7c5 100644 --- a/java/src/main/java/com/google/protobuf/CodedInputStream.java +++ b/java/src/main/java/com/google/protobuf/CodedInputStream.java @@ -373,14 +373,14 @@ public final class CodedInputStream { 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, "UTF-8"); + 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), "UTF-8"); + return new String(readRawBytesSlowPath(size), Internal.UTF_8); } } @@ -409,7 +409,7 @@ public final class CodedInputStream { if (!Utf8.isValidUtf8(bytes, pos, pos + size)) { throw InvalidProtocolBufferException.invalidUtf8(); } - return new String(bytes, pos, size, "UTF-8"); + return new String(bytes, pos, size, Internal.UTF_8); } /** Read a {@code group} field value from the stream. */ diff --git a/java/src/main/java/com/google/protobuf/CodedOutputStream.java b/java/src/main/java/com/google/protobuf/CodedOutputStream.java index fafe0356..954fde08 100644 --- a/java/src/main/java/com/google/protobuf/CodedOutputStream.java +++ b/java/src/main/java/com/google/protobuf/CodedOutputStream.java @@ -32,7 +32,6 @@ package com.google.protobuf; import java.io.IOException; import java.io.OutputStream; -import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; /** @@ -144,7 +143,7 @@ public final class CodedOutputStream { int bufferSize) { return newInstance(new ByteBufferOutputStream(byteBuffer), bufferSize); } - + private static class ByteBufferOutputStream extends OutputStream { private final ByteBuffer byteBuffer; public ByteBufferOutputStream(ByteBuffer byteBuffer) { @@ -420,7 +419,7 @@ public final class CodedOutputStream { // 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. - final byte[] bytes = value.getBytes("UTF-8"); + final byte[] bytes = value.getBytes(Internal.UTF_8); writeRawVarint32(bytes.length); writeRawBytes(bytes); } @@ -750,7 +749,7 @@ public final class CodedOutputStream { computeUInt32Size(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber) + computeLazyFieldSize(WireFormat.MESSAGE_SET_MESSAGE, value); } - + // ----------------------------------------------------------------- /** @@ -827,13 +826,9 @@ public final class CodedOutputStream { * {@code string} field. */ public static int computeStringSizeNoTag(final String value) { - try { - final byte[] bytes = value.getBytes("UTF-8"); - return computeRawVarint32Size(bytes.length) + - bytes.length; - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("UTF-8 not supported.", e); - } + final byte[] bytes = value.getBytes(Internal.UTF_8); + return computeRawVarint32Size(bytes.length) + + bytes.length; } /** diff --git a/java/src/main/java/com/google/protobuf/Descriptors.java b/java/src/main/java/com/google/protobuf/Descriptors.java index d65e8b49..3658410c 100644 --- a/java/src/main/java/com/google/protobuf/Descriptors.java +++ b/java/src/main/java/com/google/protobuf/Descriptors.java @@ -43,7 +43,6 @@ import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import java.util.logging.Logger; -import java.io.UnsupportedEncodingException; /** * Contains a collection of classes which describe protocol message types. @@ -256,7 +255,7 @@ public final class Descriptors { throws DescriptorValidationException { return buildFrom(proto, dependencies, false); } - + /** * Construct a {@code FileDescriptor}. @@ -319,12 +318,7 @@ public final class Descriptors { } final byte[] descriptorBytes; - try { - descriptorBytes = descriptorData.toString().getBytes("ISO-8859-1"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException( - "Standard encoding ISO-8859-1 not supported by JVM.", e); - } + descriptorBytes = descriptorData.toString().getBytes(Internal.ISO_8859_1); FileDescriptorProto proto; try { @@ -495,9 +489,9 @@ public final class Descriptors { proto.getExtension(i), this, null, i, true); } } - + /** - * Create a placeholder FileDescriptor for a message Descriptor. + * Create a placeholder FileDescriptor for a message Descriptor. */ FileDescriptor(String packageName, Descriptor message) throws DescriptorValidationException { @@ -561,7 +555,7 @@ public final class Descriptors { extensions[i].setProto(proto.getExtension(i)); } } - + boolean supportsUnknownEnumValue() { return getSyntax() == Syntax.PROTO3; } @@ -650,6 +644,30 @@ public final class Descriptors { 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. @@ -745,7 +763,7 @@ public final class Descriptors { 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); } @@ -897,7 +915,7 @@ public final class Descriptors { return (type == Type.STRING) && (getFile().getOptions().getJavaStringCheckUtf8()); } - boolean isMapField() { + public boolean isMapField() { return getType() == Type.MESSAGE && isRepeated() && getMessageType().getOptions().getMapEntry(); } @@ -923,9 +941,18 @@ public final class Descriptors { return proto.getLabel() == FieldDescriptorProto.Label.LABEL_REPEATED; } - /** Does this field have the {@code [packed = true]} option? */ + /** 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() { - return getOptions().getPacked(); + 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? */ @@ -1333,8 +1360,8 @@ public final class Descriptors { "Message type had default value."); } } catch (NumberFormatException e) { - throw new DescriptorValidationException(this, - "Could not parse default value: \"" + + throw new DescriptorValidationException(this, + "Could not parse default value: \"" + proto.getDefaultValue() + '\"', e); } } else { @@ -1456,7 +1483,7 @@ public final class Descriptors { return file.pool.enumValuesByNumber.get( new DescriptorPool.DescriptorIntPair(this, number)); } - + /** * Get the enum value for a number. If no enum value has this number, * construct an EnumValueDescriptor for it. @@ -1494,7 +1521,7 @@ public final class Descriptors { // 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); @@ -1509,7 +1536,7 @@ public final class Descriptors { } return result; } - + // Used in tests only. int getUnknownEnumValueDescriptorCount() { return unknownValues.size(); @@ -1585,7 +1612,7 @@ public final class Descriptors { /** Get the value's number. */ public int getNumber() { return proto.getNumber(); } - + @Override public String toString() { return proto.getName(); } @@ -1627,7 +1654,7 @@ public final class Descriptors { file.pool.addSymbol(this); file.pool.addEnumValueByNumber(this); } - + private Integer number; // Create an unknown enum value. private EnumValueDescriptor( @@ -1643,7 +1670,7 @@ public final class Descriptors { this.type = parent; this.fullName = parent.getFullName() + '.' + proto.getName(); this.number = number; - + // Don't add this descriptor into pool. } @@ -1930,13 +1957,13 @@ public final class Descriptors { * descriptors defined in a particular file. */ private static final class DescriptorPool { - - /** Defines what subclass of descriptors to search in the descriptor pool. + + /** 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>(); @@ -1982,9 +2009,9 @@ public final class Descriptors { 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. + + /** 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) { @@ -2013,18 +2040,18 @@ public final class Descriptors { /** Checks if the descriptor is a valid type for a message field. */ boolean isType(GenericDescriptor descriptor) { - return (descriptor instanceof 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) || + 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 '.'), @@ -2082,7 +2109,7 @@ public final class Descriptors { // Append firstPart and try to find scopeToTry.append(firstPart); - result = findSymbol(scopeToTry.toString(), + result = findSymbol(scopeToTry.toString(), DescriptorPool.SearchFilter.AGGREGATES_ONLY); if (result != null) { @@ -2323,6 +2350,11 @@ public final class Descriptors { 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]; } diff --git a/java/src/main/java/com/google/protobuf/DoubleArrayList.java b/java/src/main/java/com/google/protobuf/DoubleArrayList.java new file mode 100644 index 00000000..90ebe109 --- /dev/null +++ b/java/src/main/java/com/google/protobuf/DoubleArrayList.java @@ -0,0 +1,243 @@ +// 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}. + */ + DoubleArrayList() { + array = new double[DEFAULT_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/src/main/java/com/google/protobuf/DynamicMessage.java b/java/src/main/java/com/google/protobuf/DynamicMessage.java index 9c5e6c61..3ea1b688 100644 --- a/java/src/main/java/com/google/protobuf/DynamicMessage.java +++ b/java/src/main/java/com/google/protobuf/DynamicMessage.java @@ -35,8 +35,8 @@ import com.google.protobuf.Descriptors.EnumValueDescriptor; import com.google.protobuf.Descriptors.FieldDescriptor; import com.google.protobuf.Descriptors.OneofDescriptor; -import java.io.InputStream; import java.io.IOException; +import java.io.InputStream; import java.util.Collections; import java.util.List; import java.util.Map; @@ -600,9 +600,12 @@ public final class DynamicMessage extends AbstractMessage { } // TODO(xiaofeng): Re-enable this check after Orgstore is fixed to not // set incorrect EnumValueDescriptors. - // if (field.getEnumType() != ((EnumValueDescriptor) value).getType()) { - // throw new IllegalArgumentException( - // "EnumValueDescriptor doesn't match Enum Field."); + // 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())); // } } diff --git a/java/src/main/java/com/google/protobuf/FieldSet.java b/java/src/main/java/com/google/protobuf/FieldSet.java index ff9b5bc0..47924b65 100644 --- a/java/src/main/java/com/google/protobuf/FieldSet.java +++ b/java/src/main/java/com/google/protobuf/FieldSet.java @@ -553,42 +553,13 @@ final class FieldSet<FieldDescriptorType extends CodedInputStream input, final WireFormat.FieldType type, boolean checkUtf8) throws IOException { - switch (type) { - case DOUBLE : return input.readDouble (); - case FLOAT : return input.readFloat (); - case INT64 : return input.readInt64 (); - case UINT64 : return input.readUInt64 (); - case INT32 : return input.readInt32 (); - case FIXED64 : return input.readFixed64 (); - case FIXED32 : return input.readFixed32 (); - case BOOL : return input.readBool (); - case STRING : if (checkUtf8) { - return input.readStringRequireUtf8(); - } else { - return input.readString(); - } - case BYTES : return input.readBytes (); - case UINT32 : return input.readUInt32 (); - case SFIXED32: return input.readSFixed32(); - case SFIXED64: return input.readSFixed64(); - case SINT32 : return input.readSInt32 (); - case SINT64 : return input.readSInt64 (); - - case GROUP: - throw new IllegalArgumentException( - "readPrimitiveField() cannot handle nested groups."); - case MESSAGE: - throw new IllegalArgumentException( - "readPrimitiveField() cannot handle embedded messages."); - case ENUM: - // We don't handle enums because we don't know what to do if the - // value is not recognized. - throw new IllegalArgumentException( - "readPrimitiveField() cannot handle enums."); + if (checkUtf8) { + return WireFormat.readPrimitiveField(input, type, + WireFormat.Utf8Validation.STRICT); + } else { + return WireFormat.readPrimitiveField(input, type, + WireFormat.Utf8Validation.LOOSE); } - - throw new RuntimeException( - "There is no way to get here, but the compiler thinks otherwise."); } @@ -685,9 +656,15 @@ final class FieldSet<FieldDescriptorType extends case FIXED64 : output.writeFixed64NoTag ((Long ) value); break; case FIXED32 : output.writeFixed32NoTag ((Integer ) value); break; case BOOL : output.writeBoolNoTag ((Boolean ) value); break; - case STRING : output.writeStringNoTag ((String ) value); break; case GROUP : output.writeGroupNoTag ((MessageLite) value); break; case MESSAGE : output.writeMessageNoTag ((MessageLite) value); break; + case 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); @@ -843,7 +820,6 @@ final class FieldSet<FieldDescriptorType extends case FIXED64 : return CodedOutputStream.computeFixed64SizeNoTag ((Long )value); case FIXED32 : return CodedOutputStream.computeFixed32SizeNoTag ((Integer )value); case BOOL : return CodedOutputStream.computeBoolSizeNoTag ((Boolean )value); - case STRING : return CodedOutputStream.computeStringSizeNoTag ((String )value); case GROUP : return CodedOutputStream.computeGroupSizeNoTag ((MessageLite)value); case BYTES : if (value instanceof ByteString) { @@ -851,6 +827,12 @@ final class FieldSet<FieldDescriptorType extends } 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); diff --git a/java/src/main/java/com/google/protobuf/FloatArrayList.java b/java/src/main/java/com/google/protobuf/FloatArrayList.java new file mode 100644 index 00000000..293eaff6 --- /dev/null +++ b/java/src/main/java/com/google/protobuf/FloatArrayList.java @@ -0,0 +1,242 @@ +// 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}. + */ + FloatArrayList() { + array = new float[DEFAULT_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/src/main/java/com/google/protobuf/GeneratedMessage.java b/java/src/main/java/com/google/protobuf/GeneratedMessage.java index 156d1633..9457d999 100644 --- a/java/src/main/java/com/google/protobuf/GeneratedMessage.java +++ b/java/src/main/java/com/google/protobuf/GeneratedMessage.java @@ -73,7 +73,7 @@ public abstract class GeneratedMessage extends AbstractMessage /** For use by generated code only. */ protected UnknownFieldSet unknownFields; - + protected GeneratedMessage() { unknownFields = UnknownFieldSet.getDefaultInstance(); } @@ -109,8 +109,15 @@ public abstract class GeneratedMessage extends AbstractMessage return internalGetFieldAccessorTable().descriptor; } - /** Internal helper which returns a mutable map. */ - private Map<FieldDescriptor, Object> getAllFieldsMutable() { + /** + * 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; @@ -122,7 +129,12 @@ public abstract class GeneratedMessage extends AbstractMessage } } else { if (hasField(field)) { - result.put(field, getField(field)); + if (getBytesForString + && field.getJavaType() == FieldDescriptor.JavaType.STRING) { + result.put(field, getFieldRaw(field)); + } else { + result.put(field, getField(field)); + } } } } @@ -161,7 +173,23 @@ public abstract class GeneratedMessage extends AbstractMessage //@Override (Java 1.6 override semantics, but we must support 1.5) public Map<FieldDescriptor, Object> getAllFields() { - return Collections.unmodifiableMap(getAllFieldsMutable()); + 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) @@ -184,6 +212,18 @@ public abstract class GeneratedMessage extends AbstractMessage 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) @@ -214,6 +254,24 @@ public abstract class GeneratedMessage extends AbstractMessage 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. @@ -549,12 +607,12 @@ public abstract class GeneratedMessage extends AbstractMessage * Gets the map field with the given field number. This method should be * overridden in the generated message class if the message contains map * fields. - * + * * Unlike other field types, reflection support for map fields can't be * implemented based on generated public API because we need to access a * map field as a list in reflection API but the generated API only allows * us to access it as a map. This method returns the underlying map field - * directly and thus enables us to access the map field as a list. + * directly and thus enables us to access the map field as a list. */ @SuppressWarnings({"unused", "rawtypes"}) protected MapField internalGetMapField(int fieldNumber) { @@ -563,6 +621,15 @@ public abstract class GeneratedMessage extends AbstractMessage 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()); + } } // ================================================================= @@ -683,7 +750,7 @@ public abstract class GeneratedMessage extends AbstractMessage 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); @@ -825,7 +892,16 @@ public abstract class GeneratedMessage extends AbstractMessage @Override public Map<FieldDescriptor, Object> getAllFields() { - final Map<FieldDescriptor, Object> result = super.getAllFieldsMutable(); + 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); } @@ -1313,7 +1389,7 @@ public abstract class GeneratedMessage extends AbstractMessage implements ExtensionDescriptorRetriever { private volatile FieldDescriptor descriptor; protected abstract FieldDescriptor loadDescriptor(); - + public FieldDescriptor getDescriptor() { if (descriptor == null) { synchronized (this) { @@ -1651,17 +1727,17 @@ public abstract class GeneratedMessage extends AbstractMessage } } } - + /** * Gets the map field with the given field number. This method should be * overridden in the generated message class if the message contains map * fields. - * + * * Unlike other field types, reflection support for map fields can't be * implemented based on generated public API because we need to access a * map field as a list in reflection API but the generated API only allows * us to access it as a map. This method returns the underlying map field - * directly and thus enables us to access the map field as a list. + * directly and thus enables us to access the map field as a list. */ @SuppressWarnings({"rawtypes", "unused"}) protected MapField internalGetMapField(int fieldNumber) { @@ -1709,7 +1785,7 @@ public abstract class GeneratedMessage extends AbstractMessage oneofs = new OneofAccessor[descriptor.getOneofs().size()]; initialized = false; } - + private boolean isMapFieldEnabled(FieldDescriptor field) { boolean result = true; return result; @@ -1761,6 +1837,10 @@ public abstract class GeneratedMessage extends AbstractMessage 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, @@ -1817,9 +1897,13 @@ public abstract class GeneratedMessage extends AbstractMessage 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); @@ -1934,11 +2018,11 @@ public abstract class GeneratedMessage extends AbstractMessage 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(); } @@ -1949,6 +2033,12 @@ public abstract class GeneratedMessage extends AbstractMessage 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); } @@ -1957,12 +2047,22 @@ public abstract class GeneratedMessage extends AbstractMessage 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 void setRepeated(final Builder builder, - final int index, final Object value) { + 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."); } @@ -2058,6 +2158,12 @@ public abstract class GeneratedMessage extends AbstractMessage 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. @@ -2075,6 +2181,13 @@ public abstract class GeneratedMessage extends AbstractMessage 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); @@ -2130,15 +2243,21 @@ public abstract class GeneratedMessage extends AbstractMessage 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++) { @@ -2155,6 +2274,14 @@ public abstract class GeneratedMessage extends AbstractMessage 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) { @@ -2170,12 +2297,20 @@ public abstract class GeneratedMessage extends AbstractMessage 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) { - getMapField(builder).getMutableList().set(index, (Message) value); + getMutableMapField(builder).getMutableList().set(index, (Message) value); } public void addRepeated(Builder builder, Object value) { - getMapField(builder).getMutableList().add((Message) value); + getMutableMapField(builder).getMutableList().add((Message) value); } public boolean has(GeneratedMessage message) { @@ -2197,7 +2332,7 @@ public abstract class GeneratedMessage extends AbstractMessage } public void clear(Builder builder) { - getMapField(builder).getMutableList().clear(); + getMutableMapField(builder).getMutableList().clear(); } public com.google.protobuf.Message.Builder newBuilder() { @@ -2208,7 +2343,7 @@ public abstract class GeneratedMessage extends AbstractMessage throw new UnsupportedOperationException( "Nested builder not supported for map fields."); } - + public com.google.protobuf.Message.Builder getRepeatedBuilder( Builder builder, int index) { throw new UnsupportedOperationException( @@ -2226,7 +2361,7 @@ public abstract class GeneratedMessage extends AbstractMessage final Class<? extends Builder> builderClass, final String containingOneofCamelCaseName) { super(descriptor, camelCaseName, messageClass, builderClass, containingOneofCamelCaseName); - + enumDescriptor = descriptor.getEnumType(); valueOfMethod = getMethodOrDie(type, "valueOf", @@ -2244,12 +2379,12 @@ public abstract class GeneratedMessage extends AbstractMessage 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; @@ -2291,7 +2426,7 @@ public abstract class GeneratedMessage extends AbstractMessage final Class<? extends GeneratedMessage> messageClass, final Class<? extends Builder> builderClass) { super(descriptor, camelCaseName, messageClass, builderClass); - + enumDescriptor = descriptor.getEnumType(); valueOfMethod = getMethodOrDie(type, "valueOf", @@ -2315,7 +2450,7 @@ public abstract class GeneratedMessage extends AbstractMessage private final Method valueOfMethod; private final Method getValueDescriptorMethod; - + private boolean supportUnknownEnumValue; private Method getRepeatedValueMethod; private Method getRepeatedValueMethodBuilder; @@ -2388,6 +2523,60 @@ public abstract class GeneratedMessage extends AbstractMessage // --------------------------------------------------------------- + /** + * 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( @@ -2395,7 +2584,8 @@ public abstract class GeneratedMessage extends AbstractMessage final Class<? extends GeneratedMessage> messageClass, final Class<? extends Builder> builderClass, final String containingOneofCamelCaseName) { - super(descriptor, camelCaseName, messageClass, builderClass, containingOneofCamelCaseName); + super(descriptor, camelCaseName, messageClass, builderClass, + containingOneofCamelCaseName); newBuilderMethod = getMethodOrDie(type, "newBuilder"); getBuilderMethodBuilder = @@ -2492,7 +2682,7 @@ public abstract class GeneratedMessage extends AbstractMessage protected Object writeReplace() throws ObjectStreamException { return new GeneratedMessageLite.SerializedForm(this); } - + /** * Checks that the {@link Extension} is non-Lite and returns it as a * {@link GeneratedExtension}. @@ -2503,7 +2693,7 @@ public abstract class GeneratedMessage extends AbstractMessage if (extension.isLite()) { throw new IllegalArgumentException("Expected non-lite extension."); } - + return (Extension<MessageType, T>) extension; } } diff --git a/java/src/main/java/com/google/protobuf/GeneratedMessageLite.java b/java/src/main/java/com/google/protobuf/GeneratedMessageLite.java index 4d25c077..bd6bc463 100644 --- a/java/src/main/java/com/google/protobuf/GeneratedMessageLite.java +++ b/java/src/main/java/com/google/protobuf/GeneratedMessageLite.java @@ -30,6 +30,12 @@ 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; @@ -42,22 +48,62 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** * Lite version of {@link GeneratedMessage}. * * @author kenton@google.com Kenton Varda */ -public abstract class GeneratedMessageLite extends AbstractMessageLite - implements Serializable { +public abstract class GeneratedMessageLite< + MessageType extends GeneratedMessageLite<MessageType, BuilderType>, + BuilderType extends GeneratedMessageLite.Builder<MessageType, BuilderType>> + extends AbstractMessageLite + implements Serializable { + + /** + * Holds all the {@link PrototypeHolder}s for loaded classes. + */ + // TODO(dweis): Consider different concurrency values. + // TODO(dweis): This will prevent garbage collection of the class loader. + // Ideally we'd use something like ClassValue but that's Java 7 only. + private static final Map<Class<?>, PrototypeHolder<?, ?>> PROTOTYPE_MAP = + new ConcurrentHashMap<Class<?>, PrototypeHolder<?, ?>>(); + + // For use by generated code only. + protected static < + MessageType extends GeneratedMessageLite<MessageType, BuilderType>, + BuilderType extends GeneratedMessageLite.Builder< + MessageType, BuilderType>> void onLoad(Class<MessageType> clazz, + PrototypeHolder<MessageType, BuilderType> protoTypeHolder) { + PROTOTYPE_MAP.put(clazz, protoTypeHolder); + } + private static final long serialVersionUID = 1L; /** For use by generated code only. */ - protected UnknownFieldSetLite unknownFields; + protected UnknownFieldSetLite unknownFields = + UnknownFieldSetLite.getDefaultInstance(); - public Parser<? extends MessageLite> getParserForType() { - throw new UnsupportedOperationException( - "This is supposed to be overridden by subclasses."); + /** For use by generated code only. */ + protected int memoizedSerializedSize = -1; + + @SuppressWarnings("unchecked") // Guaranteed by runtime. + public final Parser<MessageType> getParserForType() { + return (Parser<MessageType>) PROTOTYPE_MAP + .get(getClass()).getParserForType(); + } + + @SuppressWarnings("unchecked") // Guaranteed by runtime. + public final MessageType getDefaultInstanceForType() { + return (MessageType) PROTOTYPE_MAP + .get(getClass()).getDefaultInstanceForType(); + } + + @SuppressWarnings("unchecked") // Guaranteed by runtime. + public final BuilderType newBuilderForType() { + return (BuilderType) PROTOTYPE_MAP + .get(getClass()).newBuilderForType(); } /** @@ -73,24 +119,107 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite return unknownFields.mergeFieldFrom(tag, input); } + 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; + } + + /** + * 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... args); + + /** + * 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.concat(this.unknownFields, unknownFields); + } + @SuppressWarnings("unchecked") - public abstract static class Builder<MessageType extends GeneratedMessageLite, - BuilderType extends Builder> - extends AbstractMessageLite.Builder<BuilderType> { + public abstract static class Builder< + MessageType extends GeneratedMessageLite<MessageType, BuilderType>, + BuilderType extends Builder<MessageType, BuilderType>> + extends AbstractMessageLite.Builder<BuilderType> { private final MessageType defaultInstance; - - /** For use by generated code only. */ - protected UnknownFieldSetLite unknownFields = - UnknownFieldSetLite.getDefaultInstance(); + 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 BuilderType clear() { - unknownFields = UnknownFieldSetLite.getDefaultInstance(); + 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; } @@ -102,8 +231,12 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite return builder; } - /** All subclasses implement this. */ - public abstract MessageType buildPartial(); + //@Override (Java 1.6 override semantics, but we must support 1.5) + public MessageType buildPartial() { + instance.dynamicMethod(MethodToInvoke.MAKE_IMMUTABLE); + isBuilt = true; + return instance; + } //@Override (Java 1.6 override semantics, but we must support 1.5) public final MessageType build() { @@ -113,9 +246,13 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite } return result; } - + /** All subclasses implement this. */ - public abstract BuilderType mergeFrom(MessageType message); + public BuilderType mergeFrom(MessageType message) { + copyOnWrite(); + instance.dynamicMethod(MethodToInvoke.MERGE_FROM, message); + return (BuilderType) this; + } public MessageType getDefaultInstanceForType() { return defaultInstance; @@ -132,18 +269,6 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite int tag) throws IOException { return unknownFields.mergeFieldFrom(tag, input); } - - /** - * Merge some unknown fields into the {@link UnknownFieldSetLite} for this - * message. - * - * <p>For use by generated code only. - */ - protected final BuilderType mergeUnknownFields( - final UnknownFieldSetLite unknownFields) { - this.unknownFields = UnknownFieldSetLite.concat(this.unknownFields, unknownFields); - return (BuilderType) this; - } public BuilderType mergeFrom( com.google.protobuf.CodedInputStream input, @@ -174,7 +299,9 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite * Lite equivalent of {@link com.google.protobuf.GeneratedMessage.ExtendableMessageOrBuilder}. */ public interface ExtendableMessageOrBuilder< - MessageType extends ExtendableMessage> extends MessageLiteOrBuilder { + MessageType extends ExtendableMessage<MessageType, BuilderType>, + BuilderType extends ExtendableBuilder<MessageType, BuilderType>> + extends MessageLiteOrBuilder { /** Check if a singular extension is present. */ <Type> boolean hasExtension( @@ -197,15 +324,23 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite * Lite equivalent of {@link GeneratedMessage.ExtendableMessage}. */ public abstract static class ExtendableMessage< - MessageType extends ExtendableMessage<MessageType>> - extends GeneratedMessageLite - implements ExtendableMessageOrBuilder<MessageType> { + 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); + } private void verifyExtensionContainingType( final GeneratedExtension<MessageType, ?> extension) { @@ -349,46 +484,44 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite */ @SuppressWarnings("unchecked") public abstract static class ExtendableBuilder< - MessageType extends ExtendableMessage<MessageType>, + MessageType extends ExtendableMessage<MessageType, BuilderType>, BuilderType extends ExtendableBuilder<MessageType, BuilderType>> extends Builder<MessageType, BuilderType> - implements ExtendableMessageOrBuilder<MessageType> { + 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(); } - private FieldSet<ExtensionDescriptor> extensions = FieldSet.emptySet(); - private boolean extensionsIsMutable; - // For immutable message conversion. void internalSetExtensionSet(FieldSet<ExtensionDescriptor> extensions) { - this.extensions = extensions; + copyOnWrite(); + instance.extensions = extensions; } - @Override - public BuilderType clear() { - extensions.clear(); - extensionsIsMutable = false; - return super.clear(); + // @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(); } - private void ensureExtensionsIsMutable() { - if (!extensionsIsMutable) { - extensions = extensions.clone(); - extensionsIsMutable = true; + // @Override (Java 1.6 override semantics, but we must support 1.5) + public final MessageType buildPartial() { + if (isBuilt) { + return instance; } - } - /** - * Called by the build code path to create a copy of the extensions for - * building the message. - * <p> - * For use by generated code only. - */ - protected final FieldSet<ExtensionDescriptor> buildExtensions() { - extensions.makeImmutable(); - extensionsIsMutable = false; - return extensions; + instance.extensions.makeImmutable(); + return super.buildPartial(); } private void verifyExtensionContainingType( @@ -406,22 +539,14 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite //@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); + 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) { - GeneratedExtension<MessageType, List<Type>> extensionLite = - checkIsLite(extension); - - verifyExtensionContainingType(extensionLite); - return extensions.getRepeatedFieldCount(extensionLite.descriptor); + return instance.getExtensionCount(extension); } /** Get the value of an extension. */ @@ -429,16 +554,7 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite @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); - } + return instance.getExtension(extension); } /** Get one element of a repeated extension. */ @@ -447,12 +563,7 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite 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)); + return instance.getExtension(extension, index); } // This is implemented here only to work around an apparent bug in the @@ -471,9 +582,8 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite checkIsLite(extension); verifyExtensionContainingType(extensionLite); - ensureExtensionsIsMutable(); - extensions.setField(extensionLite.descriptor, - extensionLite.toFieldSetType(value)); + copyOnWrite(); + instance.extensions.setField(extensionLite.descriptor, extensionLite.toFieldSetType(value)); return (BuilderType) this; } @@ -485,9 +595,9 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite checkIsLite(extension); verifyExtensionContainingType(extensionLite); - ensureExtensionsIsMutable(); - extensions.setRepeatedField(extensionLite.descriptor, index, - extensionLite.singularToFieldSetType(value)); + copyOnWrite(); + instance.extensions.setRepeatedField( + extensionLite.descriptor, index, extensionLite.singularToFieldSetType(value)); return (BuilderType) this; } @@ -499,9 +609,9 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite checkIsLite(extension); verifyExtensionContainingType(extensionLite); - ensureExtensionsIsMutable(); - extensions.addRepeatedField(extensionLite.descriptor, - extensionLite.singularToFieldSetType(value)); + copyOnWrite(); + instance.extensions.addRepeatedField( + extensionLite.descriptor, extensionLite.singularToFieldSetType(value)); return (BuilderType) this; } @@ -511,20 +621,10 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite GeneratedExtension<MessageType, ?> extensionLite = checkIsLite(extension); verifyExtensionContainingType(extensionLite); - ensureExtensionsIsMutable(); - extensions.clearField(extensionLite.descriptor); + copyOnWrite(); + instance.extensions.clearField(extensionLite.descriptor); return (BuilderType) this; } - - /** Called by subclasses to check if all extensions are initialized. */ - protected boolean extensionsAreInitialized() { - return extensions.isInitialized(); - } - - protected final void mergeExtensionFields(final MessageType other) { - ensureExtensionsIsMutable(); - extensions.mergeFrom(((ExtendableMessage) other).extensions); - } } //----------------------------------------------------------------- @@ -991,7 +1091,10 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite * Checks that the {@link Extension} is Lite and returns it as a * {@link GeneratedExtension}. */ - private static <MessageType extends ExtendableMessage<MessageType>, T> + private static < + MessageType extends ExtendableMessage<MessageType, BuilderType>, + BuilderType extends ExtendableBuilder<MessageType, BuilderType>, + T> GeneratedExtension<MessageType, T> checkIsLite( ExtensionLite<MessageType, T> extension) { if (!extension.isLite()) { @@ -1000,4 +1103,172 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite return (GeneratedExtension<MessageType, T>) extension; } + + /** + * Represents the state needed to implement *ForType methods. Generated code + * must provide a static singleton instance by adding it with + * {@link GeneratedMessageLite#onLoad(Class, PrototypeHolder)} on class load. + * <ul> + * <li>{@link #getDefaultInstanceForType()} + * <li>{@link #getParserForType()} + * <li>{@link #newBuilderForType()} + * </ul> + * This allows us to trade three generated methods for a static Map. + */ + protected static class PrototypeHolder< + MessageType extends GeneratedMessageLite<MessageType, BuilderType>, + BuilderType extends GeneratedMessageLite.Builder< + MessageType, BuilderType>> { + + private final MessageType defaultInstance; + private final Parser<MessageType> parser; + + public PrototypeHolder( + MessageType defaultInstance, Parser<MessageType> parser) { + this.defaultInstance = defaultInstance; + this.parser = parser; + } + + public MessageType getDefaultInstanceForType() { + return defaultInstance; + } + + public Parser<MessageType> getParserForType() { + return parser; + } + + @SuppressWarnings("unchecked") // Guaranteed by runtime. + public BuilderType newBuilderForType() { + return (BuilderType) defaultInstance.toBuilder(); + } + } + + /** + * 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 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 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 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 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 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> emptyProtobufList() { + return ProtobufArrayList.emptyList(); + } + + protected static LazyStringArrayList emptyLazyStringArrayList() { + return LazyStringArrayList.emptyList(); + } } diff --git a/java/src/main/java/com/google/protobuf/IntArrayList.java b/java/src/main/java/com/google/protobuf/IntArrayList.java new file mode 100644 index 00000000..f7609cc9 --- /dev/null +++ b/java/src/main/java/com/google/protobuf/IntArrayList.java @@ -0,0 +1,242 @@ +// 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}. + */ + IntArrayList() { + array = new int[DEFAULT_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/src/main/java/com/google/protobuf/Internal.java b/java/src/main/java/com/google/protobuf/Internal.java index 5a0de6d1..20054b79 100644 --- a/java/src/main/java/com/google/protobuf/Internal.java +++ b/java/src/main/java/com/google/protobuf/Internal.java @@ -31,8 +31,8 @@ package com.google.protobuf; import java.io.IOException; -import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; +import java.nio.charset.Charset; import java.util.AbstractList; import java.util.AbstractMap; import java.util.AbstractSet; @@ -51,6 +51,10 @@ import java.util.Set; * @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. @@ -80,14 +84,7 @@ public class Internal { * generated code calls this automatically. */ public static String stringDefaultValue(String bytes) { - try { - return new String(bytes.getBytes("ISO-8859-1"), "UTF-8"); - } catch (UnsupportedEncodingException e) { - // This should never happen since all JVMs are required to implement - // both of the above character sets. - throw new IllegalStateException( - "Java VM does not support a standard character set.", e); - } + return new String(bytes.getBytes(ISO_8859_1), UTF_8); } /** @@ -99,37 +96,23 @@ public class Internal { * embed raw bytes as a string literal with ISO-8859-1 encoding. */ public static ByteString bytesDefaultValue(String bytes) { - try { - return ByteString.copyFrom(bytes.getBytes("ISO-8859-1")); - } catch (UnsupportedEncodingException e) { - // This should never happen since all JVMs are required to implement - // ISO-8859-1. - throw new IllegalStateException( - "Java VM does not support a standard character set.", e); - } + 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. + * This is like {@link #bytesDefaultValue}, but returns a byte array. */ public static byte[] byteArrayDefaultValue(String bytes) { - try { - return bytes.getBytes("ISO-8859-1"); - } catch (UnsupportedEncodingException e) { - // This should never happen since all JVMs are required to implement - // ISO-8859-1. - throw new IllegalStateException( - "Java VM does not support a standard character set.", e); - } + 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. + * This is like {@link #bytesDefaultValue}, but returns a ByteBuffer. */ public static ByteBuffer byteBufferDefaultValue(String bytes) { return ByteBuffer.wrap(byteArrayDefaultValue(bytes)); @@ -185,7 +168,7 @@ public class Internal { public static boolean isValidUtf8(ByteString byteString) { return byteString.isValidUtf8(); } - + /** * Like {@link #isValidUtf8(ByteString)} but for byte arrays. */ @@ -197,22 +180,14 @@ public class Internal { * Helper method to get the UTF-8 bytes of a string. */ public static byte[] toByteArray(String value) { - try { - return value.getBytes("UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("UTF-8 not supported?", e); - } + 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) { - try { - return new String(bytes, "UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("UTF-8 not supported?", e); - } + return new String(bytes, UTF_8); } /** @@ -274,7 +249,7 @@ public class Internal { } return hash; } - + /** * Helper method for implementing {@link Message#equals(Object)} for bytes field. */ @@ -298,7 +273,7 @@ public class Internal { } return hash; } - + /** * Helper method for implementing {@link Message#hashCode()} for bytes field. */ @@ -309,7 +284,7 @@ public class Internal { // based hashCode() method. return LiteralByteString.hashCode(bytes); } - + /** * Helper method for implementing {@link Message#equals(Object)} for bytes * field. @@ -322,7 +297,7 @@ public class Internal { // compare all the content. return a.duplicate().clear().equals(b.duplicate().clear()); } - + /** * Helper method for implementing {@link Message#equals(Object)} for bytes * field. @@ -351,9 +326,9 @@ public class Internal { } return hash; } - + private static final int DEFAULT_BUFFER_SIZE = 4096; - + /** * Helper method for implementing {@link Message#hashCode()} for bytes * field. @@ -382,18 +357,18 @@ public class Internal { return h == 0 ? 1 : h; } } - + /** * 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); @@ -458,13 +433,13 @@ public class Internal { 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) { @@ -474,7 +449,7 @@ public class Internal { } return valueConverter.doForward(result); } - + @Override public V put(K key, V value) { RealValue oldValue = realMap.put(key, valueConverter.doBackward(value)); @@ -488,13 +463,13 @@ public class Internal { 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()); @@ -503,17 +478,17 @@ public class Internal { @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(); @@ -529,14 +504,14 @@ public class Internal { 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(); @@ -558,4 +533,132 @@ public class Internal { } } } + + /** + * 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(Integer)} but more efficient in that it doesn't box the element. + */ + void addInt(int element); + + /** + * Like {@link #set(int, Integer)} 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(Boolean)} but more efficient in that it doesn't box the element. + */ + void addBoolean(boolean element); + + /** + * Like {@link #set(int, Boolean)} 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(Long)} but more efficient in that it doesn't box the element. + */ + void addLong(long element); + + /** + * Like {@link #set(int, Long)} 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(Double)} but more efficient in that it doesn't box the element. + */ + void addDouble(double element); + + /** + * Like {@link #set(int, Double)} 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(Float)} but more efficient in that it doesn't box the element. + */ + void addFloat(float element); + + /** + * Like {@link #set(int, Float)} but more efficient in that it doesn't box the element. + */ + float setFloat(int index, float element); + } } diff --git a/java/src/main/java/com/google/protobuf/LazyFieldLite.java b/java/src/main/java/com/google/protobuf/LazyFieldLite.java index 1fc80e87..eea1fe3c 100644 --- a/java/src/main/java/com/google/protobuf/LazyFieldLite.java +++ b/java/src/main/java/com/google/protobuf/LazyFieldLite.java @@ -30,8 +30,6 @@ package com.google.protobuf; -import java.io.IOException; - /** * LazyFieldLite encapsulates the logic of lazily parsing message fields. It stores * the message in a ByteString initially and then parse it on-demand. @@ -44,40 +42,102 @@ import java.io.IOException; * @author xiangl@google.com (Xiang Li) */ public class LazyFieldLite { - private ByteString bytes; + 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; - private volatile boolean isDirty = false; + /** + * 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.bytes = bytes; + 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 value == null && bytes == null; + 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() { - bytes = null; + // 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; - extensionRegistry = null; - isDirty = true; + 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. At first time, serialized data is parsed by - * {@code defaultInstance.getParserForType()}. + * 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. @@ -88,38 +148,109 @@ public class LazyFieldLite { } /** - * LazyField is not thread-safe for write access. Synchronizations are needed + * 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; - bytes = null; - isDirty = true; return originalValue; } - public void merge(LazyFieldLite value) { - if (value.containsDefaultInstance()) { + /** + * 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 (bytes == null) { - this.bytes = value.bytes; + 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 { - this.bytes.concat(value.toByteString()); + // All extensions from the other message will be dropped because we have no registry. + setValue(mergeValueAndBytes(this.value, other.toByteString(), EMPTY_REGISTRY)); + return; } - isDirty = false; } - public void setByteString(ByteString bytes, ExtensionRegistryLite extensionRegistry) { - this.bytes = bytes; - this.extensionRegistry = extensionRegistry; - isDirty = false; + 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; + } } - public ExtensionRegistryLite getExtensionRegistry() { - return extensionRegistry; + /** + * 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; } /** @@ -128,30 +259,43 @@ public class LazyFieldLite { * parsed. Be careful when using this method. */ public int getSerializedSize() { - if (isDirty) { + if (delayedBytes != null) { + return delayedBytes.size(); + } else if (memoizedBytes != null) { + return memoizedBytes.size(); + } else if (value != null) { return value.getSerializedSize(); + } else { + return 0; } - return bytes.size(); } + /** + * Returns a BytesString for this field in a thread-safe way. + */ public ByteString toByteString() { - if (!isDirty) { - return bytes; + if (delayedBytes != null) { + return delayedBytes; + } + if (memoizedBytes != null) { + return memoizedBytes; } synchronized (this) { - if (!isDirty) { - return bytes; + if (memoizedBytes != null) { + return memoizedBytes; } if (value == null) { - bytes = ByteString.EMPTY; + memoizedBytes = ByteString.EMPTY; } else { - bytes = value.toByteString(); + memoizedBytes = value.toByteString(); } - isDirty = false; - return bytes; + return memoizedBytes; } } + /** + * Might lazily parse the bytes that were previously passed in. Is thread-safe. + */ protected void ensureInitialized(MessageLite defaultInstance) { if (value != null) { return; @@ -161,16 +305,35 @@ public class LazyFieldLite { return; } try { - if (bytes != null) { - value = defaultInstance.getParserForType() - .parseFrom(bytes, extensionRegistry); + 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 { - value = defaultInstance; + this.value = defaultInstance; + this.memoizedBytes = ByteString.EMPTY; + this.delayedBytes = null; } - } catch (IOException e) { - // TODO(xiangl): Refactory the API to support the exception thrown from - // lazily load messages. + } 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/src/main/java/com/google/protobuf/LazyStringArrayList.java b/java/src/main/java/com/google/protobuf/LazyStringArrayList.java index 2d40a51f..a2997e1c 100644 --- a/java/src/main/java/com/google/protobuf/LazyStringArrayList.java +++ b/java/src/main/java/com/google/protobuf/LazyStringArrayList.java @@ -62,11 +62,20 @@ import java.util.RandomAccess; * * @author jonp@google.com (Jon Perlow) */ -public class LazyStringArrayList extends AbstractList<String> +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; + } - public static final LazyStringList EMPTY = - new LazyStringArrayList().getUnmodifiableView(); + // For compatibility with older runtimes. + public static final LazyStringList EMPTY = EMPTY_LIST; private final List<Object> list; @@ -116,12 +125,26 @@ public class LazyStringArrayList extends AbstractList<String> @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++; } @@ -137,6 +160,7 @@ public class LazyStringArrayList extends AbstractList<String> @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 @@ -148,6 +172,7 @@ public class LazyStringArrayList extends AbstractList<String> // @Override public boolean addAllByteString(Collection<? extends ByteString> values) { + ensureIsMutable(); boolean ret = list.addAll(values); modCount++; return ret; @@ -155,6 +180,7 @@ public class LazyStringArrayList extends AbstractList<String> // @Override public boolean addAllByteArray(Collection<byte[]> c) { + ensureIsMutable(); boolean ret = list.addAll(c); modCount++; return ret; @@ -162,6 +188,7 @@ public class LazyStringArrayList extends AbstractList<String> @Override public String remove(int index) { + ensureIsMutable(); Object o = list.remove(index); modCount++; return asString(o); @@ -169,18 +196,21 @@ public class LazyStringArrayList extends AbstractList<String> @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++; } @@ -207,14 +237,23 @@ public class LazyStringArrayList extends AbstractList<String> // @Override public void set(int index, ByteString s) { - list.set(index, 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) { - list.set(index, 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) { @@ -253,6 +292,7 @@ public class LazyStringArrayList extends AbstractList<String> // @Override public void mergeFrom(LazyStringList other) { + ensureIsMutable(); for (Object o : other.getUnderlyingElements()) { if (o instanceof byte[]) { byte[] b = (byte[]) o; @@ -267,20 +307,15 @@ public class LazyStringArrayList extends AbstractList<String> private static class ByteArrayListView extends AbstractList<byte[]> implements RandomAccess { - private final List<Object> list; + private final LazyStringArrayList list; - ByteArrayListView(List<Object> list) { + ByteArrayListView(LazyStringArrayList list) { this.list = list; } @Override public byte[] get(int index) { - Object o = list.get(index); - byte[] b = asByteArray(o); - if (b != o) { - list.set(index, b); - } - return b; + return list.getByteArray(index); } @Override @@ -290,7 +325,7 @@ public class LazyStringArrayList extends AbstractList<String> @Override public byte[] set(int index, byte[] s) { - Object o = list.set(index, s); + Object o = list.setAndReturn(index, s); modCount++; return asByteArray(o); } @@ -311,25 +346,20 @@ public class LazyStringArrayList extends AbstractList<String> // @Override public List<byte[]> asByteArrayList() { - return new ByteArrayListView(list); + return new ByteArrayListView(this); } private static class ByteStringListView extends AbstractList<ByteString> implements RandomAccess { - private final List<Object> list; + private final LazyStringArrayList list; - ByteStringListView(List<Object> list) { + ByteStringListView(LazyStringArrayList list) { this.list = list; } @Override public ByteString get(int index) { - Object o = list.get(index); - ByteString b = asByteString(o); - if (b != o) { - list.set(index, b); - } - return b; + return list.getByteString(index); } @Override @@ -339,7 +369,7 @@ public class LazyStringArrayList extends AbstractList<String> @Override public ByteString set(int index, ByteString s) { - Object o = list.set(index, s); + Object o = list.setAndReturn(index, s); modCount++; return asByteString(o); } @@ -360,12 +390,15 @@ public class LazyStringArrayList extends AbstractList<String> // @Override public List<ByteString> asByteStringList() { - return new ByteStringListView(list); + return new ByteStringListView(this); } // @Override public LazyStringList getUnmodifiableView() { - return new UnmodifiableLazyStringList(this); + if (isModifiable()) { + return new UnmodifiableLazyStringList(this); + } + return this; } } diff --git a/java/src/main/java/com/google/protobuf/LiteralByteString.java b/java/src/main/java/com/google/protobuf/LiteralByteString.java index 767b9f35..c5a8512a 100644 --- a/java/src/main/java/com/google/protobuf/LiteralByteString.java +++ b/java/src/main/java/com/google/protobuf/LiteralByteString.java @@ -34,8 +34,8 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; @@ -112,7 +112,7 @@ class LiteralByteString extends ByteString { // ByteString -> byte[] @Override - protected void copyToInternal(byte[] target, int sourceOffset, + 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. @@ -152,9 +152,8 @@ class LiteralByteString extends ByteString { } @Override - public String toString(String charsetName) - throws UnsupportedEncodingException { - return new String(bytes, getOffsetIntoBytes(), size(), charsetName); + protected String toStringInternal(Charset charset) { + return new String(bytes, getOffsetIntoBytes(), size(), charset); } // ================================================================= @@ -195,12 +194,12 @@ class LiteralByteString extends ByteString { LiteralByteString otherAsLiteral = (LiteralByteString) other; // If we know the hash codes and they are not equal, we know the byte // strings are not equal. - if (hash != 0 - && otherAsLiteral.hash != 0 + if (hash != 0 + && otherAsLiteral.hash != 0 && hash != otherAsLiteral.hash) { return false; } - + return equalsRange((LiteralByteString) other, 0, size()); } else if (other instanceof RopeByteString) { return other.equals(this); @@ -281,14 +280,14 @@ class LiteralByteString extends ByteString { protected 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; diff --git a/java/src/main/java/com/google/protobuf/LongArrayList.java b/java/src/main/java/com/google/protobuf/LongArrayList.java new file mode 100644 index 00000000..298617ff --- /dev/null +++ b/java/src/main/java/com/google/protobuf/LongArrayList.java @@ -0,0 +1,242 @@ +// 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}. + */ + LongArrayList() { + array = new long[DEFAULT_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/src/main/java/com/google/protobuf/MapField.java b/java/src/main/java/com/google/protobuf/MapField.java index 82906d37..b290993c 100644 --- a/java/src/main/java/com/google/protobuf/MapField.java +++ b/java/src/main/java/com/google/protobuf/MapField.java @@ -30,9 +30,11 @@ package com.google.protobuf; +import com.google.protobuf.MapFieldLite.MutatabilityAwareMap; + import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -51,7 +53,7 @@ import java.util.Map; * and getList() concurrently in multiple threads. If write-access is needed, * all access must be synchronized. */ -public class MapField<K, V> { +public class MapField<K, V> implements MutabilityOracle { /** * Indicates where the data of this map field is currently stored. * @@ -72,8 +74,9 @@ public class MapField<K, V> { */ private enum StorageMode {MAP, LIST, BOTH} + private volatile boolean isMutable; private volatile StorageMode mode; - private Map<K, V> mapData; + private MutatabilityAwareMap<K, V> mapData; private List<Message> listData; // Convert between a map entry Message and a key-value pair. @@ -110,20 +113,19 @@ public class MapField<K, V> { private MapField( Converter<K, V> converter, StorageMode mode, - Map<K, V> mapData, - List<Message> listData) { + Map<K, V> mapData) { this.converter = converter; + this.isMutable = true; this.mode = mode; - this.mapData = mapData; - this.listData = listData; + this.mapData = new MutatabilityAwareMap<K, V>(this, mapData); + this.listData = null; } private MapField( MapEntry<K, V> defaultEntry, StorageMode mode, - Map<K, V> mapData, - List<Message> listData) { - this(new ImmutableMessageConverter<K, V>(defaultEntry), mode, mapData, listData); + Map<K, V> mapData) { + this(new ImmutableMessageConverter<K, V>(defaultEntry), mode, mapData); } @@ -131,14 +133,14 @@ public class MapField<K, V> { public static <K, V> MapField<K, V> emptyMapField( MapEntry<K, V> defaultEntry) { return new MapField<K, V>( - defaultEntry, StorageMode.MAP, Collections.<K, V>emptyMap(), null); + 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 HashMap<K, V>(), null); + defaultEntry, StorageMode.MAP, new LinkedHashMap<K, V>()); } @@ -151,7 +153,7 @@ public class MapField<K, V> { converter.convertMessageToKeyAndValue(message, map); } - private List<Message> convertMapToList(Map<K, V> mapData) { + private List<Message> convertMapToList(MutatabilityAwareMap<K, V> mapData) { List<Message> listData = new ArrayList<Message>(); for (Map.Entry<K, V> entry : mapData.entrySet()) { listData.add( @@ -161,12 +163,12 @@ public class MapField<K, V> { return listData; } - private Map<K, V> convertListToMap(List<Message> listData) { - Map<K, V> mapData = new HashMap<K, V>(); + private MutatabilityAwareMap<K, V> convertListToMap(List<Message> listData) { + Map<K, V> mapData = new LinkedHashMap<K, V>(); for (Message item : listData) { convertMessageToKeyAndValue(item, mapData); } - return mapData; + return new MutatabilityAwareMap<K, V>(this, mapData); } /** Returns the content of this MapField as a read-only Map. */ @@ -199,7 +201,7 @@ public class MapField<K, V> { } public void clear() { - mapData = new HashMap<K, V>(); + mapData = new MutatabilityAwareMap<K, V>(this, new LinkedHashMap<K, V>()); mode = StorageMode.MAP; } @@ -221,7 +223,7 @@ public class MapField<K, V> { /** Returns a deep copy of this MapField. */ public MapField<K, V> copy() { return new MapField<K, V>( - converter, StorageMode.MAP, MapFieldLite.copy(getMap()), null); + converter, StorageMode.MAP, MapFieldLite.copy(getMap())); } /** Gets the content of this MapField as a read-only List. */ @@ -256,4 +258,29 @@ public class MapField<K, V> { 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/src/main/java/com/google/protobuf/MapFieldLite.java b/java/src/main/java/com/google/protobuf/MapFieldLite.java index 7f94c690..c17fa7b1 100644 --- a/java/src/main/java/com/google/protobuf/MapFieldLite.java +++ b/java/src/main/java/com/google/protobuf/MapFieldLite.java @@ -31,9 +31,12 @@ package com.google.protobuf; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; -import java.util.HashMap; +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. @@ -41,16 +44,21 @@ import java.util.Map; * This class is a protobuf implementation detail. Users shouldn't use this * class directly. */ -public class MapFieldLite<K, V> { - private Map<K, V> mapData; +public class MapFieldLite<K, V> implements MutabilityOracle { + private MutatabilityAwareMap<K, V> mapData; + private boolean isMutable; private MapFieldLite(Map<K, V> mapData) { - this.mapData = 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"}) @@ -60,7 +68,7 @@ public class MapFieldLite<K, V> { /** Creates a new MapFieldLite instance. */ public static <K, V> MapFieldLite<K, V> newMapField() { - return new MapFieldLite<K, V>(new HashMap<K, V>()); + return new MapFieldLite<K, V>(new LinkedHashMap<K, V>()); } /** Gets the content of this MapField as a read-only Map. */ @@ -168,7 +176,7 @@ public class MapFieldLite<K, V> { */ @SuppressWarnings("unchecked") static <K, V> Map<K, V> copy(Map<K, V> map) { - Map<K, V> result = new HashMap<K, V>(); + Map<K, V> result = new LinkedHashMap<K, V>(); for (Map.Entry<K, V> entry : map.entrySet()) { result.put(entry.getKey(), (V) copy(entry.getValue())); } @@ -179,4 +187,360 @@ public class MapFieldLite<K, V> { 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/src/main/java/com/google/protobuf/Message.java b/java/src/main/java/com/google/protobuf/Message.java index 5c75b58b..fa0265e2 100644 --- a/java/src/main/java/com/google/protobuf/Message.java +++ b/java/src/main/java/com/google/protobuf/Message.java @@ -163,7 +163,7 @@ public interface Message extends MessageLite, MessageOrBuilder { * field builder, which will then be nested into its parent builder. * <p> * NOTE: implementations that do not support nested builders will throw - * <code>UnsupportedException</code>. + * <code>UnsupportedOperationException</code>. */ Builder getFieldBuilder(Descriptors.FieldDescriptor field); @@ -181,7 +181,7 @@ public interface Message extends MessageLite, MessageOrBuilder { * field builder, which will then be nested into its parent builder. * <p> * NOTE: implementations that do not support nested builders will throw - * <code>UnsupportedException</code>. + * <code>UnsupportedOperationException</code>. */ Builder getRepeatedFieldBuilder(Descriptors.FieldDescriptor field, int index); diff --git a/java/src/main/java/com/google/protobuf/MessageReflection.java b/java/src/main/java/com/google/protobuf/MessageReflection.java index 06e3c99b..de4bfd3e 100644 --- a/java/src/main/java/com/google/protobuf/MessageReflection.java +++ b/java/src/main/java/com/google/protobuf/MessageReflection.java @@ -45,13 +45,14 @@ import java.util.TreeMap; */ class MessageReflection { - static void writeMessageTo(Message message, CodedOutputStream output, + static void writeMessageTo( + Message message, + Map<FieldDescriptor, Object> fields, + CodedOutputStream output, boolean alwaysWriteRequiredFields) throws IOException { final boolean isMessageSet = message.getDescriptorForType().getOptions().getMessageSetWireFormat(); - - Map<FieldDescriptor, Object> fields = message.getAllFields(); if (alwaysWriteRequiredFields) { fields = new TreeMap<FieldDescriptor, Object>(fields); for (final FieldDescriptor field : @@ -82,13 +83,15 @@ class MessageReflection { } } - static int getSerializedSize(Message message) { + 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 : - message.getAllFields().entrySet()) { + fields.entrySet()) { final Descriptors.FieldDescriptor field = entry.getKey(); final Object value = entry.getValue(); if (isMessageSet && field.isExtension() && @@ -340,14 +343,12 @@ class MessageReflection { ByteString bytes, ExtensionRegistryLite registry, Descriptors.FieldDescriptor descriptor, Message defaultInstance) throws IOException; - + /** - * Read a primitive field from input. Note that builders and mutable - * messages may use different Java types to represent a primtive field. + * Returns the UTF8 validation level for the field. */ - Object readPrimitiveField( - CodedInputStream input, WireFormat.FieldType type, - boolean checkUtf8) throws IOException; + WireFormat.Utf8Validation getUtf8Validation(Descriptors.FieldDescriptor + descriptor); /** * Returns a new merge target for a sub-field. When defaultInstance is @@ -513,11 +514,18 @@ class MessageReflection { return new BuilderAdapter(builder.newBuilderForField(field)); } } - - public Object readPrimitiveField( - CodedInputStream input, WireFormat.FieldType type, - boolean checkUtf8) throws IOException { - return FieldSet.readPrimitiveField(input, type, checkUtf8); + + 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() { @@ -651,11 +659,14 @@ class MessageReflection { throw new UnsupportedOperationException( "newMergeTargetForField() called on FieldSet object"); } - - public Object readPrimitiveField( - CodedInputStream input, WireFormat.FieldType type, - boolean checkUtf8) throws IOException { - return FieldSet.readPrimitiveField(input, type, checkUtf8); + + 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() { @@ -767,8 +778,8 @@ class MessageReflection { } } else { while (input.getBytesUntilLimit() > 0) { - final Object value = - target.readPrimitiveField(input, field.getLiteType(), field.needsUtf8Check()); + final Object value = WireFormat.readPrimitiveField( + input, field.getLiteType(), target.getUtf8Validation(field)); target.addRepeatedField(field, value); } } @@ -801,7 +812,8 @@ class MessageReflection { } break; default: - value = target.readPrimitiveField(input, field.getLiteType(), field.needsUtf8Check()); + value = WireFormat.readPrimitiveField( + input, field.getLiteType(), target.getUtf8Validation(field)); break; } diff --git a/java/src/main/java/com/google/protobuf/MutabilityOracle.java b/java/src/main/java/com/google/protobuf/MutabilityOracle.java new file mode 100644 index 00000000..82b723c9 --- /dev/null +++ b/java/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/src/main/java/com/google/protobuf/Parser.java b/java/src/main/java/com/google/protobuf/Parser.java index f636014b..227c02b7 100644 --- a/java/src/main/java/com/google/protobuf/Parser.java +++ b/java/src/main/java/com/google/protobuf/Parser.java @@ -227,8 +227,8 @@ public interface Parser<MessageType> { * {@link MessageLite#writeDelimitedTo(java.io.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 + * @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) diff --git a/java/src/main/java/com/google/protobuf/ProtobufArrayList.java b/java/src/main/java/com/google/protobuf/ProtobufArrayList.java new file mode 100644 index 00000000..759368c9 --- /dev/null +++ b/java/src/main/java/com/google/protobuf/ProtobufArrayList.java @@ -0,0 +1,95 @@ +// 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); + } + + @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/src/main/java/com/google/protobuf/RopeByteString.java b/java/src/main/java/com/google/protobuf/RopeByteString.java index fa23c9dd..4f3eb127 100644 --- a/java/src/main/java/com/google/protobuf/RopeByteString.java +++ b/java/src/main/java/com/google/protobuf/RopeByteString.java @@ -30,14 +30,14 @@ package com.google.protobuf; -import java.io.InvalidObjectException; +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.io.UnsupportedEncodingException; -import java.io.ByteArrayInputStream; import java.nio.ByteBuffer; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; @@ -418,9 +418,8 @@ class RopeByteString extends ByteString { } @Override - public String toString(String charsetName) - throws UnsupportedEncodingException { - return new String(toByteArray(), charsetName); + protected String toStringInternal(Charset charset) { + return new String(toByteArray(), charset); } // ================================================================= diff --git a/java/src/main/java/com/google/protobuf/TextFormat.java b/java/src/main/java/com/google/protobuf/TextFormat.java index 4f6756ed..a79ce559 100644 --- a/java/src/main/java/com/google/protobuf/TextFormat.java +++ b/java/src/main/java/com/google/protobuf/TextFormat.java @@ -409,9 +409,9 @@ public final class TextFormat { case STRING: generator.print("\""); - generator.print(escapeNonAscii ? - escapeText((String) value) : - escapeDoubleQuotesAndBackslashes((String) value) + generator.print(escapeNonAscii + ? escapeText((String) value) + : escapeDoubleQuotesAndBackslashes((String) value) .replace("\n", "\\n")); generator.print("\""); break; @@ -730,8 +730,8 @@ public final class TextFormat { } final char c = currentToken.charAt(0); - return ('0' <= c && c <= '9') || - c == '-' || c == '+'; + return ('0' <= c && c <= '9') + || c == '-' || c == '+'; } /** @@ -749,10 +749,10 @@ public final class TextFormat { 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 == '.')) { + if (('a' <= c && c <= 'z') + || ('A' <= c && c <= 'Z') + || ('0' <= c && c <= '9') + || (c == '_') || (c == '.')) { // OK } else { throw parseException( @@ -941,14 +941,14 @@ public final class TextFormat { * Otherwise, throw a {@link ParseException}. */ public boolean consumeBoolean() throws ParseException { - if (currentToken.equals("true") || - currentToken.equals("t") || - currentToken.equals("1")) { + if (currentToken.equals("true") + || currentToken.equals("t") + || currentToken.equals("1")) { nextToken(); return true; - } else if (currentToken.equals("false") || - currentToken.equals("f") || - currentToken.equals("0")) { + } else if (currentToken.equals("false") + || currentToken.equals("f") + || currentToken.equals("0")) { nextToken(); return false; } else { @@ -999,14 +999,15 @@ public final class TextFormat { */ private void consumeByteString(List<ByteString> list) throws ParseException { - final char quote = currentToken.length() > 0 ? currentToken.charAt(0) - : '\0'; + 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) { + if (currentToken.length() < 2 + || currentToken.charAt(currentToken.length() - 1) != quote) { throw parseException("String missing ending quote."); } @@ -1340,8 +1341,8 @@ public final class TextFormat { } else { if (extension.descriptor.getContainingType() != type) { throw tokenizer.parseExceptionPreviousToken( - "Extension \"" + name + "\" does not extend message type \"" + - type.getFullName() + "\"."); + "Extension \"" + name + "\" does not extend message type \"" + + type.getFullName() + "\"."); } field = extension.descriptor; } @@ -1365,20 +1366,20 @@ public final class TextFormat { } } // Again, special-case group names as described above. - if (field != null && field.getType() == FieldDescriptor.Type.GROUP && - !field.getMessageType().getName().equals(name)) { + if (field != null && field.getType() == FieldDescriptor.Type.GROUP + && !field.getMessageType().getName().equals(name)) { field = null; } if (field == null) { if (!allowUnknownFields) { throw tokenizer.parseExceptionPreviousToken( - "Message type \"" + type.getFullName() + - "\" has no field named \"" + name + "\"."); + "Message type \"" + type.getFullName() + + "\" has no field named \"" + name + "\"."); } else { logger.warning( - "Message type \"" + type.getFullName() + - "\" has no field named \"" + name + "\"."); + "Message type \"" + type.getFullName() + + "\" has no field named \"" + name + "\"."); } } } @@ -1391,8 +1392,9 @@ public final class TextFormat { // 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("<")) { + if (tokenizer.tryConsume(":") + && !tokenizer.lookingAt("{") + && !tokenizer.lookingAt("<")) { skipFieldValue(tokenizer); } else { skipFieldMessage(tokenizer); @@ -1516,16 +1518,16 @@ public final class TextFormat { value = enumType.findValueByNumber(number); if (value == null) { throw tokenizer.parseExceptionPreviousToken( - "Enum type \"" + enumType.getFullName() + - "\" has no value with number " + number + '.'); + "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 + "\"."); + "Enum type \"" + enumType.getFullName() + + "\" has no value named \"" + id + "\"."); } } @@ -1578,8 +1580,9 @@ public final class TextFormat { // 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("{")) { + if (tokenizer.tryConsume(":") + && !tokenizer.lookingAt("<") + && !tokenizer.lookingAt("{")) { skipFieldValue(tokenizer); } else { skipFieldMessage(tokenizer); @@ -1617,11 +1620,11 @@ public final class TextFormat { while (tokenizer.tryConsumeString()) {} return; } - if (!tokenizer.tryConsumeIdentifier() && // includes enum & boolean - !tokenizer.tryConsumeInt64() && // includes int32 - !tokenizer.tryConsumeUInt64() && // includes uint32 - !tokenizer.tryConsumeDouble() && - !tokenizer.tryConsumeFloat()) { + 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); } @@ -1647,19 +1650,19 @@ public final class TextFormat { * which no defined short-hand escape sequence is defined will be escaped * using 3-digit octal sequences. */ - private static String escapeBytes(final ByteSequence input) { + 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 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; @@ -1688,11 +1691,13 @@ public final class TextFormat { * which no defined short-hand escape sequence is defined will be escaped * using 3-digit octal sequences. */ - static String escapeBytes(final ByteString input) { + 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); } @@ -1702,11 +1707,13 @@ public final class TextFormat { /** * Like {@link #escapeBytes(ByteString)}, but used for byte array. */ - static String escapeBytes(final byte[] input) { + 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]; } @@ -1718,7 +1725,7 @@ public final class TextFormat { * {@link #escapeBytes(ByteString)}. Two-digit hex escapes (starting with * "\x") are also recognized. */ - static ByteString unescapeBytes(final CharSequence charString) + 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()); @@ -1749,7 +1756,7 @@ public final class TextFormat { code = code * 8 + digitValue(input.byteAt(i)); } // TODO: Check that 0 <= code && code <= 0xFF. - result[pos++] = (byte)code; + result[pos++] = (byte) code; } else { switch (c) { case 'a' : result[pos++] = 0x07; break; @@ -1777,12 +1784,12 @@ public final class TextFormat { ++i; code = code * 16 + digitValue(input.byteAt(i)); } - result[pos++] = (byte)code; + result[pos++] = (byte) code; break; default: throw new InvalidEscapeSequenceException( - "Invalid escape sequence: '\\" + (char)c + '\''); + "Invalid escape sequence: '\\" + (char) c + '\''); } } } else { @@ -1801,7 +1808,7 @@ public final class TextFormat { * Thrown by {@link TextFormat#unescapeBytes} and * {@link TextFormat#unescapeText} when an invalid escape sequence is seen. */ - static class InvalidEscapeSequenceException extends IOException { + public static class InvalidEscapeSequenceException extends IOException { private static final long serialVersionUID = -8164033650142593304L; InvalidEscapeSequenceException(final String description) { @@ -1841,9 +1848,9 @@ public final class TextFormat { /** 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'); + return ('0' <= c && c <= '9') + || ('a' <= c && c <= 'f') + || ('A' <= c && c <= 'F'); } /** diff --git a/java/src/main/java/com/google/protobuf/Utf8.java b/java/src/main/java/com/google/protobuf/Utf8.java index 4d0ef53b..4271b41b 100644 --- a/java/src/main/java/com/google/protobuf/Utf8.java +++ b/java/src/main/java/com/google/protobuf/Utf8.java @@ -46,7 +46,7 @@ package com.google.protobuf; * <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, "UTF-8").getBytes("UTF-8")) + * Arrays.equals(bytes, new String(bytes, Internal.UTF_8).getBytes(Internal.UTF_8)) * }</pre> * * <p>See the Unicode Standard,</br> diff --git a/java/src/main/java/com/google/protobuf/WireFormat.java b/java/src/main/java/com/google/protobuf/WireFormat.java index eba2528e..ba83b666 100644 --- a/java/src/main/java/com/google/protobuf/WireFormat.java +++ b/java/src/main/java/com/google/protobuf/WireFormat.java @@ -30,6 +30,8 @@ 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 @@ -160,4 +162,84 @@ public final class WireFormat { 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/src/test/java/com/google/protobuf/BooleanArrayListTest.java b/java/src/test/java/com/google/protobuf/BooleanArrayListTest.java new file mode 100644 index 00000000..df89c263 --- /dev/null +++ b/java/src/test/java/com/google/protobuf/BooleanArrayListTest.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 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) { + if (list.contains(1)) { + throw new RuntimeException("Cannot test the immutability of lists that contain 1."); + } + + 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(1)); + 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(1)); + 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/src/test/java/com/google/protobuf/BoundedByteStringTest.java b/java/src/test/java/com/google/protobuf/BoundedByteStringTest.java index 6c9596ca..1562a1a6 100644 --- a/java/src/test/java/com/google/protobuf/BoundedByteStringTest.java +++ b/java/src/test/java/com/google/protobuf/BoundedByteStringTest.java @@ -62,7 +62,7 @@ public class BoundedByteStringTest extends LiteralByteStringTest { @Override public void testToString() throws UnsupportedEncodingException { String testString = "I love unicode \u1234\u5678 characters"; - LiteralByteString unicode = new LiteralByteString(testString.getBytes(UTF_8)); + 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)); @@ -72,6 +72,19 @@ public class BoundedByteStringTest extends LiteralByteStringTest { testString.substring(2, testString.length() - 6), roundTripString); } + @Override + public void testCharsetToString() 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(Internal.UTF_8); + assertEquals(classUnderTest + " unicode bytes must match", + testString.substring(2, testString.length() - 6), roundTripString); + } + public void testJavaSerialization() throws Exception { ByteArrayOutputStream out = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(out); diff --git a/java/src/test/java/com/google/protobuf/ByteStringTest.java b/java/src/test/java/com/google/protobuf/ByteStringTest.java index 15de17cd..8af5dcde 100644 --- a/java/src/test/java/com/google/protobuf/ByteStringTest.java +++ b/java/src/test/java/com/google/protobuf/ByteStringTest.java @@ -41,6 +41,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; @@ -56,7 +57,7 @@ import java.util.Random; */ public class ByteStringTest extends TestCase { - private static final String UTF_16 = "UTF-16"; + private static final Charset UTF_16 = Charset.forName("UTF-16"); static byte[] getTestBytes(int size, long seed) { Random random = new Random(seed); @@ -139,7 +140,7 @@ public class ByteStringTest extends TestCase { public void testCopyFrom_Utf8() throws UnsupportedEncodingException { String testString = "I love unicode \u1234\u5678 characters"; ByteString byteString = ByteString.copyFromUtf8(testString); - byte[] testBytes = testString.getBytes("UTF-8"); + byte[] testBytes = testString.getBytes(Internal.UTF_8); assertTrue("copyFromUtf8 string must respect the charset", isArrayRange(byteString.toByteArray(), testBytes, 0, testBytes.length)); } @@ -380,7 +381,7 @@ public class ByteStringTest extends TestCase { 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; @@ -400,7 +401,7 @@ public class ByteStringTest extends TestCase { public void testToStringUtf8() throws UnsupportedEncodingException { String testString = "I love unicode \u1234\u5678 characters"; - byte[] testBytes = testString.getBytes("UTF-8"); + byte[] testBytes = testString.getBytes(Internal.UTF_8); ByteString byteString = ByteString.copyFrom(testBytes); assertEquals("copyToStringUtf8 must respect the charset", testString, byteString.toStringUtf8()); @@ -492,13 +493,13 @@ public class ByteStringTest extends TestCase { isArrayRange(bytes, byteString.toByteArray(), 0, bytes.length)); } } - + public void testNewOutputEmpty() throws IOException { // 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}); @@ -705,14 +706,14 @@ public class ByteStringTest extends TestCase { } 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. @@ -727,7 +728,7 @@ public class ByteStringTest extends TestCase { 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; diff --git a/java/src/test/java/com/google/protobuf/CodedOutputStreamTest.java b/java/src/test/java/com/google/protobuf/CodedOutputStreamTest.java index 0785cc91..365789c0 100644 --- a/java/src/test/java/com/google/protobuf/CodedOutputStreamTest.java +++ b/java/src/test/java/com/google/protobuf/CodedOutputStreamTest.java @@ -321,7 +321,7 @@ public class CodedOutputStreamTest extends TestCase { final int BUFFER_SIZE = 4 * 1024; ByteArrayOutputStream outputStream = new ByteArrayOutputStream(BUFFER_SIZE); CodedOutputStream codedStream = CodedOutputStream.newInstance(outputStream); - byte[] value = "abcde".getBytes("UTF-8"); + byte[] value = "abcde".getBytes(Internal.UTF_8); for (int i = 0; i < 1024; ++i) { codedStream.writeRawBytes(value, 0, value.length); } @@ -330,7 +330,7 @@ public class CodedOutputStreamTest extends TestCase { assertTrue(codedStream.getTotalBytesWritten() > BUFFER_SIZE); assertEquals(value.length * 1024, codedStream.getTotalBytesWritten()); } - + public void testWriteToByteBuffer() throws Exception { final int bufferSize = 16 * 1024; ByteBuffer buffer = ByteBuffer.allocate(bufferSize); @@ -351,7 +351,7 @@ public class CodedOutputStreamTest extends TestCase { codedStream.writeRawByte((byte) 3); } codedStream.flush(); - + // Check that data is correctly written to the ByteBuffer. assertEquals(0, buffer.remaining()); buffer.flip(); @@ -365,9 +365,9 @@ public class CodedOutputStreamTest extends TestCase { assertEquals((byte) 3, buffer.get()); } } - + public void testWriteByteBuffer() throws Exception { - byte[] value = "abcde".getBytes("UTF-8"); + byte[] value = "abcde".getBytes(Internal.UTF_8); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); CodedOutputStream codedStream = CodedOutputStream.newInstance(outputStream); ByteBuffer byteBuffer = ByteBuffer.wrap(value, 0, 1); @@ -377,10 +377,10 @@ public class CodedOutputStreamTest extends TestCase { // 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); diff --git a/java/src/test/java/com/google/protobuf/DescriptorsTest.java b/java/src/test/java/com/google/protobuf/DescriptorsTest.java index 05832a12..edd7fc46 100644 --- a/java/src/test/java/com/google/protobuf/DescriptorsTest.java +++ b/java/src/test/java/com/google/protobuf/DescriptorsTest.java @@ -35,32 +35,29 @@ 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.DescriptorValidationException; -import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.Descriptors.Descriptor; -import com.google.protobuf.Descriptors.FieldDescriptor; -import com.google.protobuf.Descriptors.OneofDescriptor; +import com.google.protobuf.Descriptors.DescriptorValidationException; import com.google.protobuf.Descriptors.EnumDescriptor; import com.google.protobuf.Descriptors.EnumValueDescriptor; -import com.google.protobuf.Descriptors.ServiceDescriptor; +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.ImportMessage; +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.TestAllTypes; 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 protobuf_unittest.UnittestCustomOptions; - -import protobuf_unittest.TestCustomOptions; - import junit.framework.TestCase; @@ -286,7 +283,7 @@ public class DescriptorsTest extends TestCase { d = TestExtremeDefaultValues.getDescriptor(); assertEquals( ByteString.copyFrom( - "\0\001\007\b\f\n\r\t\013\\\'\"\u00fe".getBytes("ISO-8859-1")), + "\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()); @@ -497,7 +494,7 @@ public class DescriptorsTest extends TestCase { .build(); // translate and crosslink FileDescriptor file = - Descriptors.FileDescriptor.buildFrom(fileDescriptorProto, + Descriptors.FileDescriptor.buildFrom(fileDescriptorProto, new FileDescriptor[0]); // verify resulting descriptors assertNotNull(file); @@ -518,7 +515,7 @@ public class DescriptorsTest extends TestCase { } assertTrue(barFound); } - + public void testDependencyOrder() throws Exception { FileDescriptorProto fooProto = FileDescriptorProto.newBuilder() .setName("foo.proto").build(); @@ -537,14 +534,14 @@ public class DescriptorsTest extends TestCase { new FileDescriptor[0]); FileDescriptor barFile = Descriptors.FileDescriptor.buildFrom(barProto, new FileDescriptor[] {fooFile}); - - // Items in the FileDescriptor array can be in any order. + + // 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(); @@ -628,7 +625,7 @@ public class DescriptorsTest extends TestCase { Descriptors.FileDescriptor.buildFrom( fooProto, new FileDescriptor[] {forwardFile}); } - + /** * Tests the translate/crosslink for an example with a more complex namespace * referencing. @@ -677,7 +674,7 @@ public class DescriptorsTest extends TestCase { 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 { @@ -691,6 +688,9 @@ public class DescriptorsTest extends TestCase { 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 { @@ -706,6 +706,19 @@ public class DescriptorsTest extends TestCase { 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( diff --git a/java/src/test/java/com/google/protobuf/DoubleArrayListTest.java b/java/src/test/java/com/google/protobuf/DoubleArrayListTest.java new file mode 100644 index 00000000..e7a73d70 --- /dev/null +++ b/java/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(1)) { + 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(1)); + 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(1)); + 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/src/test/java/com/google/protobuf/FloatArrayListTest.java b/java/src/test/java/com/google/protobuf/FloatArrayListTest.java new file mode 100644 index 00000000..8f3e93dc --- /dev/null +++ b/java/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(1)) { + 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(1)); + 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(1)); + 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/src/test/java/com/google/protobuf/GeneratedMessageTest.java b/java/src/test/java/com/google/protobuf/GeneratedMessageTest.java index 41ed7bd0..2bd8d1a9 100644 --- a/java/src/test/java/com/google/protobuf/GeneratedMessageTest.java +++ b/java/src/test/java/com/google/protobuf/GeneratedMessageTest.java @@ -56,6 +56,7 @@ 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; @@ -1510,6 +1511,17 @@ public class GeneratedMessageTest extends TestCase { } } + 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(); diff --git a/java/src/test/java/com/google/protobuf/IntArrayListTest.java b/java/src/test/java/com/google/protobuf/IntArrayListTest.java new file mode 100644 index 00000000..3733eb30 --- /dev/null +++ b/java/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/src/test/java/com/google/protobuf/IsValidUtf8Test.java b/java/src/test/java/com/google/protobuf/IsValidUtf8Test.java index 89b64519..8751baae 100644 --- a/java/src/test/java/com/google/protobuf/IsValidUtf8Test.java +++ b/java/src/test/java/com/google/protobuf/IsValidUtf8Test.java @@ -72,8 +72,11 @@ public class IsValidUtf8Test extends TestCase { * Tests that round tripping of all three byte permutations work. */ public void testIsValidUtf8_3Bytes() throws UnsupportedEncodingException { - IsValidUtf8TestUtil.testBytes(3, - IsValidUtf8TestUtil.EXPECTED_THREE_BYTE_ROUNDTRIPPABLE_COUNT); + // Travis' OOM killer doesn't like this test + if (System.getenv("TRAVIS") == null) { + IsValidUtf8TestUtil.testBytes(3, + IsValidUtf8TestUtil.EXPECTED_THREE_BYTE_ROUNDTRIPPABLE_COUNT); + } } /** diff --git a/java/src/test/java/com/google/protobuf/IsValidUtf8TestUtil.java b/java/src/test/java/com/google/protobuf/IsValidUtf8TestUtil.java index f41595ec..321669f3 100644 --- a/java/src/test/java/com/google/protobuf/IsValidUtf8TestUtil.java +++ b/java/src/test/java/com/google/protobuf/IsValidUtf8TestUtil.java @@ -33,18 +33,17 @@ 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; -import java.nio.charset.CharsetDecoder; -import java.nio.charset.Charset; -import java.nio.charset.CodingErrorAction; -import java.nio.charset.CharsetEncoder; -import java.nio.charset.CoderResult; -import java.nio.ByteBuffer; -import java.nio.CharBuffer; /** * Shared testing code for {@link IsValidUtf8Test} and @@ -220,8 +219,8 @@ class IsValidUtf8TestUtil { } ByteString bs = ByteString.copyFrom(bytes); boolean isRoundTrippable = bs.isValidUtf8(); - String s = new String(bytes, "UTF-8"); - byte[] bytesReencoded = s.getBytes("UTF-8"); + String s = new String(bytes, Internal.UTF_8); + byte[] bytesReencoded = s.getBytes(Internal.UTF_8); boolean bytesEqual = Arrays.equals(bytes, bytesReencoded); if (bytesEqual != isRoundTrippable) { @@ -313,10 +312,10 @@ class IsValidUtf8TestUtil { void testBytesUsingByteBuffers( int numBytes, long expectedCount, long start, long lim) throws UnsupportedEncodingException { - CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder() + CharsetDecoder decoder = Internal.UTF_8.newDecoder() .onMalformedInput(CodingErrorAction.REPLACE) .onUnmappableCharacter(CodingErrorAction.REPLACE); - CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder() + CharsetEncoder encoder = Internal.UTF_8.newEncoder() .onMalformedInput(CodingErrorAction.REPLACE) .onUnmappableCharacter(CodingErrorAction.REPLACE); byte[] bytes = new byte[numBytes]; diff --git a/java/src/test/java/com/google/protobuf/LazyFieldLiteTest.java b/java/src/test/java/com/google/protobuf/LazyFieldLiteTest.java index e67c6d27..211b5697 100644 --- a/java/src/test/java/com/google/protobuf/LazyFieldLiteTest.java +++ b/java/src/test/java/com/google/protobuf/LazyFieldLiteTest.java @@ -30,6 +30,9 @@ 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; @@ -111,12 +114,146 @@ public class LazyFieldLiteTest extends TestCase { 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(TestUtil.getExtensionRegistry(), bytes); + return new LazyFieldLite(extensionRegistry, bytes); } private void changeValue(LazyFieldLite lazyField) { diff --git a/java/src/test/java/com/google/protobuf/LazyMessageLiteTest.java b/java/src/test/java/com/google/protobuf/LazyMessageLiteTest.java index 9de794fe..afe0fffd 100644 --- a/java/src/test/java/com/google/protobuf/LazyMessageLiteTest.java +++ b/java/src/test/java/com/google/protobuf/LazyMessageLiteTest.java @@ -30,6 +30,7 @@ package com.google.protobuf; +import protobuf_unittest.LazyFieldsLite.LazyExtension; import protobuf_unittest.LazyFieldsLite.LazyInnerMessageLite; import protobuf_unittest.LazyFieldsLite.LazyMessageLite; import protobuf_unittest.LazyFieldsLite.LazyNestedInnerMessageLite; @@ -285,4 +286,22 @@ public class LazyMessageLiteTest extends TestCase { 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/src/test/java/com/google/protobuf/LazyStringArrayListTest.java b/java/src/test/java/com/google/protobuf/LazyStringArrayListTest.java index f3012b96..0f42ac50 100644 --- a/java/src/test/java/com/google/protobuf/LazyStringArrayListTest.java +++ b/java/src/test/java/com/google/protobuf/LazyStringArrayListTest.java @@ -30,9 +30,13 @@ 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; /** @@ -171,4 +175,188 @@ public class LazyStringArrayListTest extends TestCase { 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/src/test/java/com/google/protobuf/LiteTest.java b/java/src/test/java/com/google/protobuf/LiteTest.java index 4d8037fc..8c3b5e5c 100644 --- a/java/src/test/java/com/google/protobuf/LiteTest.java +++ b/java/src/test/java/com/google/protobuf/LiteTest.java @@ -30,9 +30,18 @@ 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.TestAllTypesLite; +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.TestNestedExtensionLite; import junit.framework.TestCase; @@ -149,13 +158,1302 @@ public class LiteTest extends TestCase { public void testClone() { TestAllTypesLite.Builder expected = TestAllTypesLite.newBuilder() .setOptionalInt32(123); - assertEquals( - expected.getOptionalInt32(), expected.clone().getOptionalInt32()); + 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)); + 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()); + + 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/src/test/java/com/google/protobuf/LiteralByteStringTest.java b/java/src/test/java/com/google/protobuf/LiteralByteStringTest.java index ff39ca3f..958b6a7e 100644 --- a/java/src/test/java/com/google/protobuf/LiteralByteStringTest.java +++ b/java/src/test/java/com/google/protobuf/LiteralByteStringTest.java @@ -248,7 +248,7 @@ public class LiteralByteStringTest extends TestCase { assertTrue(classUnderTest + ".writeTo() must give back the same bytes", Arrays.equals(referenceBytes, roundTripBytes)); } - + public void testWriteTo_mutating() throws IOException { OutputStream os = new OutputStream() { @Override @@ -289,16 +289,44 @@ public class LiteralByteStringTest extends TestCase { 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(UTF_8)); + 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() throws UnsupportedEncodingException { + 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() throws UnsupportedEncodingException{ + 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() throws UnsupportedEncodingException{ + 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); @@ -311,7 +339,7 @@ public class LiteralByteStringTest extends TestCase { byte[] mungedBytes = new byte[referenceBytes.length]; System.arraycopy(referenceBytes, 0, mungedBytes, 0, referenceBytes.length); - mungedBytes[mungedBytes.length - 5] ^= 0xFF; + 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))); } diff --git a/java/src/test/java/com/google/protobuf/LongArrayListTest.java b/java/src/test/java/com/google/protobuf/LongArrayListTest.java new file mode 100644 index 00000000..3a52ec7f --- /dev/null +++ b/java/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(1)) { + 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(1)); + 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(1)); + 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/src/test/java/com/google/protobuf/MapForProto2LiteTest.java b/java/src/test/java/com/google/protobuf/MapForProto2LiteTest.java index 22dbeb76..6cff689f 100644 --- a/java/src/test/java/com/google/protobuf/MapForProto2LiteTest.java +++ b/java/src/test/java/com/google/protobuf/MapForProto2LiteTest.java @@ -36,6 +36,11 @@ 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. */ @@ -170,6 +175,140 @@ public class MapForProto2LiteTest extends TestCase { 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(); @@ -274,4 +413,26 @@ public class MapForProto2LiteTest extends TestCase { 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/src/test/java/com/google/protobuf/MapForProto2Test.java b/java/src/test/java/com/google/protobuf/MapForProto2Test.java index 33ba7150..7e984040 100644 --- a/java/src/test/java/com/google/protobuf/MapForProto2Test.java +++ b/java/src/test/java/com/google/protobuf/MapForProto2Test.java @@ -34,11 +34,13 @@ 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; @@ -176,6 +178,116 @@ public class MapForProto2Test extends TestCase { 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(); @@ -499,4 +611,54 @@ public class MapForProto2Test extends TestCase { 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/src/test/java/com/google/protobuf/MapTest.java b/java/src/test/java/com/google/protobuf/MapTest.java index 9a25e302..0509be15 100644 --- a/java/src/test/java/com/google/protobuf/MapTest.java +++ b/java/src/test/java/com/google/protobuf/MapTest.java @@ -41,6 +41,7 @@ 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; @@ -57,22 +58,22 @@ public class MapTest extends TestCase { 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); @@ -88,22 +89,22 @@ public class MapTest extends TestCase { 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()); @@ -118,21 +119,21 @@ public class MapTest extends TestCase { 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); @@ -148,22 +149,22 @@ public class MapTest extends TestCase { 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()); @@ -178,22 +179,132 @@ public class MapTest extends TestCase { 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(); @@ -207,14 +318,14 @@ public class MapTest extends TestCase { 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(); @@ -222,12 +333,12 @@ public class MapTest extends TestCase { 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()); @@ -236,7 +347,7 @@ public class MapTest extends TestCase { 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(); @@ -244,23 +355,23 @@ public class MapTest extends TestCase { 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); @@ -268,57 +379,95 @@ public class MapTest extends TestCase { 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)) { @@ -337,7 +486,7 @@ public class MapTest extends TestCase { 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); @@ -348,7 +497,7 @@ public class MapTest extends TestCase { 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()) { @@ -357,7 +506,7 @@ public class MapTest extends TestCase { 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) { @@ -385,14 +534,14 @@ public class MapTest extends TestCase { 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)); @@ -405,7 +554,7 @@ public class MapTest extends TestCase { 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)); @@ -425,7 +574,7 @@ public class MapTest extends TestCase { 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); @@ -442,35 +591,35 @@ public class MapTest extends TestCase { 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. @@ -479,22 +628,22 @@ public class MapTest extends TestCase { 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(); @@ -502,7 +651,7 @@ public class MapTest extends TestCase { // 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); @@ -517,7 +666,7 @@ public class MapTest extends TestCase { 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(). @@ -528,7 +677,7 @@ public class MapTest extends TestCase { 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(); @@ -538,17 +687,17 @@ public class MapTest extends TestCase { // 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()); @@ -573,4 +722,26 @@ public class MapTest extends TestCase { 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/src/test/java/com/google/protobuf/ProtobufArrayListTest.java b/java/src/test/java/com/google/protobuf/ProtobufArrayListTest.java new file mode 100644 index 00000000..f9f5e9c7 --- /dev/null +++ b/java/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.<Double>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.<Double>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/src/test/java/com/google/protobuf/RopeByteStringSubstringTest.java b/java/src/test/java/com/google/protobuf/RopeByteStringSubstringTest.java index 212f8d68..cc385599 100644 --- a/java/src/test/java/com/google/protobuf/RopeByteStringSubstringTest.java +++ b/java/src/test/java/com/google/protobuf/RopeByteStringSubstringTest.java @@ -35,7 +35,7 @@ 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. + * {@link LiteralByteStringTest}. Only a couple of methods are overridden. * * @author carlanton@google.com (Carl Haverl) */ @@ -94,4 +94,34 @@ public class RopeByteStringSubstringTest extends LiteralByteStringTest { assertEquals(classUnderTest + " string must must have same hashCode as the flat string", flatString.hashCode(), unicode.hashCode()); } + + @Override + public void testCharsetToString() 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(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/src/test/java/com/google/protobuf/RopeByteStringTest.java b/java/src/test/java/com/google/protobuf/RopeByteStringTest.java index b3970196..bd0d15e3 100644 --- a/java/src/test/java/com/google/protobuf/RopeByteStringTest.java +++ b/java/src/test/java/com/google/protobuf/RopeByteStringTest.java @@ -42,7 +42,7 @@ 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}. * @@ -118,6 +118,60 @@ public class RopeByteStringTest extends LiteralByteStringTest { flatString.hashCode(), unicode.hashCode()); } + @Override + public void testCharsetToString() 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(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() throws UnsupportedEncodingException { + 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)); + } + + public void testToString_raisesException() throws UnsupportedEncodingException{ + 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 + } + } + public void testJavaSerialization() throws Exception { ByteArrayOutputStream out = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(out); diff --git a/java/src/test/java/com/google/protobuf/TestUtil.java b/java/src/test/java/com/google/protobuf/TestUtil.java index 135a1174..19a96d0e 100644 --- a/java/src/test/java/com/google/protobuf/TestUtil.java +++ b/java/src/test/java/com/google/protobuf/TestUtil.java @@ -276,11 +276,7 @@ public final class TestUtil { /** Helper to convert a String to ByteString. */ static ByteString toBytes(String str) { - try { - return ByteString.copyFrom(str.getBytes("UTF-8")); - } catch(java.io.UnsupportedEncodingException e) { - throw new RuntimeException("UTF-8 not supported.", e); - } + return ByteString.copyFrom(str.getBytes(Internal.UTF_8)); } /** diff --git a/java/src/test/java/com/google/protobuf/TextFormatTest.java b/java/src/test/java/com/google/protobuf/TextFormatTest.java index eba06ca0..5d846646 100644 --- a/java/src/test/java/com/google/protobuf/TextFormatTest.java +++ b/java/src/test/java/com/google/protobuf/TextFormatTest.java @@ -243,8 +243,8 @@ public class TextFormatTest extends TestCase { * characters. The characters are converted directly to bytes, *not* * encoded using UTF-8. */ - private ByteString bytes(String str) throws Exception { - return ByteString.copyFrom(str.getBytes("ISO-8859-1")); + private ByteString bytes(String str) { + return ByteString.copyFrom(str.getBytes(Internal.ISO_8859_1)); } /** @@ -863,7 +863,7 @@ public class TextFormatTest extends TestCase { 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", diff --git a/java/src/test/java/com/google/protobuf/UnknownFieldSetLiteTest.java b/java/src/test/java/com/google/protobuf/UnknownFieldSetLiteTest.java index cec3da1e..e76b4a67 100644 --- a/java/src/test/java/com/google/protobuf/UnknownFieldSetLiteTest.java +++ b/java/src/test/java/com/google/protobuf/UnknownFieldSetLiteTest.java @@ -229,7 +229,7 @@ public class UnknownFieldSetLiteTest extends TestCase { public void testMalformedBytes() throws Exception { try { - Foo.parseFrom("this is a malformed protocol buffer".getBytes("UTF-8")); + Foo.parseFrom("this is a malformed protocol buffer".getBytes(Internal.UTF_8)); fail(); } catch (InvalidProtocolBufferException e) { // Expected. diff --git a/java/src/test/java/com/google/protobuf/WellKnownTypesTest.java b/java/src/test/java/com/google/protobuf/WellKnownTypesTest.java new file mode 100644 index 00000000..982e200f --- /dev/null +++ b/java/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/src/test/java/com/google/protobuf/field_presence_test.proto b/java/src/test/java/com/google/protobuf/field_presence_test.proto index 8d8ea8ef..8f3ca8c6 100644 --- a/java/src/test/java/com/google/protobuf/field_presence_test.proto +++ b/java/src/test/java/com/google/protobuf/field_presence_test.proto @@ -45,15 +45,15 @@ message TestAllTypes { BAZ = 2; } message NestedMessage { - optional int32 value = 1; + int32 value = 1; } - optional int32 optional_int32 = 1; - optional string optional_string = 2; - optional bytes optional_bytes = 3; - optional NestedEnum optional_nested_enum = 4; - optional NestedMessage optional_nested_message = 5; - optional protobuf_unittest.TestRequired optional_proto2_message = 6; + 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; @@ -75,12 +75,12 @@ message TestAllTypes { } message TestOptionalFieldsOnly { - optional int32 optional_int32 = 1; - optional string optional_string = 2; - optional bytes optional_bytes = 3; - optional TestAllTypes.NestedEnum optional_nested_enum = 4; - optional TestAllTypes.NestedMessage optional_nested_message = 5; - optional protobuf_unittest.TestRequired optional_proto2_message = 6; + 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 { diff --git a/java/src/test/java/com/google/protobuf/lazy_fields_lite.proto b/java/src/test/java/com/google/protobuf/lazy_fields_lite.proto index 015dc267..5580f72d 100644 --- a/java/src/test/java/com/google/protobuf/lazy_fields_lite.proto +++ b/java/src/test/java/com/google/protobuf/lazy_fields_lite.proto @@ -54,6 +54,15 @@ 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 { diff --git a/java/src/test/java/com/google/protobuf/map_for_proto2_lite_test.proto b/java/src/test/java/com/google/protobuf/map_for_proto2_lite_test.proto index a1fe856c..d5418f28 100644 --- a/java/src/test/java/com/google/protobuf/map_for_proto2_lite_test.proto +++ b/java/src/test/java/com/google/protobuf/map_for_proto2_lite_test.proto @@ -63,6 +63,13 @@ message TestUnknownEnumValue { // 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/src/test/java/com/google/protobuf/map_for_proto2_test.proto b/java/src/test/java/com/google/protobuf/map_for_proto2_test.proto index a0ec7ac5..a9be5166 100644 --- a/java/src/test/java/com/google/protobuf/map_for_proto2_test.proto +++ b/java/src/test/java/com/google/protobuf/map_for_proto2_test.proto @@ -65,3 +65,10 @@ message TestUnknownEnumValue { // 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/src/test/java/com/google/protobuf/map_initialization_order_test.proto b/java/src/test/java/com/google/protobuf/map_initialization_order_test.proto new file mode 100644 index 00000000..b02ac599 --- /dev/null +++ b/java/src/test/java/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/src/test/java/com/google/protobuf/map_test.proto b/java/src/test/java/com/google/protobuf/map_test.proto index 105ee3f8..2f7709be 100644 --- a/java/src/test/java/com/google/protobuf/map_test.proto +++ b/java/src/test/java/com/google/protobuf/map_test.proto @@ -36,9 +36,10 @@ option java_package = "map_test"; option java_outer_classname = "MapTestProto"; option java_generate_equals_and_hash = true; + message TestMap { message MessageValue { - optional int32 value = 1; + int32 value = 1; } enum EnumValue { FOO = 0; @@ -59,5 +60,5 @@ message TestMap { // propagate the onChange event and mark its parent dirty when a change // is made to a map field. message TestOnChangeEventPropagation { - optional TestMap optional_message = 1; + TestMap optional_message = 1; } diff --git a/java/src/test/java/com/google/protobuf/test_bad_identifiers.proto b/java/src/test/java/com/google/protobuf/test_bad_identifiers.proto index 67035fd5..dc082615 100644 --- a/java/src/test/java/com/google/protobuf/test_bad_identifiers.proto +++ b/java/src/test/java/com/google/protobuf/test_bad_identifiers.proto @@ -45,6 +45,7 @@ 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; |