diff options
author | kenton@google.com <kenton@google.com@630680e5-0e50-0410-840e-4b1c322b438d> | 2009-04-25 02:53:47 +0000 |
---|---|---|
committer | kenton@google.com <kenton@google.com@630680e5-0e50-0410-840e-4b1c322b438d> | 2009-04-25 02:53:47 +0000 |
commit | d37d46dfbcedadeb439ad0367f8afcf8867dca43 (patch) | |
tree | b896df229f7c671637924c156d5a759ba50a3190 /java/src | |
parent | 709ea28f3264aa5632e5577a4080671173fc6166 (diff) | |
download | protobuf-d37d46dfbcedadeb439ad0367f8afcf8867dca43.tar.gz protobuf-d37d46dfbcedadeb439ad0367f8afcf8867dca43.tar.bz2 protobuf-d37d46dfbcedadeb439ad0367f8afcf8867dca43.zip |
Integrate recent changes from Google-internal code tree. See CHANGES.txt
for details.
Diffstat (limited to 'java/src')
17 files changed, 696 insertions, 24 deletions
diff --git a/java/src/main/java/com/google/protobuf/AbstractMessage.java b/java/src/main/java/com/google/protobuf/AbstractMessage.java index 24c32a47..53175cdd 100644 --- a/java/src/main/java/com/google/protobuf/AbstractMessage.java +++ b/java/src/main/java/com/google/protobuf/AbstractMessage.java @@ -32,6 +32,7 @@ package com.google.protobuf; import com.google.protobuf.Descriptors.FieldDescriptor; +import java.io.FilterInputStream; import java.io.InputStream; import java.io.IOException; import java.io.OutputStream; @@ -152,6 +153,13 @@ public abstract class AbstractMessage implements Message { codedOutput.flush(); } + public void writeDelimitedTo(OutputStream output) throws IOException { + CodedOutputStream codedOutput = CodedOutputStream.newInstance(output); + codedOutput.writeRawVarint32(getSerializedSize()); + writeTo(codedOutput); + codedOutput.flush(); + } + private int memoizedSize = -1; public int getSerializedSize() { @@ -207,7 +215,8 @@ public abstract class AbstractMessage implements Message { if (getDescriptorForType() != otherMessage.getDescriptorForType()) { return false; } - return getAllFields().equals(otherMessage.getAllFields()); + return getAllFields().equals(otherMessage.getAllFields()) && + getUnknownFields().equals(otherMessage.getUnknownFields()); } @Override @@ -215,6 +224,7 @@ public abstract class AbstractMessage implements Message { int hash = 41; hash = (19 * hash) + getDescriptorForType().hashCode(); hash = (53 * hash) + getAllFields().hashCode(); + hash = (29 * hash) + getUnknownFields().hashCode(); return hash; } @@ -397,5 +407,51 @@ public abstract class AbstractMessage implements Message { codedInput.checkLastTagWas(0); return (BuilderType) this; } + + public BuilderType mergeDelimitedFrom(InputStream input, + ExtensionRegistry extensionRegistry) + throws IOException { + final int size = CodedInputStream.readRawVarint32(input); + + // A stream which will not read more than |size| bytes. + InputStream limitedInput = new FilterInputStream(input) { + int limit = size; + + @Override + public int available() throws IOException { + return Math.min(super.available(), limit); + } + + @Override + public int read() throws IOException { + if (limit <= 0) return -1; + int result = super.read(); + if (result >= 0) --limit; + return result; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (limit <= 0) return -1; + len = Math.min(len, limit); + int result = super.read(b, off, len); + if (result >= 0) limit -= result; + return result; + } + + @Override + public long skip(long n) throws IOException { + long result = super.skip(Math.min(n, limit)); + if (result >= 0) limit -= result; + return result; + } + }; + return mergeFrom(limitedInput, extensionRegistry); + } + + public BuilderType mergeDelimitedFrom(InputStream input) + throws IOException { + return mergeDelimitedFrom(input, ExtensionRegistry.getEmptyRegistry()); + } } } diff --git a/java/src/main/java/com/google/protobuf/BlockingRpcChannel.java b/java/src/main/java/com/google/protobuf/BlockingRpcChannel.java new file mode 100644 index 00000000..1e81143a --- /dev/null +++ b/java/src/main/java/com/google/protobuf/BlockingRpcChannel.java @@ -0,0 +1,51 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// http://code.google.com/p/protobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package com.google.protobuf; + +/** + * <p>Abstract interface for a blocking RPC channel. {@code BlockingRpcChannel} + * is the blocking equivalent to {@link RpcChannel}. + * + * @author kenton@google.com Kenton Varda + * @author cpovirk@google.com Chris Povirk + */ +public interface BlockingRpcChannel { + /** + * Call the given method of the remote service and blocks until it returns. + * {@code callBlockingMethod()} is the blocking equivalent to + * {@link RpcChannel#callMethod}. + */ + Message callBlockingMethod( + Descriptors.MethodDescriptor method, + RpcController controller, + Message request, + Message responsePrototype) throws ServiceException; +} diff --git a/java/src/main/java/com/google/protobuf/BlockingService.java b/java/src/main/java/com/google/protobuf/BlockingService.java new file mode 100644 index 00000000..ecc80096 --- /dev/null +++ b/java/src/main/java/com/google/protobuf/BlockingService.java @@ -0,0 +1,64 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// http://code.google.com/p/protobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package com.google.protobuf; + +/** + * Blocking equivalent to {@link Service}. + * + * @author kenton@google.com Kenton Varda + * @author cpovirk@google.com Chris Povirk + */ +public interface BlockingService { + /** + * Equivalent to {@link Service#getDescriptorForType}. + */ + Descriptors.ServiceDescriptor getDescriptorForType(); + + /** + * Equivalent to {@link Service#callMethod}, except that + * {@code callBlockingMethod()} returns the result of the RPC or throws a + * {@link ServiceException} if there is a failure, rather than passing the + * information to a callback. + */ + Message callBlockingMethod(Descriptors.MethodDescriptor method, + RpcController controller, + Message request) throws ServiceException; + + /** + * Equivalent to {@link Service#getRequestPrototype}. + */ + Message getRequestPrototype(Descriptors.MethodDescriptor method); + + /** + * Equivalent to {@link Service#getResponsePrototype}. + */ + Message getResponsePrototype(Descriptors.MethodDescriptor method); +} diff --git a/java/src/main/java/com/google/protobuf/CodedInputStream.java b/java/src/main/java/com/google/protobuf/CodedInputStream.java index 8f277a9f..b6be2e87 100644 --- a/java/src/main/java/com/google/protobuf/CodedInputStream.java +++ b/java/src/main/java/com/google/protobuf/CodedInputStream.java @@ -77,7 +77,7 @@ public final class CodedInputStream { * may legally end wherever a tag occurs, and zero is not a valid tag number. */ public int readTag() throws IOException { - if (bufferPos == bufferSize && !refillBuffer(false)) { + if (isAtEnd()) { lastTag = 0; return 0; } @@ -383,6 +383,39 @@ public final class CodedInputStream { return result; } + /** + * Reads a varint from the input one byte at a time, so that it does not + * read any bytes after the end of the varint. If you simply wrapped the + * stream in a CodedInputStream and used {@link #readRawVarint32(InputStream)} + * then you would probably end up reading past the end of the varint since + * CodedInputStream buffers its input. + */ + static int readRawVarint32(InputStream input) throws IOException { + int result = 0; + int offset = 0; + for (; offset < 32; offset += 7) { + int b = input.read(); + if (b == -1) { + throw InvalidProtocolBufferException.truncatedMessage(); + } + result |= (b & 0x7f) << offset; + if ((b & 0x80) == 0) { + return result; + } + } + // Keep reading up to 64 bits. + for (; offset < 64; offset += 7) { + int b = input.read(); + if (b == -1) { + throw InvalidProtocolBufferException.truncatedMessage(); + } + if ((b & 0x80) == 0) { + return result; + } + } + throw InvalidProtocolBufferException.malformedVarint(); + } + /** Read a raw Varint from the stream. */ public long readRawVarint64() throws IOException { int shift = 0; @@ -526,6 +559,10 @@ public final class CodedInputStream { * size limits only apply when reading from an {@code InputStream}, not * when constructed around a raw byte array (nor with * {@link ByteString#newCodedInput}). + * <p> + * If you want to read several messages from a single CodedInputStream, you + * could call {@link #resetSizeCounter()} after each one to avoid hitting the + * size limit. * * @return the old limit. */ @@ -540,6 +577,13 @@ public final class CodedInputStream { } /** + * Resets the current size counter to zero (see {@link #setSizeLimit(int)}). + */ + public void resetSizeCounter() { + totalBytesRetired = 0; + } + + /** * Sets {@code currentLimit} to (current position) + {@code byteLimit}. This * is called when descending into a length-delimited embedded message. * @@ -597,6 +641,15 @@ public final class CodedInputStream { } /** + * Returns true if the stream has reached the end of the input. This is the + * case if either the end of the underlying input source has been reached or + * if the stream has reached a limit created using {@link #pushLimit(int)}. + */ + public boolean isAtEnd() throws IOException { + return bufferPos == bufferSize && !refillBuffer(false); + } + + /** * Called with {@code this.buffer} is empty to read more bytes from the * input. If {@code mustSucceed} is true, refillBuffer() gurantees that * either there will be at least one byte in the buffer when it returns @@ -622,6 +675,11 @@ public final class CodedInputStream { bufferPos = 0; bufferSize = (input == null) ? -1 : input.read(buffer); + if (bufferSize == 0 || bufferSize < -1) { + throw new IllegalStateException( + "InputStream#read(byte[]) returned invalid result: " + bufferSize + + "\nThe InputStream implementation is buggy."); + } if (bufferSize == -1) { bufferSize = 0; if (mustSucceed) { @@ -778,7 +836,7 @@ public final class CodedInputStream { throw InvalidProtocolBufferException.truncatedMessage(); } - if (size < bufferSize - bufferPos) { + if (size <= bufferSize - bufferPos) { // We have all the bytes we need already. bufferPos += size; } else { diff --git a/java/src/main/java/com/google/protobuf/Descriptors.java b/java/src/main/java/com/google/protobuf/Descriptors.java index 80266a59..24499efb 100644 --- a/java/src/main/java/com/google/protobuf/Descriptors.java +++ b/java/src/main/java/com/google/protobuf/Descriptors.java @@ -1689,7 +1689,7 @@ public final class Descriptors { GenericDescriptor old = descriptorsByName.put(fullName, - new PackageDescriptor(fullName, name, file)); + new PackageDescriptor(name, fullName, file)); if (old != null) { descriptorsByName.put(fullName, old); if (!(old instanceof PackageDescriptor)) { diff --git a/java/src/main/java/com/google/protobuf/DynamicMessage.java b/java/src/main/java/com/google/protobuf/DynamicMessage.java index 99ae253c..59fb7b02 100644 --- a/java/src/main/java/com/google/protobuf/DynamicMessage.java +++ b/java/src/main/java/com/google/protobuf/DynamicMessage.java @@ -260,7 +260,8 @@ public final class DynamicMessage extends AbstractMessage { } public DynamicMessage build() { - if (!isInitialized()) { + // If fields == null, we'll throw an appropriate exception later. + if (fields != null && !isInitialized()) { throw new UninitializedMessageException( new DynamicMessage(type, fields, unknownFields)); } @@ -282,6 +283,10 @@ public final class DynamicMessage extends AbstractMessage { } public DynamicMessage buildPartial() { + if (fields == null) { + throw new IllegalStateException( + "build() has already been called on this Builder."); + } fields.makeImmutable(); DynamicMessage result = new DynamicMessage(type, fields, unknownFields); diff --git a/java/src/main/java/com/google/protobuf/Message.java b/java/src/main/java/com/google/protobuf/Message.java index 2f8700db..a5951232 100644 --- a/java/src/main/java/com/google/protobuf/Message.java +++ b/java/src/main/java/com/google/protobuf/Message.java @@ -183,9 +183,28 @@ public interface Message { * Serializes the message and writes it to {@code output}. This is just a * trivial wrapper around {@link #writeTo(CodedOutputStream)}. This does * not flush or close the stream. + * <p> + * NOTE: Protocol Buffers are not self-delimiting. Therefore, if you write + * any more data to the stream after the message, you must somehow ensure + * that the parser on the receiving end does not interpret this as being + * part of the protocol message. This can be done e.g. by writing the size + * of the message before the data, then making sure to limit the input to + * that size on the receiving end (e.g. by wrapping the InputStream in one + * which limits the input). Alternatively, just use + * {@link #writeDelimitedTo(OutputStream)}. */ void writeTo(OutputStream output) throws IOException; + /** + * Like {@link #writeTo(OutputStream)}, but writes the size of the message + * as a varint before writing the data. This allows more data to be written + * to the stream after the message without the need to delimit the message + * data yourself. Use {@link Builder#mergeDelimitedFrom(InputStream)} (or + * the static method {@code YourMessageType.parseDelimitedFrom(InputStream)}) + * to parse messages written by this method. + */ + void writeDelimitedTo(OutputStream output) throws IOException; + // ================================================================= // Builders @@ -434,8 +453,11 @@ public interface Message { * {@link #mergeFrom(CodedInputStream)}. Note that this method always * reads the <i>entire</i> input (unless it throws an exception). If you * want it to stop earlier, you will need to wrap your input in some - * wrapper stream that limits reading. Despite usually reading the entire - * input, this does not close the stream. + * wrapper stream that limits reading. Or, use + * {@link Message#writeDelimitedTo(OutputStream)} to write your message and + * {@link #mergeDelimitedFrom(InputStream)} to read it. + * <p> + * Despite usually reading the entire input, this does not close the stream. */ Builder mergeFrom(InputStream input) throws IOException; @@ -447,5 +469,22 @@ public interface Message { Builder mergeFrom(InputStream input, ExtensionRegistry extensionRegistry) throws IOException; + + /** + * Like {@link #mergeFrom(InputStream)}, but does not read until EOF. + * Instead, the size of the message (encoded as a varint) is read first, + * then the message data. Use + * {@link Message#writeDelimitedTo(OutputStream)} to write messages in this + * format. + */ + Builder mergeDelimitedFrom(InputStream input) + throws IOException; + + /** + * Like {@link #mergeDelimitedFrom(InputStream)} but supporting extensions. + */ + Builder mergeDelimitedFrom(InputStream input, + ExtensionRegistry extensionRegistry) + throws IOException; } } diff --git a/java/src/main/java/com/google/protobuf/RpcUtil.java b/java/src/main/java/com/google/protobuf/RpcUtil.java index a11b0437..8144bbfb 100644 --- a/java/src/main/java/com/google/protobuf/RpcUtil.java +++ b/java/src/main/java/com/google/protobuf/RpcUtil.java @@ -39,7 +39,7 @@ public final class RpcUtil { private RpcUtil() {} /** - * Take an {@code RcpCallabck<Message>} and convert it to an + * Take an {@code RpcCallback<Message>} and convert it to an * {@code RpcCallback} accepting a specific message type. This is always * type-safe (parameter type contravariance). */ @@ -58,8 +58,8 @@ public final class RpcUtil { } /** - * Take an {@code RcpCallabck} accepting a specific message type and convert - * it to an {@code RcpCallabck<Message>}. The generalized callback will + * Take an {@code RpcCallback} accepting a specific message type and convert + * it to an {@code RpcCallback<Message>}. The generalized callback will * accept any message object which has the same descriptor, and will convert * it to the correct class before calling the original callback. However, * if the generalized callback is given a message with a different descriptor, diff --git a/java/src/main/java/com/google/protobuf/ServiceException.java b/java/src/main/java/com/google/protobuf/ServiceException.java new file mode 100644 index 00000000..70b9d0c1 --- /dev/null +++ b/java/src/main/java/com/google/protobuf/ServiceException.java @@ -0,0 +1,42 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// http://code.google.com/p/protobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package com.google.protobuf; + +/** + * Thrown by blocking RPC methods when a failure occurs. + * + * @author cpovirk@google.com (Chris Povirk) + */ +public final class ServiceException extends Exception { + public ServiceException(String message) { + super(message); + } +} diff --git a/java/src/main/java/com/google/protobuf/UnknownFieldSet.java b/java/src/main/java/com/google/protobuf/UnknownFieldSet.java index 445951b0..77fef323 100644 --- a/java/src/main/java/com/google/protobuf/UnknownFieldSet.java +++ b/java/src/main/java/com/google/protobuf/UnknownFieldSet.java @@ -34,6 +34,7 @@ import java.io.InputStream; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.TreeMap; import java.util.List; @@ -85,6 +86,20 @@ public final class UnknownFieldSet { } private Map<Integer, Field> fields; + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + return (other instanceof UnknownFieldSet) && + this.fields.equals(((UnknownFieldSet) other).fields); + } + + @Override + public int hashCode() { + return this.fields.hashCode(); + } + /** Get a map of fields in the set by number. */ public Map<Integer, Field> asMap() { return fields; @@ -540,6 +555,36 @@ public final class UnknownFieldSet { */ public List<UnknownFieldSet> getGroupList() { return group; } + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof Field)) { + return false; + } + return Arrays.equals(this.getIdentityArray(), + ((Field) other).getIdentityArray()); + } + + @Override + public int hashCode() { + return Arrays.hashCode(getIdentityArray()); + } + + /** + * Returns the array of objects to be used to uniquely identify this + * {@link UnknownFieldSet.Field} instance. + */ + private Object[] getIdentityArray() { + return new Object[] { + this.varint, + this.fixed32, + this.fixed64, + this.lengthDelimited, + this.group}; + } + /** * Serializes the field, including field number, and writes it to * {@code output}. diff --git a/java/src/test/java/com/google/protobuf/AbstractMessageTest.java b/java/src/test/java/com/google/protobuf/AbstractMessageTest.java index da073561..2c59fd0d 100644 --- a/java/src/test/java/com/google/protobuf/AbstractMessageTest.java +++ b/java/src/test/java/com/google/protobuf/AbstractMessageTest.java @@ -30,14 +30,14 @@ package com.google.protobuf; +import protobuf_unittest.UnittestOptimizeFor.TestOptimizedForSize; import protobuf_unittest.UnittestProto; import protobuf_unittest.UnittestProto.ForeignMessage; -import protobuf_unittest.UnittestProto.TestAllTypes; import protobuf_unittest.UnittestProto.TestAllExtensions; +import protobuf_unittest.UnittestProto.TestAllTypes; import protobuf_unittest.UnittestProto.TestPackedTypes; import protobuf_unittest.UnittestProto.TestRequired; import protobuf_unittest.UnittestProto.TestRequiredForeign; -import protobuf_unittest.UnittestOptimizeFor.TestOptimizedForSize; import junit.framework.TestCase; @@ -329,7 +329,7 @@ public class AbstractMessageTest extends TestCase { // ----------------------------------------------------------------- // Tests for equals and hashCode - public void testEqualsAndHashCode() { + public void testEqualsAndHashCode() throws Exception { TestAllTypes a = TestUtil.getAllSet(); TestAllTypes b = TestAllTypes.newBuilder().build(); TestAllTypes c = TestAllTypes.newBuilder(b).addRepeatedString("x").build(); @@ -337,7 +337,7 @@ public class AbstractMessageTest extends TestCase { TestAllExtensions e = TestUtil.getAllExtensionsSet(); TestAllExtensions f = TestAllExtensions.newBuilder(e) .addExtension(UnittestProto.repeatedInt32Extension, 999).build(); - + checkEqualsIsConsistent(a); checkEqualsIsConsistent(b); checkEqualsIsConsistent(c); @@ -364,10 +364,25 @@ public class AbstractMessageTest extends TestCase { checkNotEqual(d, f); checkNotEqual(e, f); + + // Deserializing into the TestEmptyMessage such that every field + // is an {@link UnknownFieldSet.Field}. + UnittestProto.TestEmptyMessage eUnknownFields = + UnittestProto.TestEmptyMessage.parseFrom(e.toByteArray()); + UnittestProto.TestEmptyMessage fUnknownFields = + UnittestProto.TestEmptyMessage.parseFrom(f.toByteArray()); + checkNotEqual(eUnknownFields, fUnknownFields); + checkEqualsIsConsistent(eUnknownFields); + checkEqualsIsConsistent(fUnknownFields); + + // Subseqent reconstitutions should be identical + UnittestProto.TestEmptyMessage eUnknownFields2 = + UnittestProto.TestEmptyMessage.parseFrom(e.toByteArray()); + checkEqualsIsConsistent(eUnknownFields, eUnknownFields2); } /** - * Asserts that the given protos are equal and have the same hash code. + * Asserts that the given proto has symetric equals and hashCode methods. */ private void checkEqualsIsConsistent(Message message) { // Object should be equal to itself. @@ -375,9 +390,16 @@ public class AbstractMessageTest extends TestCase { // Object should be equal to a dynamic copy of itself. DynamicMessage dynamic = DynamicMessage.newBuilder(message).build(); - assertEquals(message, dynamic); - assertEquals(dynamic, message); - assertEquals(dynamic.hashCode(), message.hashCode()); + checkEqualsIsConsistent(message, dynamic); + } + + /** + * Asserts that the given protos are equal and have the same hash code. + */ + private void checkEqualsIsConsistent(Message message1, Message message2) { + assertEquals(message1, message2); + assertEquals(message2, message1); + assertEquals(message2.hashCode(), message1.hashCode()); } /** diff --git a/java/src/test/java/com/google/protobuf/CodedInputStreamTest.java b/java/src/test/java/com/google/protobuf/CodedInputStreamTest.java index eaaddf44..850b8aa7 100644 --- a/java/src/test/java/com/google/protobuf/CodedInputStreamTest.java +++ b/java/src/test/java/com/google/protobuf/CodedInputStreamTest.java @@ -95,6 +95,7 @@ public class CodedInputStreamTest extends TestCase { input = CodedInputStream.newInstance(data); assertEquals(value, input.readRawVarint64()); + assertTrue(input.isAtEnd()); // Try different block sizes. for (int blockSize = 1; blockSize <= 16; blockSize *= 2) { @@ -105,7 +106,17 @@ public class CodedInputStreamTest extends TestCase { input = CodedInputStream.newInstance( new SmallBlockInputStream(data, blockSize)); assertEquals(value, input.readRawVarint64()); + assertTrue(input.isAtEnd()); } + + // Try reading direct from an InputStream. We want to verify that it + // doesn't read past the end of the input, so we copy to a new, bigger + // array first. + byte[] longerData = new byte[data.length + 1]; + System.arraycopy(data, 0, longerData, 0, data.length); + InputStream rawInput = new ByteArrayInputStream(longerData); + assertEquals((int)value, CodedInputStream.readRawVarint32(rawInput)); + assertEquals(1, rawInput.available()); } /** @@ -131,6 +142,14 @@ public class CodedInputStreamTest extends TestCase { } catch (InvalidProtocolBufferException e) { assertEquals(expected.getMessage(), e.getMessage()); } + + // Make sure we get the same error when reading direct from an InputStream. + try { + CodedInputStream.readRawVarint32(new ByteArrayInputStream(data)); + fail("Should have thrown an exception."); + } catch (InvalidProtocolBufferException e) { + assertEquals(expected.getMessage(), e.getMessage()); + } } /** Tests readRawVarint32() and readRawVarint64(). */ @@ -180,12 +199,14 @@ public class CodedInputStreamTest extends TestCase { throws Exception { CodedInputStream input = CodedInputStream.newInstance(data); assertEquals(value, input.readRawLittleEndian32()); + assertTrue(input.isAtEnd()); // Try different block sizes. for (int blockSize = 1; blockSize <= 16; blockSize *= 2) { input = CodedInputStream.newInstance( new SmallBlockInputStream(data, blockSize)); assertEquals(value, input.readRawLittleEndian32()); + assertTrue(input.isAtEnd()); } } @@ -197,12 +218,14 @@ public class CodedInputStreamTest extends TestCase { throws Exception { CodedInputStream input = CodedInputStream.newInstance(data); assertEquals(value, input.readRawLittleEndian64()); + assertTrue(input.isAtEnd()); // Try different block sizes. for (int blockSize = 1; blockSize <= 16; blockSize *= 2) { input = CodedInputStream.newInstance( new SmallBlockInputStream(data, blockSize)); assertEquals(value, input.readRawLittleEndian64()); + assertTrue(input.isAtEnd()); } } @@ -288,6 +311,20 @@ public class CodedInputStreamTest extends TestCase { } } + /** + * Test that a bug in skipRawBytes() has been fixed: if the skip skips + * exactly up to a limit, this should not break things. + */ + public void testSkipRawBytesBug() throws Exception { + byte[] rawBytes = new byte[] { 1, 2 }; + CodedInputStream input = CodedInputStream.newInstance(rawBytes); + + int limit = input.pushLimit(1); + input.skipRawBytes(1); + input.popLimit(limit); + assertEquals(2, input.readRawByte()); + } + public void testReadHugeBlob() throws Exception { // Allocate and initialize a 1MB blob. byte[] blob = new byte[1 << 20]; @@ -392,6 +429,30 @@ public class CodedInputStreamTest extends TestCase { } } + public void testResetSizeCounter() throws Exception { + CodedInputStream input = CodedInputStream.newInstance( + new SmallBlockInputStream(new byte[256], 8)); + input.setSizeLimit(16); + input.readRawBytes(16); + + try { + input.readRawByte(); + fail("Should have thrown an exception!"); + } catch (InvalidProtocolBufferException e) { + // success. + } + + input.resetSizeCounter(); + input.readRawByte(); // No exception thrown. + + try { + input.readRawBytes(16); // Hits limit again. + fail("Should have thrown an exception!"); + } catch (InvalidProtocolBufferException e) { + // success. + } + } + /** * Tests that if we read an string that contains invalid UTF-8, no exception * is thrown. Instead, the invalid bytes are replaced with the Unicode diff --git a/java/src/test/java/com/google/protobuf/DynamicMessageTest.java b/java/src/test/java/com/google/protobuf/DynamicMessageTest.java index fc53c531..120b0f13 100644 --- a/java/src/test/java/com/google/protobuf/DynamicMessageTest.java +++ b/java/src/test/java/com/google/protobuf/DynamicMessageTest.java @@ -61,6 +61,18 @@ public class DynamicMessageTest extends TestCase { reflectionTester.assertAllFieldsSetViaReflection(message); } + public void testDoubleBuildError() throws Exception { + Message.Builder builder = + DynamicMessage.newBuilder(TestAllTypes.getDescriptor()); + builder.build(); + try { + builder.build(); + fail("Should have thrown exception."); + } catch (IllegalStateException e) { + // Success. + } + } + public void testDynamicMessageSettersRejectNull() throws Exception { Message.Builder builder = DynamicMessage.newBuilder(TestAllTypes.getDescriptor()); diff --git a/java/src/test/java/com/google/protobuf/GeneratedMessageTest.java b/java/src/test/java/com/google/protobuf/GeneratedMessageTest.java index 04ba769e..58d82193 100644 --- a/java/src/test/java/com/google/protobuf/GeneratedMessageTest.java +++ b/java/src/test/java/com/google/protobuf/GeneratedMessageTest.java @@ -71,6 +71,17 @@ public class GeneratedMessageTest extends TestCase { TestUtil.assertAllFieldsSet(message); } + public void testDoubleBuildError() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + builder.build(); + try { + builder.build(); + fail("Should have thrown exception."); + } catch (IllegalStateException e) { + // Success. + } + } + public void testSettersRejectNull() throws Exception { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); try { diff --git a/java/src/test/java/com/google/protobuf/ServiceTest.java b/java/src/test/java/com/google/protobuf/ServiceTest.java index 6a747f48..d8523ea3 100644 --- a/java/src/test/java/com/google/protobuf/ServiceTest.java +++ b/java/src/test/java/com/google/protobuf/ServiceTest.java @@ -30,6 +30,10 @@ package com.google.protobuf; +import com.google.protobuf.Descriptors.MethodDescriptor; +import protobuf_unittest.MessageWithNoOuter; +import protobuf_unittest.ServiceWithNoOuter; +import protobuf_unittest.UnittestProto.TestAllTypes; import protobuf_unittest.UnittestProto.TestService; import protobuf_unittest.UnittestProto.FooRequest; import protobuf_unittest.UnittestProto.FooResponse; @@ -56,6 +60,7 @@ public class ServiceTest extends TestCase { private final Descriptors.MethodDescriptor barDescriptor = TestService.getDescriptor().getMethods().get(1); + @Override protected void setUp() throws Exception { super.setUp(); control = EasyMock.createStrictControl(); @@ -127,6 +132,94 @@ public class ServiceTest extends TestCase { control.verify(); } + /** Tests generated blocking stubs. */ + public void testBlockingStub() throws Exception { + FooRequest fooRequest = FooRequest.newBuilder().build(); + BarRequest barRequest = BarRequest.newBuilder().build(); + BlockingRpcChannel mockChannel = + control.createMock(BlockingRpcChannel.class); + TestService.BlockingInterface stub = + TestService.newBlockingStub(mockChannel); + + FooResponse fooResponse = FooResponse.newBuilder().build(); + BarResponse barResponse = BarResponse.newBuilder().build(); + + EasyMock.expect(mockChannel.callBlockingMethod( + EasyMock.same(fooDescriptor), + EasyMock.same(mockController), + EasyMock.same(fooRequest), + EasyMock.same(FooResponse.getDefaultInstance()))).andReturn(fooResponse); + EasyMock.expect(mockChannel.callBlockingMethod( + EasyMock.same(barDescriptor), + EasyMock.same(mockController), + EasyMock.same(barRequest), + EasyMock.same(BarResponse.getDefaultInstance()))).andReturn(barResponse); + control.replay(); + + assertSame(fooResponse, stub.foo(mockController, fooRequest)); + assertSame(barResponse, stub.bar(mockController, barRequest)); + control.verify(); + } + + public void testNewReflectiveService() { + ServiceWithNoOuter.Interface impl = + control.createMock(ServiceWithNoOuter.Interface.class); + RpcController controller = control.createMock(RpcController.class); + Service service = ServiceWithNoOuter.newReflectiveService(impl); + + MethodDescriptor fooMethod = + ServiceWithNoOuter.getDescriptor().findMethodByName("Foo"); + MessageWithNoOuter request = MessageWithNoOuter.getDefaultInstance(); + RpcCallback<Message> callback = new RpcCallback<Message>() { + public void run(Message parameter) { + // No reason this should be run. + fail(); + } + }; + RpcCallback<TestAllTypes> specializedCallback = + RpcUtil.specializeCallback(callback); + + impl.foo(EasyMock.same(controller), EasyMock.same(request), + EasyMock.same(specializedCallback)); + EasyMock.expectLastCall(); + + control.replay(); + + service.callMethod(fooMethod, controller, request, callback); + + control.verify(); + } + + public void testNewReflectiveBlockingService() throws ServiceException { + ServiceWithNoOuter.BlockingInterface impl = + control.createMock(ServiceWithNoOuter.BlockingInterface.class); + RpcController controller = control.createMock(RpcController.class); + BlockingService service = + ServiceWithNoOuter.newReflectiveBlockingService(impl); + + MethodDescriptor fooMethod = + ServiceWithNoOuter.getDescriptor().findMethodByName("Foo"); + MessageWithNoOuter request = MessageWithNoOuter.getDefaultInstance(); + RpcCallback<Message> callback = new RpcCallback<Message>() { + public void run(Message parameter) { + // No reason this should be run. + fail(); + } + }; + + TestAllTypes expectedResponse = TestAllTypes.getDefaultInstance(); + EasyMock.expect(impl.foo(EasyMock.same(controller), EasyMock.same(request))) + .andReturn(expectedResponse); + + control.replay(); + + Message response = + service.callBlockingMethod(fooMethod, controller, request); + assertEquals(expectedResponse, response); + + control.verify(); + } + // ================================================================= /** @@ -135,7 +228,7 @@ public class ServiceTest extends TestCase { * In other words, c wraps the given callback. */ private <Type extends Message> RpcCallback<Type> wrapsCallback( - MockCallback callback) { + MockCallback<?> callback) { EasyMock.reportMatcher(new WrapsCallback(callback)); return null; } @@ -153,9 +246,9 @@ public class ServiceTest extends TestCase { /** Implementation of the wrapsCallback() argument matcher. */ private static class WrapsCallback implements IArgumentMatcher { - private MockCallback callback; + private MockCallback<?> callback; - public WrapsCallback(MockCallback callback) { + public WrapsCallback(MockCallback<?> callback) { this.callback = callback; } diff --git a/java/src/test/java/com/google/protobuf/UnknownFieldSetTest.java b/java/src/test/java/com/google/protobuf/UnknownFieldSetTest.java index 212b381f..ea088b32 100644 --- a/java/src/test/java/com/google/protobuf/UnknownFieldSetTest.java +++ b/java/src/test/java/com/google/protobuf/UnknownFieldSetTest.java @@ -31,13 +31,13 @@ package com.google.protobuf; import protobuf_unittest.UnittestProto; -import protobuf_unittest.UnittestProto.TestAllTypes; import protobuf_unittest.UnittestProto.TestAllExtensions; +import protobuf_unittest.UnittestProto.TestAllTypes; import protobuf_unittest.UnittestProto.TestEmptyMessage; -import protobuf_unittest.UnittestProto. - TestEmptyMessageWithExtensions; +import protobuf_unittest.UnittestProto.TestEmptyMessageWithExtensions; import junit.framework.TestCase; + import java.util.Arrays; import java.util.Map; @@ -341,4 +341,97 @@ public class UnknownFieldSetTest extends TestCase { assertEquals(1, field.getVarintList().size()); assertEquals(0x7FFFFFFFFFFFFFFFL, (long)field.getVarintList().get(0)); } + + public void testEqualsAndHashCode() { + UnknownFieldSet.Field fixed32Field = + UnknownFieldSet.Field.newBuilder() + .addFixed32(1) + .build(); + UnknownFieldSet.Field fixed64Field = + UnknownFieldSet.Field.newBuilder() + .addFixed64(1) + .build(); + UnknownFieldSet.Field varIntField = + UnknownFieldSet.Field.newBuilder() + .addVarint(1) + .build(); + UnknownFieldSet.Field lengthDelimitedField = + UnknownFieldSet.Field.newBuilder() + .addLengthDelimited(ByteString.EMPTY) + .build(); + UnknownFieldSet.Field groupField = + UnknownFieldSet.Field.newBuilder() + .addGroup(unknownFields) + .build(); + + UnknownFieldSet a = + UnknownFieldSet.newBuilder() + .addField(1, fixed32Field) + .build(); + UnknownFieldSet b = + UnknownFieldSet.newBuilder() + .addField(1, fixed64Field) + .build(); + UnknownFieldSet c = + UnknownFieldSet.newBuilder() + .addField(1, varIntField) + .build(); + UnknownFieldSet d = + UnknownFieldSet.newBuilder() + .addField(1, lengthDelimitedField) + .build(); + UnknownFieldSet e = + UnknownFieldSet.newBuilder() + .addField(1, groupField) + .build(); + + checkEqualsIsConsistent(a); + checkEqualsIsConsistent(b); + checkEqualsIsConsistent(c); + checkEqualsIsConsistent(d); + checkEqualsIsConsistent(e); + + checkNotEqual(a, b); + checkNotEqual(a, c); + checkNotEqual(a, d); + checkNotEqual(a, e); + checkNotEqual(b, c); + checkNotEqual(b, d); + checkNotEqual(b, e); + checkNotEqual(c, d); + checkNotEqual(c, e); + checkNotEqual(d, e); + } + + /** + * Asserts that the given field sets are not equal and have different + * hash codes. + * + * @warning It's valid for non-equal objects to have the same hash code, so + * this test is stricter than it needs to be. However, this should happen + * relatively rarely. + */ + private void checkNotEqual(UnknownFieldSet s1, UnknownFieldSet s2) { + String equalsError = String.format("%s should not be equal to %s", s1, s2); + assertFalse(equalsError, s1.equals(s2)); + assertFalse(equalsError, s2.equals(s1)); + + assertFalse( + String.format("%s should have a different hash code from %s", s1, s2), + s1.hashCode() == s2.hashCode()); + } + + /** + * Asserts that the given field sets are equal and have identical hash codes. + */ + private void checkEqualsIsConsistent(UnknownFieldSet set) { + // Object should be equal to itself. + assertEquals(set, set); + + // Object should be equal to a copy of itself. + UnknownFieldSet copy = UnknownFieldSet.newBuilder(set).build(); + assertEquals(set, copy); + assertEquals(copy, set); + assertEquals(set.hashCode(), copy.hashCode()); + } } diff --git a/java/src/test/java/com/google/protobuf/WireFormatTest.java b/java/src/test/java/com/google/protobuf/WireFormatTest.java index 48453faf..4afefdb6 100644 --- a/java/src/test/java/com/google/protobuf/WireFormatTest.java +++ b/java/src/test/java/com/google/protobuf/WireFormatTest.java @@ -31,6 +31,10 @@ package com.google.protobuf; import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; + import protobuf_unittest.UnittestProto; import protobuf_unittest.UnittestProto.TestAllExtensions; import protobuf_unittest.UnittestProto.TestAllTypes; @@ -130,6 +134,22 @@ public class WireFormatTest extends TestCase { TestUtil.getAllExtensionsSet().getSerializedSize()); } + public void testSerializeDelimited() throws Exception { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + TestUtil.getAllSet().writeDelimitedTo(output); + output.write(12); + TestUtil.getPackedSet().writeDelimitedTo(output); + output.write(34); + + ByteArrayInputStream input = new ByteArrayInputStream(output.toByteArray()); + + TestUtil.assertAllFieldsSet(TestAllTypes.parseDelimitedFrom(input)); + assertEquals(12, input.read()); + TestUtil.assertPackedFieldsSet(TestPackedTypes.parseDelimitedFrom(input)); + assertEquals(34, input.read()); + assertEquals(-1, input.read()); + } + private void assertFieldsInOrder(ByteString data) throws Exception { CodedInputStream input = data.newCodedInput(); int previousTag = 0; |