diff options
author | Jon Skeet <skeet@pobox.com> | 2008-08-14 20:33:26 +0100 |
---|---|---|
committer | unknown <Jon@.(none)> | 2008-08-14 20:33:26 +0100 |
commit | f92c545f493dce6e5a2e378e8268c1e7b0e55a4d (patch) | |
tree | 7c2a4a78ca01c82d165c9ece810be052ff6cc691 /java | |
download | protobuf-f92c545f493dce6e5a2e378e8268c1e7b0e55a4d.tar.gz protobuf-f92c545f493dce6e5a2e378e8268c1e7b0e55a4d.tar.bz2 protobuf-f92c545f493dce6e5a2e378e8268c1e7b0e55a4d.zip |
Initial commit of C# code developed before installing Git.
committer: Jon Skeet <skeet@pobox.com>
Diffstat (limited to 'java')
35 files changed, 15466 insertions, 0 deletions
diff --git a/java/README.txt b/java/README.txt new file mode 100644 index 00000000..050bbfe7 --- /dev/null +++ b/java/README.txt @@ -0,0 +1,74 @@ +Protocol Buffers - Google's data interchange format +Copyright 2008 Google Inc. + +This directory contains the Java Protocol Buffers runtime library. + +Installation - With Maven +========================= + +The Protocol Buffers build is managed using Maven. If you would +rather build without Maven, see the next section. + +1) Install Apache Maven if you don't have it: + + http://maven.apache.org/ + +2) Build the C++ code, or obtain a binary distribution of protoc. If + you install a binary distribution, make sure that it is the same + version as this package. If in doubt, run: + + $ protoc --version + + You will need to place the protoc executable in ../src. (If you + built it yourself, it should already be there.) + +3) Run the tests: + + $ mvn test + + If some tests fail, this library may not work correctly on your + system. Continue at your own risk. + +4) Install the library into your Maven repository: + + $ mvn install + +5) If you do not use Maven to manage your own build, you can build a + .jar file to use: + + $ mvn package + + The .jar will be placed in the "target" directory. + +Installation - Without Maven +============================ + +If you would rather not install Maven to build the library, you may +follow these instructions instead. Note that these instructions skip +running unit tests. + +1) Build the C++ code, or obtain a binary distribution of protoc. If + you install a binary distribution, make sure that it is the same + version as this package. If in doubt, run: + + $ protoc --version + + If you built the C++ code without installing, the compiler binary + should be located in ../src. + +2) Invoke protoc to build DescriptorProtos.java: + + $ protoc --java_out=src/main/java -I../src \ + ../src/google/protobuf/descriptor.proto + +3) Compile the code in src/main/java using whatever means you prefer. + +4) Install the classes wherever you prefer. + +Usage +===== + +The complete documentation for Protocol Buffers is available via the +web at: + + http://code.google.com/apis/protocolbuffers/ diff --git a/java/pom.xml b/java/pom.xml new file mode 100644 index 00000000..1bb26b2e --- /dev/null +++ b/java/pom.xml @@ -0,0 +1,125 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <!-- This is commented until the parent can be deployed to a repository. --> + <!-- + <parent> + <groupId>com</groupId> + <artifactId>google</artifactId> + <version>1.0-SNAPSHOT</version> + </parent> + --> + <groupId>com.google.protobuf</groupId> + <artifactId>protobuf-java</artifactId> + <version>2.0.1-SNAPSHOT</version> + <packaging>jar</packaging> + <name>Protocol Buffer Java API</name> + <description> + Protocol Buffers are a way of encoding structured data in an efficient yet + extensible format. + </description> + <inceptionYear>2008</inceptionYear> + <url>http://code.google.com/p/protobuf</url> + <licenses> + <license> + <name>The Apache Software License, Version 2.0</name> + <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url> + <distribution>repo</distribution> + </license> + </licenses> + <scm> + <url>http://code.google.com/p/protobuf/source/browse</url> + <connection> + scm:svn:http://protobuf.googlecode.com/svn/trunk/ + </connection> + </scm> + <dependencies> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>4.4</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.easymock</groupId> + <artifactId>easymock</artifactId> + <version>2.2</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.easymock</groupId> + <artifactId>easymockclassextension</artifactId> + <version>2.2.1</version> + <scope>test</scope> + </dependency> + </dependencies> + <build> + <plugins> + <plugin> + <artifactId>maven-compiler-plugin</artifactId> + <configuration> + <source>1.5</source> + <target>1.5</target> + </configuration> + </plugin> + <plugin> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <includes> + <include>**/*Test.java</include> + </includes> + </configuration> + </plugin> + <plugin> + <artifactId>maven-antrun-plugin</artifactId> + <executions> + <execution> + <id>generate-sources</id> + <phase>generate-sources</phase> + <configuration> + <tasks> + <mkdir dir="target/generated-sources" /> + <exec executable="../src/protoc"> + <arg value="--java_out=target/generated-sources" /> + <arg value="--proto_path=../src" /> + <arg value="../src/google/protobuf/descriptor.proto" /> + </exec> + </tasks> + <sourceRoot>target/generated-sources</sourceRoot> + </configuration> + <goals> + <goal>run</goal> + </goals> + </execution> + <execution> + <id>generate-test-sources</id> + <phase>generate-test-sources</phase> + <configuration> + <tasks> + <mkdir dir="target/generated-test-sources" /> + <exec executable="../src/protoc"> + <arg value="--java_out=target/generated-test-sources" /> + <arg value="--proto_path=../src" /> + <arg value="--proto_path=src/test/java" /> + <arg value="../src/google/protobuf/unittest.proto" /> + <arg value="../src/google/protobuf/unittest_import.proto" /> + <arg value="../src/google/protobuf/unittest_mset.proto" /> + <arg + value="src/test/java/com/google/protobuf/multiple_files_test.proto" /> + <arg + value="../src/google/protobuf/unittest_optimize_for.proto" /> + </exec> + </tasks> + <testSourceRoot>target/generated-test-sources</testSourceRoot> + </configuration> + <goals> + <goal>run</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> +</project> diff --git a/java/src/main/java/com/google/protobuf/AbstractMessage.java b/java/src/main/java/com/google/protobuf/AbstractMessage.java new file mode 100644 index 00000000..f4145190 --- /dev/null +++ b/java/src/main/java/com/google/protobuf/AbstractMessage.java @@ -0,0 +1,343 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.protobuf; + +import com.google.protobuf.Descriptors.FieldDescriptor; + +import java.io.InputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.List; +import java.util.Map; + +/** + * A partial implementation of the {@link Message} interface which implements + * as many methods of that interface as possible in terms of other methods. + * + * @author kenton@google.com Kenton Varda + */ +public abstract class AbstractMessage implements Message { + @SuppressWarnings("unchecked") + public boolean isInitialized() { + // Check that all required fields are present. + for (FieldDescriptor field : getDescriptorForType().getFields()) { + if (field.isRequired()) { + if (!hasField(field)) { + return false; + } + } + } + + // Check that embedded messages are initialized. + for (Map.Entry<FieldDescriptor, Object> entry : getAllFields().entrySet()) { + FieldDescriptor field = entry.getKey(); + if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + if (field.isRepeated()) { + for (Message element : (List<Message>) entry.getValue()) { + if (!element.isInitialized()) { + return false; + } + } + } else { + if (!((Message) entry.getValue()).isInitialized()) { + return false; + } + } + } + } + + return true; + } + + public final String toString() { + return TextFormat.printToString(this); + } + + public void writeTo(CodedOutputStream output) throws IOException { + for (Map.Entry<FieldDescriptor, Object> entry : getAllFields().entrySet()) { + FieldDescriptor field = entry.getKey(); + if (field.isRepeated()) { + for (Object element : (List) entry.getValue()) { + output.writeField(field.getType(), field.getNumber(), element); + } + } else { + output.writeField(field.getType(), field.getNumber(), entry.getValue()); + } + } + + UnknownFieldSet unknownFields = getUnknownFields(); + if (getDescriptorForType().getOptions().getMessageSetWireFormat()) { + unknownFields.writeAsMessageSetTo(output); + } else { + unknownFields.writeTo(output); + } + } + + public ByteString toByteString() { + try { + ByteString.CodedBuilder out = + ByteString.newCodedBuilder(getSerializedSize()); + writeTo(out.getCodedOutput()); + return out.build(); + } catch (IOException e) { + throw new RuntimeException( + "Serializing to a ByteString threw an IOException (should " + + "never happen).", e); + } + } + + public byte[] toByteArray() { + try { + byte[] result = new byte[getSerializedSize()]; + CodedOutputStream output = CodedOutputStream.newInstance(result); + writeTo(output); + output.checkNoSpaceLeft(); + return result; + } catch (IOException e) { + throw new RuntimeException( + "Serializing to a byte array threw an IOException " + + "(should never happen).", e); + } + } + + public void writeTo(OutputStream output) throws IOException { + CodedOutputStream codedOutput = CodedOutputStream.newInstance(output); + writeTo(codedOutput); + codedOutput.flush(); + } + + private int memoizedSize = -1; + + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + for (Map.Entry<FieldDescriptor, Object> entry : getAllFields().entrySet()) { + FieldDescriptor field = entry.getKey(); + if (field.isRepeated()) { + for (Object element : (List) entry.getValue()) { + size += CodedOutputStream.computeFieldSize( + field.getType(), field.getNumber(), element); + } + } else { + size += CodedOutputStream.computeFieldSize( + field.getType(), field.getNumber(), entry.getValue()); + } + } + + UnknownFieldSet unknownFields = getUnknownFields(); + if (getDescriptorForType().getOptions().getMessageSetWireFormat()) { + size += unknownFields.getSerializedSizeAsMessageSet(); + } else { + size += unknownFields.getSerializedSize(); + } + + memoizedSize = size; + return size; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (!(other instanceof Message)) { + return false; + } + Message otherMessage = (Message) other; + if (getDescriptorForType() != otherMessage.getDescriptorForType()) { + return false; + } + return getAllFields().equals(otherMessage.getAllFields()); + } + + @Override + public int hashCode() { + int hash = 41; + hash = (19 * hash) + getDescriptorForType().hashCode(); + hash = (53 * hash) + getAllFields().hashCode(); + return hash; + } + + // ================================================================= + + /** + * A partial implementation of the {@link Message.Builder} interface which + * implements as many methods of that interface as possible in terms of + * other methods. + */ + @SuppressWarnings("unchecked") + public static abstract class Builder<BuilderType extends Builder> + implements Message.Builder { + // The compiler produces an error if this is not declared explicitly. + public abstract BuilderType clone(); + + public BuilderType clear() { + for (Map.Entry<FieldDescriptor, Object> entry : + getAllFields().entrySet()) { + clearField(entry.getKey()); + } + return (BuilderType) this; + } + + public BuilderType mergeFrom(Message other) { + if (other.getDescriptorForType() != getDescriptorForType()) { + throw new IllegalArgumentException( + "mergeFrom(Message) can only merge messages of the same type."); + } + + // Note: We don't attempt to verify that other's fields have valid + // types. Doing so would be a losing battle. We'd have to verify + // all sub-messages as well, and we'd have to make copies of all of + // them to insure that they don't change after verification (since + // the Message interface itself cannot enforce immutability of + // implementations). + // TODO(kenton): Provide a function somewhere called makeDeepCopy() + // which allows people to make secure deep copies of messages. + + for (Map.Entry<FieldDescriptor, Object> entry : + other.getAllFields().entrySet()) { + FieldDescriptor field = entry.getKey(); + if (field.isRepeated()) { + for (Object element : (List)entry.getValue()) { + addRepeatedField(field, element); + } + } else if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + Message existingValue = (Message)getField(field); + if (existingValue == existingValue.getDefaultInstanceForType()) { + setField(field, entry.getValue()); + } else { + setField(field, + existingValue.newBuilderForType() + .mergeFrom(existingValue) + .mergeFrom((Message)entry.getValue()) + .build()); + } + } else { + setField(field, entry.getValue()); + } + } + + return (BuilderType) this; + } + + public BuilderType mergeFrom(CodedInputStream input) throws IOException { + return mergeFrom(input, ExtensionRegistry.getEmptyRegistry()); + } + + public BuilderType mergeFrom(CodedInputStream input, + ExtensionRegistry extensionRegistry) + throws IOException { + UnknownFieldSet.Builder unknownFields = + UnknownFieldSet.newBuilder(getUnknownFields()); + FieldSet.mergeFrom(input, unknownFields, extensionRegistry, this); + setUnknownFields(unknownFields.build()); + return (BuilderType) this; + } + + public BuilderType mergeUnknownFields(UnknownFieldSet unknownFields) { + setUnknownFields( + UnknownFieldSet.newBuilder(getUnknownFields()) + .mergeFrom(unknownFields) + .build()); + return (BuilderType) this; + } + + public BuilderType mergeFrom(ByteString data) + throws InvalidProtocolBufferException { + try { + CodedInputStream input = data.newCodedInput(); + mergeFrom(input); + input.checkLastTagWas(0); + return (BuilderType) this; + } catch (InvalidProtocolBufferException e) { + throw e; + } catch (IOException e) { + throw new RuntimeException( + "Reading from a ByteString threw an IOException (should " + + "never happen).", e); + } + } + + public BuilderType mergeFrom(ByteString data, + ExtensionRegistry extensionRegistry) + throws InvalidProtocolBufferException { + try { + CodedInputStream input = data.newCodedInput(); + mergeFrom(input, extensionRegistry); + input.checkLastTagWas(0); + return (BuilderType) this; + } catch (InvalidProtocolBufferException e) { + throw e; + } catch (IOException e) { + throw new RuntimeException( + "Reading from a ByteString threw an IOException (should " + + "never happen).", e); + } + } + + public BuilderType mergeFrom(byte[] data) + throws InvalidProtocolBufferException { + try { + CodedInputStream input = CodedInputStream.newInstance(data); + mergeFrom(input); + input.checkLastTagWas(0); + return (BuilderType) this; + } catch (InvalidProtocolBufferException e) { + throw e; + } catch (IOException e) { + throw new RuntimeException( + "Reading from a byte array threw an IOException (should " + + "never happen).", e); + } + } + + public BuilderType mergeFrom( + byte[] data, ExtensionRegistry extensionRegistry) + throws InvalidProtocolBufferException { + try { + CodedInputStream input = CodedInputStream.newInstance(data); + mergeFrom(input, extensionRegistry); + input.checkLastTagWas(0); + return (BuilderType) this; + } catch (InvalidProtocolBufferException e) { + throw e; + } catch (IOException e) { + throw new RuntimeException( + "Reading from a byte array threw an IOException (should " + + "never happen).", e); + } + } + + public BuilderType mergeFrom(InputStream input) throws IOException { + CodedInputStream codedInput = CodedInputStream.newInstance(input); + mergeFrom(codedInput); + codedInput.checkLastTagWas(0); + return (BuilderType) this; + } + + public BuilderType mergeFrom(InputStream input, + ExtensionRegistry extensionRegistry) + throws IOException { + CodedInputStream codedInput = CodedInputStream.newInstance(input); + mergeFrom(codedInput, extensionRegistry); + codedInput.checkLastTagWas(0); + return (BuilderType) this; + } + } +} diff --git a/java/src/main/java/com/google/protobuf/ByteString.java b/java/src/main/java/com/google/protobuf/ByteString.java new file mode 100644 index 00000000..4da03eca --- /dev/null +++ b/java/src/main/java/com/google/protobuf/ByteString.java @@ -0,0 +1,318 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.protobuf; + +import java.io.InputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FilterOutputStream; +import java.io.UnsupportedEncodingException; + +/** + * Immutable array of bytes. + * + * @author crazybob@google.com Bob Lee + * @author kenton@google.com Kenton Varda + */ +public final class ByteString { + private final byte[] bytes; + + private ByteString(byte[] bytes) { + this.bytes = bytes; + } + + /** + * Gets the byte at the given index. + * + * @throws ArrayIndexOutOfBoundsException {@code index} is < 0 or >= size + */ + public byte byteAt(int index) { + return bytes[index]; + } + + /** + * Gets the number of bytes. + */ + public int size() { + return this.bytes.length; + } + + /** + * Returns {@code true} if the size is {@code 0}, {@code false} otherwise. + */ + public boolean isEmpty() { + return this.bytes.length == 0; + } + + // ================================================================= + // byte[] -> ByteString + + /** + * Empty ByteString. + */ + public static final ByteString EMPTY = new ByteString(new byte[0]); + + /** + * Copies the given bytes into a {@code ByteString}. + */ + public static ByteString copyFrom(byte[] bytes, int offset, int size) { + byte[] copy = new byte[size]; + System.arraycopy(bytes, offset, copy, 0, size); + return new ByteString(copy); + } + + /** + * Copies the given bytes into a {@code ByteString}. + */ + public static ByteString copyFrom(byte[] bytes) { + return copyFrom(bytes, 0, bytes.length); + } + + /** + * Encodes {@code text} into a sequence of bytes using the named charset + * and returns the result as a {@code ByteString}. + */ + public static ByteString copyFrom(String text, String charsetName) + throws UnsupportedEncodingException { + return new ByteString(text.getBytes(charsetName)); + } + + /** + * Encodes {@code text} into a sequence of UTF-8 bytes and returns the + * result as a {@code ByteString}. + */ + public static ByteString copyFromUtf8(String text) { + try { + return new ByteString(text.getBytes("UTF-8")); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("UTF-8 not supported?", e); + } + } + + // ================================================================= + // ByteString -> byte[] + + /** + * Copies bytes into a buffer at the given offset. + * + * @param target buffer to copy into + * @param offset in the target buffer + */ + public void copyTo(byte[] target, int offset) { + System.arraycopy(bytes, 0, target, offset, bytes.length); + } + + /** + * Copies bytes into a buffer. + * + * @param target buffer to copy into + * @param sourceOffset offset within these bytes + * @param targetOffset offset within the target buffer + * @param size number of bytes to copy + */ + public void copyTo(byte[] target, int sourceOffset, int targetOffset, + int size) { + System.arraycopy(bytes, sourceOffset, target, targetOffset, size); + } + + /** + * Copies bytes to a {@code byte[]}. + */ + public byte[] toByteArray() { + int size = this.bytes.length; + byte[] copy = new byte[size]; + System.arraycopy(this.bytes, 0, copy, 0, size); + return copy; + } + + /** + * Constructs a new {@code String} by decoding the bytes using the + * specified charset. + */ + public String toString(String charsetName) + throws UnsupportedEncodingException { + return new String(this.bytes, charsetName); + } + + /** + * Constructs a new {@code String} by decoding the bytes as UTF-8. + */ + public String toStringUtf8() { + try { + return new String(this.bytes, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("UTF-8 not supported?", e); + } + } + + // ================================================================= + // equals() and hashCode() + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + + if (!(o instanceof ByteString)) { + return false; + } + + ByteString other = (ByteString) o; + int size = this.bytes.length; + if (size != other.bytes.length) { + return false; + } + + byte[] bytes = this.bytes; + byte[] otherBytes = other.bytes; + for (int i = 0; i < size; i++) { + if (bytes[i] != otherBytes[i]) { + return false; + } + } + + return true; + } + + volatile int hash = 0; + + @Override + public int hashCode() { + int h = this.hash; + + if (h == 0) { + byte[] bytes = this.bytes; + int size = this.bytes.length; + + h = size; + for (int i = 0; i < size; i++) { + h = h * 31 + bytes[i]; + } + if (h == 0) { + h = 1; + } + + this.hash = h; + } + + return h; + } + + // ================================================================= + // Input stream + + /** + * Creates an {@code InputStream} which can be used to read the bytes. + */ + public InputStream newInput() { + return new ByteArrayInputStream(bytes); + } + + /** + * Creates a {@link CodedInputStream} which can be used to read the bytes. + * Using this is more efficient than creating a {@link CodedInputStream} + * wrapping the result of {@link #newInput()}. + */ + public CodedInputStream newCodedInput() { + // We trust CodedInputStream not to modify the bytes, or to give anyone + // else access to them. + return CodedInputStream.newInstance(bytes); + } + + // ================================================================= + // Output stream + + /** + * Creates a new {@link Output} with the given initial capacity. + */ + public static Output newOutput(int initialCapacity) { + return new Output(new ByteArrayOutputStream(initialCapacity)); + } + + /** + * Creates a new {@link Output}. + */ + public static Output newOutput() { + return newOutput(32); + } + + /** + * Outputs to a {@code ByteString} instance. Call {@link #toByteString()} to + * create the {@code ByteString} instance. + */ + public static final class Output extends FilterOutputStream { + private final ByteArrayOutputStream bout; + + /** + * Constructs a new output with the given initial capacity. + */ + private Output(ByteArrayOutputStream bout) { + super(bout); + this.bout = bout; + } + + /** + * Creates a {@code ByteString} instance from this {@code Output}. + */ + public ByteString toByteString() { + byte[] byteArray = bout.toByteArray(); + return new ByteString(byteArray); + } + } + + /** + * Constructs a new ByteString builder, which allows you to efficiently + * construct a {@code ByteString} by writing to a {@link CodedOutputSteam}. + * Using this is much more efficient than calling {@code newOutput()} and + * wrapping that in a {@code CodedOutputStream}. + * + * <p>This is package-private because it's a somewhat confusing interface. + * Users can call {@link Message#toByteString()} instead of calling this + * directly. + * + * @param size The target byte size of the {@code ByteString}. You must + * write exactly this many bytes before building the result. + */ + static CodedBuilder newCodedBuilder(int size) { + return new CodedBuilder(size); + } + + /** See {@link ByteString#newCodedBuilder(int)}. */ + static final class CodedBuilder { + private final CodedOutputStream output; + private final byte[] buffer; + + private CodedBuilder(int size) { + buffer = new byte[size]; + output = CodedOutputStream.newInstance(buffer); + } + + public ByteString build() { + output.checkNoSpaceLeft(); + + // We can be confident that the CodedOutputStream will not modify the + // underlying bytes anymore because it already wrote all of them. So, + // no need to make a copy. + return new ByteString(buffer); + } + + public CodedOutputStream getCodedOutput() { + return output; + } + } +} diff --git a/java/src/main/java/com/google/protobuf/CodedInputStream.java b/java/src/main/java/com/google/protobuf/CodedInputStream.java new file mode 100644 index 00000000..b32c349c --- /dev/null +++ b/java/src/main/java/com/google/protobuf/CodedInputStream.java @@ -0,0 +1,766 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.protobuf; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +/** + * Reads and decodes protocol message fields. + * + * This class contains two kinds of methods: methods that read specific + * protocol message constructs and field types (e.g. {@link #readTag()} and + * {@link #readInt32()}) and methods that read low-level values (e.g. + * {@link #readRawVarint32()} and {@link #readRawBytes}). If you are reading + * encoded protocol messages, you should use the former methods, but if you are + * reading some other format of your own design, use the latter. + * + * @author kenton@google.com Kenton Varda + */ +public final class CodedInputStream { + /** + * Create a new CodedInputStream wrapping the given InputStream. + */ + public static CodedInputStream newInstance(InputStream input) { + return new CodedInputStream(input); + } + + /** + * Create a new CodedInputStream wrapping the given byte array. + */ + public static CodedInputStream newInstance(byte[] buf) { + return new CodedInputStream(buf); + } + + // ----------------------------------------------------------------- + + /** + * Attempt to read a field tag, returning zero if we have reached EOF. + * Protocol message parsers use this to read tags, since a protocol message + * may legally end wherever a tag occurs, and zero is not a valid tag number. + */ + public int readTag() throws IOException { + if (bufferPos == bufferSize && !refillBuffer(false)) { + lastTag = 0; + return 0; + } + + lastTag = readRawVarint32(); + if (lastTag == 0) { + // If we actually read zero, that's not a valid tag. + throw InvalidProtocolBufferException.invalidTag(); + } + return lastTag; + } + + /** + * Verifies that the last call to readTag() returned the given tag value. + * This is used to verify that a nested group ended with the correct + * end tag. + * + * @throws InvalidProtocolBufferException {@code value} does not match the + * last tag. + */ + public void checkLastTagWas(int value) throws InvalidProtocolBufferException { + if (lastTag != value) { + throw InvalidProtocolBufferException.invalidEndTag(); + } + } + + /** + * Reads and discards a single field, given its tag value. + * + * @return {@code false} if the tag is an endgroup tag, in which case + * nothing is skipped. Otherwise, returns {@code true}. + */ + public boolean skipField(int tag) throws IOException { + switch (WireFormat.getTagWireType(tag)) { + case WireFormat.WIRETYPE_VARINT: + readInt32(); + return true; + case WireFormat.WIRETYPE_FIXED64: + readRawLittleEndian64(); + return true; + case WireFormat.WIRETYPE_LENGTH_DELIMITED: + skipRawBytes(readRawVarint32()); + return true; + case WireFormat.WIRETYPE_START_GROUP: + skipMessage(); + checkLastTagWas( + WireFormat.makeTag(WireFormat.getTagFieldNumber(tag), + WireFormat.WIRETYPE_END_GROUP)); + return true; + case WireFormat.WIRETYPE_END_GROUP: + return false; + case WireFormat.WIRETYPE_FIXED32: + readRawLittleEndian32(); + return true; + default: + throw InvalidProtocolBufferException.invalidWireType(); + } + } + + /** + * Reads and discards an entire message. This will read either until EOF + * or until an endgroup tag, whichever comes first. + */ + public void skipMessage() throws IOException { + while (true) { + int tag = readTag(); + if (tag == 0 || !skipField(tag)) return; + } + } + + // ----------------------------------------------------------------- + + /** Read a {@code double} field value from the stream. */ + public double readDouble() throws IOException { + return Double.longBitsToDouble(readRawLittleEndian64()); + } + + /** Read a {@code float} field value from the stream. */ + public float readFloat() throws IOException { + return Float.intBitsToFloat(readRawLittleEndian32()); + } + + /** Read a {@code uint64} field value from the stream. */ + public long readUInt64() throws IOException { + return readRawVarint64(); + } + + /** Read an {@code int64} field value from the stream. */ + public long readInt64() throws IOException { + return readRawVarint64(); + } + + /** Read an {@code int32} field value from the stream. */ + public int readInt32() throws IOException { + return readRawVarint32(); + } + + /** Read a {@code fixed64} field value from the stream. */ + public long readFixed64() throws IOException { + return readRawLittleEndian64(); + } + + /** Read a {@code fixed32} field value from the stream. */ + public int readFixed32() throws IOException { + return readRawLittleEndian32(); + } + + /** Read a {@code bool} field value from the stream. */ + public boolean readBool() throws IOException { + return readRawVarint32() != 0; + } + + /** Read a {@code string} field value from the stream. */ + public String readString() throws IOException { + int size = readRawVarint32(); + if (size < bufferSize - bufferPos && size > 0) { + // Fast path: We already have the bytes in a contiguous buffer, so + // just copy directly from it. + String result = new String(buffer, bufferPos, size, "UTF-8"); + bufferPos += size; + return result; + } else { + // Slow path: Build a byte array first then copy it. + return new String(readRawBytes(size), "UTF-8"); + } + } + + /** Read a {@code group} field value from the stream. */ + public void readGroup(int fieldNumber, Message.Builder builder, + ExtensionRegistry extensionRegistry) + throws IOException { + if (recursionDepth >= recursionLimit) { + throw InvalidProtocolBufferException.recursionLimitExceeded(); + } + ++recursionDepth; + builder.mergeFrom(this, extensionRegistry); + checkLastTagWas( + WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP)); + --recursionDepth; + } + + /** + * Reads a {@code group} field value from the stream and merges it into the + * given {@link UnknownFieldSet}. + */ + public void readUnknownGroup(int fieldNumber, UnknownFieldSet.Builder builder) + throws IOException { + if (recursionDepth >= recursionLimit) { + throw InvalidProtocolBufferException.recursionLimitExceeded(); + } + ++recursionDepth; + builder.mergeFrom(this); + checkLastTagWas( + WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP)); + --recursionDepth; + } + + /** Read an embedded message field value from the stream. */ + public void readMessage(Message.Builder builder, + ExtensionRegistry extensionRegistry) + throws IOException { + int length = readRawVarint32(); + if (recursionDepth >= recursionLimit) { + throw InvalidProtocolBufferException.recursionLimitExceeded(); + } + int oldLimit = pushLimit(length); + ++recursionDepth; + builder.mergeFrom(this, extensionRegistry); + checkLastTagWas(0); + --recursionDepth; + popLimit(oldLimit); + } + + /** Read a {@code bytes} field value from the stream. */ + public ByteString readBytes() throws IOException { + int size = readRawVarint32(); + if (size < bufferSize - bufferPos && size > 0) { + // Fast path: We already have the bytes in a contiguous buffer, so + // just copy directly from it. + ByteString result = ByteString.copyFrom(buffer, bufferPos, size); + bufferPos += size; + return result; + } else { + // Slow path: Build a byte array first then copy it. + return ByteString.copyFrom(readRawBytes(size)); + } + } + + /** Read a {@code uint32} field value from the stream. */ + public int readUInt32() throws IOException { + return readRawVarint32(); + } + + /** + * Read an enum field value from the stream. Caller is responsible + * for converting the numeric value to an actual enum. + */ + public int readEnum() throws IOException { + return readRawVarint32(); + } + + /** Read an {@code sfixed32} field value from the stream. */ + public int readSFixed32() throws IOException { + return readRawLittleEndian32(); + } + + /** Read an {@code sfixed64} field value from the stream. */ + public long readSFixed64() throws IOException { + return readRawLittleEndian64(); + } + + /** Read an {@code sint32} field value from the stream. */ + public int readSInt32() throws IOException { + return decodeZigZag32(readRawVarint32()); + } + + /** Read an {@code sint64} field value from the stream. */ + public long readSInt64() throws IOException { + return decodeZigZag64(readRawVarint64()); + } + + /** + * Read a field of any primitive type. Enums, groups, and embedded + * messages are not handled by this method. + * + * @param type Declared type of the field. + * @return An object representing the field's value, of the exact + * type which would be returned by + * {@link Message#getField(Descriptors.FieldDescriptor)} for + * this field. + */ + public Object readPrimitiveField( + Descriptors.FieldDescriptor.Type type) throws IOException { + switch (type) { + case DOUBLE : return readDouble (); + case FLOAT : return readFloat (); + case INT64 : return readInt64 (); + case UINT64 : return readUInt64 (); + case INT32 : return readInt32 (); + case FIXED64 : return readFixed64 (); + case FIXED32 : return readFixed32 (); + case BOOL : return readBool (); + case STRING : return readString (); + case BYTES : return readBytes (); + case UINT32 : return readUInt32 (); + case SFIXED32: return readSFixed32(); + case SFIXED64: return readSFixed64(); + case SINT32 : return readSInt32 (); + case SINT64 : return 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 hanlde 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."); + } + + // ================================================================= + + /** + * Read a raw Varint from the stream. If larger than 32 bits, discard the + * upper bits. + */ + public int readRawVarint32() throws IOException { + byte tmp = readRawByte(); + if (tmp >= 0) { + return tmp; + } + int result = tmp & 0x7f; + if ((tmp = readRawByte()) >= 0) { + result |= tmp << 7; + } else { + result |= (tmp & 0x7f) << 7; + if ((tmp = readRawByte()) >= 0) { + result |= tmp << 14; + } else { + result |= (tmp & 0x7f) << 14; + if ((tmp = readRawByte()) >= 0) { + result |= tmp << 21; + } else { + result |= (tmp & 0x7f) << 21; + result |= (tmp = readRawByte()) << 28; + if (tmp < 0) { + // Discard upper 32 bits. + for (int i = 0; i < 5; i++) { + if (readRawByte() >= 0) return result; + } + throw InvalidProtocolBufferException.malformedVarint(); + } + } + } + } + return result; + } + + /** Read a raw Varint from the stream. */ + public long readRawVarint64() throws IOException { + int shift = 0; + long result = 0; + while (shift < 64) { + byte b = readRawByte(); + result |= (long)(b & 0x7F) << shift; + if ((b & 0x80) == 0) return result; + shift += 7; + } + throw InvalidProtocolBufferException.malformedVarint(); + } + + /** Read a 32-bit little-endian integer from the stream. */ + public int readRawLittleEndian32() throws IOException { + byte b1 = readRawByte(); + byte b2 = readRawByte(); + byte b3 = readRawByte(); + byte b4 = readRawByte(); + return (((int)b1 & 0xff) ) | + (((int)b2 & 0xff) << 8) | + (((int)b3 & 0xff) << 16) | + (((int)b4 & 0xff) << 24); + } + + /** Read a 64-bit little-endian integer from the stream. */ + public long readRawLittleEndian64() throws IOException { + byte b1 = readRawByte(); + byte b2 = readRawByte(); + byte b3 = readRawByte(); + byte b4 = readRawByte(); + byte b5 = readRawByte(); + byte b6 = readRawByte(); + byte b7 = readRawByte(); + byte b8 = readRawByte(); + return (((long)b1 & 0xff) ) | + (((long)b2 & 0xff) << 8) | + (((long)b3 & 0xff) << 16) | + (((long)b4 & 0xff) << 24) | + (((long)b5 & 0xff) << 32) | + (((long)b6 & 0xff) << 40) | + (((long)b7 & 0xff) << 48) | + (((long)b8 & 0xff) << 56); + } + + /** + * Decode a ZigZag-encoded 32-bit value. ZigZag encodes signed integers + * into values that can be efficiently encoded with varint. (Otherwise, + * negative values must be sign-extended to 64 bits to be varint encoded, + * thus always taking 10 bytes on the wire.) + * + * @param n An unsigned 32-bit integer, stored in a signed int because + * Java has no explicit unsigned support. + * @return A signed 32-bit integer. + */ + public static int decodeZigZag32(int n) { + return (n >>> 1) ^ -(n & 1); + } + + /** + * Decode a ZigZag-encoded 64-bit value. ZigZag encodes signed integers + * into values that can be efficiently encoded with varint. (Otherwise, + * negative values must be sign-extended to 64 bits to be varint encoded, + * thus always taking 10 bytes on the wire.) + * + * @param n An unsigned 64-bit integer, stored in a signed int because + * Java has no explicit unsigned support. + * @return A signed 64-bit integer. + */ + public static long decodeZigZag64(long n) { + return (n >>> 1) ^ -(n & 1); + } + + // ----------------------------------------------------------------- + + private byte[] buffer; + private int bufferSize; + private int bufferSizeAfterLimit = 0; + private int bufferPos = 0; + private InputStream input; + private int lastTag = 0; + + /** + * The total number of bytes read before the current buffer. The total + * bytes read up to the current position can be computed as + * {@code totalBytesRetired + bufferPos}. + */ + private int totalBytesRetired = 0; + + /** The absolute position of the end of the current message. */ + private int currentLimit = Integer.MAX_VALUE; + + /** See setRecursionLimit() */ + private int recursionDepth = 0; + private int recursionLimit = DEFAULT_RECURSION_LIMIT; + + /** See setSizeLimit() */ + private int sizeLimit = DEFAULT_SIZE_LIMIT; + + private static final int DEFAULT_RECURSION_LIMIT = 64; + private static final int DEFAULT_SIZE_LIMIT = 64 << 20; // 64MB + private static final int BUFFER_SIZE = 4096; + + private CodedInputStream(byte[] buffer) { + this.buffer = buffer; + this.bufferSize = buffer.length; + this.input = null; + } + + private CodedInputStream(InputStream input) { + this.buffer = new byte[BUFFER_SIZE]; + this.bufferSize = 0; + this.input = input; + } + + /** + * Set the maximum message recursion depth. In order to prevent malicious + * messages from causing stack overflows, {@code CodedInputStream} limits + * how deeply messages may be nested. The default limit is 64. + * + * @return the old limit. + */ + public int setRecursionLimit(int limit) { + if (limit < 0) { + throw new IllegalArgumentException( + "Recursion limit cannot be negative: " + limit); + } + int oldLimit = recursionLimit; + recursionLimit = limit; + return oldLimit; + } + + /** + * Set the maximum message size. In order to prevent malicious + * messages from exhausting memory or causing integer overflows, + * {@code CodedInputStream} limits how large a message may be. + * The default limit is 64MB. You should set this limit as small + * as you can without harming your app's functionality. Note that + * size limits only apply when reading from an {@code InputStream}, not + * when constructed around a raw byte array (nor with + * {@link ByteString#newCodedInput}). + * + * @return the old limit. + */ + public int setSizeLimit(int limit) { + if (limit < 0) { + throw new IllegalArgumentException( + "Size limit cannot be negative: " + limit); + } + int oldLimit = sizeLimit; + sizeLimit = limit; + return oldLimit; + } + + /** + * Sets {@code currentLimit} to (current position) + {@code byteLimit}. This + * is called when descending into a length-delimited embedded message. + * + * @return the old limit. + */ + public int pushLimit(int byteLimit) throws InvalidProtocolBufferException { + if (byteLimit < 0) { + throw InvalidProtocolBufferException.negativeSize(); + } + byteLimit += totalBytesRetired + bufferPos; + int oldLimit = currentLimit; + if (byteLimit > oldLimit) { + throw InvalidProtocolBufferException.truncatedMessage(); + } + currentLimit = byteLimit; + + recomputeBufferSizeAfterLimit(); + + return oldLimit; + } + + private void recomputeBufferSizeAfterLimit() { + bufferSize += bufferSizeAfterLimit; + int bufferEnd = totalBytesRetired + bufferSize; + if (bufferEnd > currentLimit) { + // Limit is in current buffer. + bufferSizeAfterLimit = bufferEnd - currentLimit; + bufferSize -= bufferSizeAfterLimit; + } else { + bufferSizeAfterLimit = 0; + } + } + + /** + * Discards the current limit, returning to the previous limit. + * + * @param oldLimit The old limit, as returned by {@code pushLimit}. + */ + public void popLimit(int oldLimit) { + currentLimit = oldLimit; + recomputeBufferSizeAfterLimit(); + } + + /** + * 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 + * or it will throw an exception. If {@code mustSucceed} is false, + * refillBuffer() returns false if no more bytes were available. + */ + private boolean refillBuffer(boolean mustSucceed) throws IOException { + if (bufferPos < bufferSize) { + throw new IllegalStateException( + "refillBuffer() called when buffer wasn't empty."); + } + + if (totalBytesRetired + bufferSize == currentLimit) { + // Oops, we hit a limit. + if (mustSucceed) { + throw InvalidProtocolBufferException.truncatedMessage(); + } else { + return false; + } + } + + totalBytesRetired += bufferSize; + + bufferPos = 0; + bufferSize = (input == null) ? -1 : input.read(buffer); + if (bufferSize == -1) { + bufferSize = 0; + if (mustSucceed) { + throw InvalidProtocolBufferException.truncatedMessage(); + } else { + return false; + } + } else { + recomputeBufferSizeAfterLimit(); + int totalBytesRead = + totalBytesRetired + bufferSize + bufferSizeAfterLimit; + if (totalBytesRead > sizeLimit || totalBytesRead < 0) { + throw InvalidProtocolBufferException.sizeLimitExceeded(); + } + return true; + } + } + + /** + * Read one byte from the input. + * + * @throws InvalidProtocolBufferException The end of the stream or the current + * limit was reached. + */ + public byte readRawByte() throws IOException { + if (bufferPos == bufferSize) { + refillBuffer(true); + } + return buffer[bufferPos++]; + } + + /** + * Read a fixed size of bytes from the input. + * + * @throws InvalidProtocolBufferException The end of the stream or the current + * limit was reached. + */ + public byte[] readRawBytes(int size) throws IOException { + if (size < 0) { + throw InvalidProtocolBufferException.negativeSize(); + } + + if (totalBytesRetired + bufferPos + size > currentLimit) { + // Read to the end of the stream anyway. + skipRawBytes(currentLimit - totalBytesRetired - bufferPos); + // Then fail. + throw InvalidProtocolBufferException.truncatedMessage(); + } + + if (size <= bufferSize - bufferPos) { + // We have all the bytes we need already. + byte[] bytes = new byte[size]; + System.arraycopy(buffer, bufferPos, bytes, 0, size); + bufferPos += size; + return bytes; + } else if (size < BUFFER_SIZE) { + // Reading more bytes than are in the buffer, but not an excessive number + // of bytes. We can safely allocate the resulting array ahead of time. + + // First copy what we have. + byte[] bytes = new byte[size]; + int pos = bufferSize - bufferPos; + System.arraycopy(buffer, bufferPos, bytes, 0, pos); + bufferPos = bufferSize; + + // We want to use refillBuffer() and then copy from the buffer into our + // byte array rather than reading directly into our byte array because + // the input may be unbuffered. + refillBuffer(true); + + while (size - pos > bufferSize) { + System.arraycopy(buffer, 0, bytes, pos, bufferSize); + pos += bufferSize; + bufferPos = bufferSize; + refillBuffer(true); + } + + System.arraycopy(buffer, 0, bytes, pos, size - pos); + bufferPos = size - pos; + + return bytes; + } else { + // The size is very large. For security reasons, we can't allocate the + // entire byte array yet. The size comes directly from the input, so a + // maliciously-crafted message could provide a bogus very large size in + // order to trick the app into allocating a lot of memory. We avoid this + // by allocating and reading only a small chunk at a time, so that the + // malicious message must actually *be* extremely large to cause + // problems. Meanwhile, we limit the allowed size of a message elsewhere. + + // Remember the buffer markers since we'll have to copy the bytes out of + // it later. + int originalBufferPos = bufferPos; + int originalBufferSize = bufferSize; + + // Mark the current buffer consumed. + totalBytesRetired += bufferSize; + bufferPos = 0; + bufferSize = 0; + + // Read all the rest of the bytes we need. + int sizeLeft = size - (originalBufferSize - originalBufferPos); + List<byte[]> chunks = new ArrayList<byte[]>(); + + while (sizeLeft > 0) { + byte[] chunk = new byte[Math.min(sizeLeft, BUFFER_SIZE)]; + int pos = 0; + while (pos < chunk.length) { + int n = (input == null) ? -1 : + input.read(chunk, pos, chunk.length - pos); + if (n == -1) { + throw InvalidProtocolBufferException.truncatedMessage(); + } + totalBytesRetired += n; + pos += n; + } + sizeLeft -= chunk.length; + chunks.add(chunk); + } + + // OK, got everything. Now concatenate it all into one buffer. + byte[] bytes = new byte[size]; + + // Start by copying the leftover bytes from this.buffer. + int pos = originalBufferSize - originalBufferPos; + System.arraycopy(buffer, originalBufferPos, bytes, 0, pos); + + // And now all the chunks. + for (byte[] chunk : chunks) { + System.arraycopy(chunk, 0, bytes, pos, chunk.length); + pos += chunk.length; + } + + // Done. + return bytes; + } + } + + /** + * Reads and discards {@code size} bytes. + * + * @throws InvalidProtocolBufferException The end of the stream or the current + * limit was reached. + */ + public void skipRawBytes(int size) throws IOException { + if (size < 0) { + throw InvalidProtocolBufferException.negativeSize(); + } + + if (totalBytesRetired + bufferPos + size > currentLimit) { + // Read to the end of the stream anyway. + skipRawBytes(currentLimit - totalBytesRetired - bufferPos); + // Then fail. + throw InvalidProtocolBufferException.truncatedMessage(); + } + + if (size < bufferSize - bufferPos) { + // We have all the bytes we need already. + bufferPos += size; + } else { + // Skipping more bytes than are in the buffer. First skip what we have. + int pos = bufferSize - bufferPos; + totalBytesRetired += pos; + bufferPos = 0; + bufferSize = 0; + + // Then skip directly from the InputStream for the rest. + while (pos < size) { + int n = (input == null) ? -1 : (int) input.skip(size - pos); + if (n <= 0) { + throw InvalidProtocolBufferException.truncatedMessage(); + } + pos += n; + totalBytesRetired += n; + } + } + } +} diff --git a/java/src/main/java/com/google/protobuf/CodedOutputStream.java b/java/src/main/java/com/google/protobuf/CodedOutputStream.java new file mode 100644 index 00000000..18498999 --- /dev/null +++ b/java/src/main/java/com/google/protobuf/CodedOutputStream.java @@ -0,0 +1,775 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.protobuf; + +import java.io.OutputStream; +import java.io.IOException; + +/** + * Encodes and writes protocol message fields. + * + * <p>This class contains two kinds of methods: methods that write specific + * protocol message constructs and field types (e.g. {@link #writeTag} and + * {@link #writeInt32}) and methods that write low-level values (e.g. + * {@link #writeRawVarint32} and {@link #writeRawBytes}). If you are + * writing encoded protocol messages, you should use the former methods, but if + * you are writing some other format of your own design, use the latter. + * + * <p>This class is totally unsynchronized. + * + * @author kneton@google.com Kenton Varda + */ +public final class CodedOutputStream { + private final byte[] buffer; + private final int limit; + private int position; + + private final OutputStream output; + + /** + * The buffer size used in {@link #newInstance(java.io.OutputStream)}. + */ + public static final int DEFAULT_BUFFER_SIZE = 4096; + + private CodedOutputStream(byte[] buffer, int offset, int length) { + this.output = null; + this.buffer = buffer; + this.position = offset; + this.limit = offset + length; + } + + private CodedOutputStream(OutputStream output, byte[] buffer) { + this.output = output; + this.buffer = buffer; + this.position = 0; + this.limit = buffer.length; + } + + /** + * Create a new {@code CodedOutputStream} wrapping the given + * {@code OutputStream}. + */ + public static CodedOutputStream newInstance(OutputStream output) { + return newInstance(output, DEFAULT_BUFFER_SIZE); + } + + /** + * Create a new {@code CodedOutputStream} wrapping the given + * {@code OutputStream} with a given buffer size. + */ + public static CodedOutputStream newInstance(OutputStream output, + int bufferSize) { + return new CodedOutputStream(output, new byte[bufferSize]); + } + + /** + * Create a new {@code CodedOutputStream} that writes directly to the given + * byte array. If more bytes are written than fit in the array, + * {@link OutOfSpaceException} will be thrown. Writing directly to a flat + * array is faster than writing to an {@code OutputStream}. See also + * {@link ByteString#newCodedBuilder}. + */ + public static CodedOutputStream newInstance(byte[] flatArray) { + return newInstance(flatArray, 0, flatArray.length); + } + + /** + * Create a new {@code CodedOutputStream} that writes directly to the given + * byte array slice. If more bytes are written than fit in the slice, + * {@link OutOfSpaceException} will be thrown. Writing directly to a flat + * array is faster than writing to an {@code OutputStream}. See also + * {@link ByteString#newCodedBuilder}. + */ + public static CodedOutputStream newInstance(byte[] flatArray, int offset, + int length) { + return new CodedOutputStream(flatArray, offset, length); + } + + // ----------------------------------------------------------------- + + /** Write a {@code double} field, including tag, to the stream. */ + public void writeDouble(int fieldNumber, double value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED64); + writeRawLittleEndian64(Double.doubleToRawLongBits(value)); + } + + /** Write a {@code float} field, including tag, to the stream. */ + public void writeFloat(int fieldNumber, float value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED32); + writeRawLittleEndian32(Float.floatToRawIntBits(value)); + } + + /** Write a {@code uint64} field, including tag, to the stream. */ + public void writeUInt64(int fieldNumber, long value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + writeRawVarint64(value); + } + + /** Write an {@code int64} field, including tag, to the stream. */ + public void writeInt64(int fieldNumber, long value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + writeRawVarint64(value); + } + + /** Write an {@code int32} field, including tag, to the stream. */ + public void writeInt32(int fieldNumber, int value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + if (value >= 0) { + writeRawVarint32(value); + } else { + // Must sign-extend. + writeRawVarint64(value); + } + } + + /** Write a {@code fixed64} field, including tag, to the stream. */ + public void writeFixed64(int fieldNumber, long value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED64); + writeRawLittleEndian64(value); + } + + /** Write a {@code fixed32} field, including tag, to the stream. */ + public void writeFixed32(int fieldNumber, int value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED32); + writeRawLittleEndian32(value); + } + + /** Write a {@code bool} field, including tag, to the stream. */ + public void writeBool(int fieldNumber, boolean value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + writeRawByte(value ? 1 : 0); + } + + /** Write a {@code string} field, including tag, to the stream. */ + public void writeString(int fieldNumber, String value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + // 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. + byte[] bytes = value.getBytes("UTF-8"); + writeRawVarint32(bytes.length); + writeRawBytes(bytes); + } + + /** Write a {@code group} field, including tag, to the stream. */ + public void writeGroup(int fieldNumber, Message value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_START_GROUP); + value.writeTo(this); + writeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP); + } + + /** Write a group represented by an {@link UnknownFieldSet}. */ + public void writeUnknownGroup(int fieldNumber, UnknownFieldSet value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_START_GROUP); + value.writeTo(this); + writeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP); + } + + /** Write an embedded message field, including tag, to the stream. */ + public void writeMessage(int fieldNumber, Message value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeRawVarint32(value.getSerializedSize()); + value.writeTo(this); + } + + /** Write a {@code bytes} field, including tag, to the stream. */ + public void writeBytes(int fieldNumber, ByteString value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + byte[] bytes = value.toByteArray(); + writeRawVarint32(bytes.length); + writeRawBytes(bytes); + } + + /** Write a {@code uint32} field, including tag, to the stream. */ + public void writeUInt32(int fieldNumber, int value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + writeRawVarint32(value); + } + + /** + * Write an enum field, including tag, to the stream. Caller is responsible + * for converting the enum value to its numeric value. + */ + public void writeEnum(int fieldNumber, int value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + writeRawVarint32(value); + } + + /** Write an {@code sfixed32} field, including tag, to the stream. */ + public void writeSFixed32(int fieldNumber, int value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED32); + writeRawLittleEndian32(value); + } + + /** Write an {@code sfixed64} field, including tag, to the stream. */ + public void writeSFixed64(int fieldNumber, long value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED64); + writeRawLittleEndian64(value); + } + + /** Write an {@code sint32} field, including tag, to the stream. */ + public void writeSInt32(int fieldNumber, int value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + writeRawVarint32(encodeZigZag32(value)); + } + + /** Write an {@code sint64} field, including tag, to the stream. */ + public void writeSInt64(int fieldNumber, long value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + writeRawVarint64(encodeZigZag64(value)); + } + + /** + * Write a MessageSet extension field to the stream. For historical reasons, + * the wire format differs from normal fields. + */ + public void writeMessageSetExtension(int fieldNumber, Message value) + throws IOException { + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_START_GROUP); + writeUInt32(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber); + writeMessage(WireFormat.MESSAGE_SET_MESSAGE, value); + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_END_GROUP); + } + + /** + * Write an unparsed MessageSet extension field to the stream. For + * historical reasons, the wire format differs from normal fields. + */ + public void writeRawMessageSetExtension(int fieldNumber, ByteString value) + throws IOException { + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_START_GROUP); + writeUInt32(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber); + writeBytes(WireFormat.MESSAGE_SET_MESSAGE, value); + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_END_GROUP); + } + + /** + * Write a field of arbitrary type, including tag, to the stream. + * + * @param type The field's type. + * @param number The field's number. + * @param value Object representing the field's value. Must be of the exact + * type which would be returned by + * {@link Message#getField(Descriptors.FieldDescriptor)} for + * this field. + */ + public void writeField(Descriptors.FieldDescriptor.Type type, + int number, Object value) throws IOException { + switch (type) { + case DOUBLE : writeDouble (number, (Double )value); break; + case FLOAT : writeFloat (number, (Float )value); break; + case INT64 : writeInt64 (number, (Long )value); break; + case UINT64 : writeUInt64 (number, (Long )value); break; + case INT32 : writeInt32 (number, (Integer )value); break; + case FIXED64 : writeFixed64 (number, (Long )value); break; + case FIXED32 : writeFixed32 (number, (Integer )value); break; + case BOOL : writeBool (number, (Boolean )value); break; + case STRING : writeString (number, (String )value); break; + case GROUP : writeGroup (number, (Message )value); break; + case MESSAGE : writeMessage (number, (Message )value); break; + case BYTES : writeBytes (number, (ByteString)value); break; + case UINT32 : writeUInt32 (number, (Integer )value); break; + case SFIXED32: writeSFixed32(number, (Integer )value); break; + case SFIXED64: writeSFixed64(number, (Long )value); break; + case SINT32 : writeSInt32 (number, (Integer )value); break; + case SINT64 : writeSInt64 (number, (Long )value); break; + + case ENUM: + writeEnum(number, ((Descriptors.EnumValueDescriptor)value).getNumber()); + break; + } + } + + // ================================================================= + + /** + * Compute the number of bytes that would be needed to encode a + * {@code double} field, including tag. + */ + public static int computeDoubleSize(int fieldNumber, double value) { + return computeTagSize(fieldNumber) + LITTLE_ENDIAN_64_SIZE; + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code float} field, including tag. + */ + public static int computeFloatSize(int fieldNumber, float value) { + return computeTagSize(fieldNumber) + LITTLE_ENDIAN_32_SIZE; + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code uint64} field, including tag. + */ + public static int computeUInt64Size(int fieldNumber, long value) { + return computeTagSize(fieldNumber) + computeRawVarint64Size(value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code int64} field, including tag. + */ + public static int computeInt64Size(int fieldNumber, long value) { + return computeTagSize(fieldNumber) + computeRawVarint64Size(value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code int32} field, including tag. + */ + public static int computeInt32Size(int fieldNumber, int value) { + if (value >= 0) { + return computeTagSize(fieldNumber) + computeRawVarint32Size(value); + } else { + // Must sign-extend. + return computeTagSize(fieldNumber) + 10; + } + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code fixed64} field, including tag. + */ + public static int computeFixed64Size(int fieldNumber, long value) { + return computeTagSize(fieldNumber) + LITTLE_ENDIAN_64_SIZE; + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code fixed32} field, including tag. + */ + public static int computeFixed32Size(int fieldNumber, int value) { + return computeTagSize(fieldNumber) + LITTLE_ENDIAN_32_SIZE; + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code bool} field, including tag. + */ + public static int computeBoolSize(int fieldNumber, boolean value) { + return computeTagSize(fieldNumber) + 1; + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code string} field, including tag. + */ + public static int computeStringSize(int fieldNumber, String value) { + try { + byte[] bytes = value.getBytes("UTF-8"); + return computeTagSize(fieldNumber) + + computeRawVarint32Size(bytes.length) + + bytes.length; + } catch (java.io.UnsupportedEncodingException e) { + throw new RuntimeException("UTF-8 not supported.", e); + } + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code group} field, including tag. + */ + public static int computeGroupSize(int fieldNumber, Message value) { + return computeTagSize(fieldNumber) * 2 + value.getSerializedSize(); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code group} field represented by an {@code UnknownFieldSet}, including + * tag. + */ + public static int computeUnknownGroupSize(int fieldNumber, + UnknownFieldSet value) { + return computeTagSize(fieldNumber) * 2 + value.getSerializedSize(); + } + + /** + * Compute the number of bytes that would be needed to encode an + * embedded message field, including tag. + */ + public static int computeMessageSize(int fieldNumber, Message value) { + int size = value.getSerializedSize(); + return computeTagSize(fieldNumber) + computeRawVarint32Size(size) + size; + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code bytes} field, including tag. + */ + public static int computeBytesSize(int fieldNumber, ByteString value) { + return computeTagSize(fieldNumber) + + computeRawVarint32Size(value.size()) + + value.size(); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code uint32} field, including tag. + */ + public static int computeUInt32Size(int fieldNumber, int value) { + return computeTagSize(fieldNumber) + computeRawVarint32Size(value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * enum field, including tag. Caller is responsible for converting the + * enum value to its numeric value. + */ + public static int computeEnumSize(int fieldNumber, int value) { + return computeTagSize(fieldNumber) + computeRawVarint32Size(value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code sfixed32} field, including tag. + */ + public static int computeSFixed32Size(int fieldNumber, int value) { + return computeTagSize(fieldNumber) + LITTLE_ENDIAN_32_SIZE; + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code sfixed64} field, including tag. + */ + public static int computeSFixed64Size(int fieldNumber, long value) { + return computeTagSize(fieldNumber) + LITTLE_ENDIAN_64_SIZE; + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code sint32} field, including tag. + */ + public static int computeSInt32Size(int fieldNumber, int value) { + return computeTagSize(fieldNumber) + + computeRawVarint32Size(encodeZigZag32(value)); + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code sint64} field, including tag. + */ + public static int computeSInt64Size(int fieldNumber, long value) { + return computeTagSize(fieldNumber) + + computeRawVarint64Size(encodeZigZag64(value)); + } + + /** + * Compute the number of bytes that would be needed to encode a + * MessageSet extension to the stream. For historical reasons, + * the wire format differs from normal fields. + */ + public static int computeMessageSetExtensionSize( + int fieldNumber, Message value) { + return computeTagSize(WireFormat.MESSAGE_SET_ITEM) * 2 + + computeUInt32Size(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber) + + computeMessageSize(WireFormat.MESSAGE_SET_MESSAGE, value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * unparsed MessageSet extension field to the stream. For + * historical reasons, the wire format differs from normal fields. + */ + public static int computeRawMessageSetExtensionSize( + int fieldNumber, ByteString value) { + return computeTagSize(WireFormat.MESSAGE_SET_ITEM) * 2 + + computeUInt32Size(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber) + + computeBytesSize(WireFormat.MESSAGE_SET_MESSAGE, value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * field of arbitrary type, including tag, to the stream. + * + * @param type The field's type. + * @param number The field's number. + * @param value Object representing the field's value. Must be of the exact + * type which would be returned by + * {@link Message#getField(Descriptors.FieldDescriptor)} for + * this field. + */ + public static int computeFieldSize( + Descriptors.FieldDescriptor.Type type, + int number, Object value) { + switch (type) { + case DOUBLE : return computeDoubleSize (number, (Double )value); + case FLOAT : return computeFloatSize (number, (Float )value); + case INT64 : return computeInt64Size (number, (Long )value); + case UINT64 : return computeUInt64Size (number, (Long )value); + case INT32 : return computeInt32Size (number, (Integer )value); + case FIXED64 : return computeFixed64Size (number, (Long )value); + case FIXED32 : return computeFixed32Size (number, (Integer )value); + case BOOL : return computeBoolSize (number, (Boolean )value); + case STRING : return computeStringSize (number, (String )value); + case GROUP : return computeGroupSize (number, (Message )value); + case MESSAGE : return computeMessageSize (number, (Message )value); + case BYTES : return computeBytesSize (number, (ByteString)value); + case UINT32 : return computeUInt32Size (number, (Integer )value); + case SFIXED32: return computeSFixed32Size(number, (Integer )value); + case SFIXED64: return computeSFixed64Size(number, (Long )value); + case SINT32 : return computeSInt32Size (number, (Integer )value); + case SINT64 : return computeSInt64Size (number, (Long )value); + + case ENUM: + return computeEnumSize(number, + ((Descriptors.EnumValueDescriptor)value).getNumber()); + } + + throw new RuntimeException( + "There is no way to get here, but the compiler thinks otherwise."); + } + + // ================================================================= + + /** + * Internal helper that writes the current buffer to the output. The + * buffer position is reset to its initial value when this returns. + */ + private void refreshBuffer() throws IOException { + if (output == null) { + // We're writing to a single buffer. + throw new OutOfSpaceException(); + } + + // Since we have an output stream, this is our buffer + // and buffer offset == 0 + output.write(buffer, 0, position); + position = 0; + } + + /** + * Flushes the stream and forces any buffered bytes to be written. This + * does not flush the underlying OutputStream. + */ + public void flush() throws IOException { + if (output != null) { + refreshBuffer(); + } + } + + /** + * If writing to a flat array, return the space left in the array. + * Otherwise, throws {@code UnsupportedOperationException}. + */ + public int spaceLeft() { + if (output == null) { + return limit - position; + } else { + throw new UnsupportedOperationException( + "spaceLeft() can only be called on CodedOutputStreams that are " + + "writing to a flat array."); + } + } + + /** + * Verifies that {@link #spaceLeft()} returns zero. It's common to create + * a byte array that is exactly big enough to hold a message, then write to + * it with a {@code CodedOutputStream}. Calling {@code checkNoSpaceLeft()} + * after writing verifies that the message was actually as big as expected, + * which can help catch bugs. + */ + public void checkNoSpaceLeft() { + if (spaceLeft() != 0) { + throw new IllegalStateException( + "Did not write as much data as expected."); + } + } + + /** + * If you create a CodedOutputStream around a simple flat array, you must + * not attempt to write more bytes than the array has space. Otherwise, + * this exception will be thrown. + */ + public static class OutOfSpaceException extends IOException { + OutOfSpaceException() { + super("CodedOutputStream was writing to a flat byte array and ran " + + "out of space."); + } + } + + /** Write a single byte. */ + public void writeRawByte(byte value) throws IOException { + if (position == limit) { + refreshBuffer(); + } + + buffer[position++] = value; + } + + /** Write a single byte, represented by an integer value. */ + public void writeRawByte(int value) throws IOException { + writeRawByte((byte) value); + } + + /** Write an array of bytes. */ + public void writeRawBytes(byte[] value) throws IOException { + writeRawBytes(value, 0, value.length); + } + + /** Write part of an array of bytes. */ + public void writeRawBytes(byte[] value, int offset, int length) + throws IOException { + if (limit - position >= length) { + // We have room in the current buffer. + System.arraycopy(value, offset, buffer, position, length); + position += length; + } else { + // Write extends past current buffer. Fill the rest of this buffer and + // flush. + int bytesWritten = limit - position; + System.arraycopy(value, offset, buffer, position, bytesWritten); + offset += bytesWritten; + length -= bytesWritten; + position = limit; + refreshBuffer(); + + // Now deal with the rest. + // Since we have an output stream, this is our buffer + // and buffer offset == 0 + if (length <= limit) { + // Fits in new buffer. + System.arraycopy(value, offset, buffer, 0, length); + position = length; + } else { + // Write is very big. Let's do it all at once. + output.write(value, offset, length); + } + } + } + + /** Encode and write a tag. */ + public void writeTag(int fieldNumber, int wireType) throws IOException { + writeRawVarint32(WireFormat.makeTag(fieldNumber, wireType)); + } + + /** Compute the number of bytes that would be needed to encode a tag. */ + public static int computeTagSize(int fieldNumber) { + return computeRawVarint32Size(WireFormat.makeTag(fieldNumber, 0)); + } + + /** + * Encode and write a varint. {@code value} is treated as + * unsigned, so it won't be sign-extended if negative. + */ + public void writeRawVarint32(int value) throws IOException { + while (true) { + if ((value & ~0x7F) == 0) { + writeRawByte(value); + return; + } else { + writeRawByte((value & 0x7F) | 0x80); + value >>>= 7; + } + } + } + + /** + * Compute the number of bytes that would be needed to encode a varint. + * {@code value} is treated as unsigned, so it won't be sign-extended if + * negative. + */ + public static int computeRawVarint32Size(int value) { + if ((value & (0xffffffff << 7)) == 0) return 1; + if ((value & (0xffffffff << 14)) == 0) return 2; + if ((value & (0xffffffff << 21)) == 0) return 3; + if ((value & (0xffffffff << 28)) == 0) return 4; + return 5; + } + + /** Encode and write a varint. */ + public void writeRawVarint64(long value) throws IOException { + while (true) { + if ((value & ~0x7FL) == 0) { + writeRawByte((int)value); + return; + } else { + writeRawByte(((int)value & 0x7F) | 0x80); + value >>>= 7; + } + } + } + + /** Compute the number of bytes that would be needed to encode a varint. */ + public static int computeRawVarint64Size(long value) { + if ((value & (0xffffffffffffffffL << 7)) == 0) return 1; + if ((value & (0xffffffffffffffffL << 14)) == 0) return 2; + if ((value & (0xffffffffffffffffL << 21)) == 0) return 3; + if ((value & (0xffffffffffffffffL << 28)) == 0) return 4; + if ((value & (0xffffffffffffffffL << 35)) == 0) return 5; + if ((value & (0xffffffffffffffffL << 42)) == 0) return 6; + if ((value & (0xffffffffffffffffL << 49)) == 0) return 7; + if ((value & (0xffffffffffffffffL << 56)) == 0) return 8; + if ((value & (0xffffffffffffffffL << 63)) == 0) return 9; + return 10; + } + + /** Write a little-endian 32-bit integer. */ + public void writeRawLittleEndian32(int value) throws IOException { + writeRawByte((value ) & 0xFF); + writeRawByte((value >> 8) & 0xFF); + writeRawByte((value >> 16) & 0xFF); + writeRawByte((value >> 24) & 0xFF); + } + + public static final int LITTLE_ENDIAN_32_SIZE = 4; + + /** Write a little-endian 64-bit integer. */ + public void writeRawLittleEndian64(long value) throws IOException { + writeRawByte((int)(value ) & 0xFF); + writeRawByte((int)(value >> 8) & 0xFF); + writeRawByte((int)(value >> 16) & 0xFF); + writeRawByte((int)(value >> 24) & 0xFF); + writeRawByte((int)(value >> 32) & 0xFF); + writeRawByte((int)(value >> 40) & 0xFF); + writeRawByte((int)(value >> 48) & 0xFF); + writeRawByte((int)(value >> 56) & 0xFF); + } + + public static final int LITTLE_ENDIAN_64_SIZE = 8; + + /** + * Encode a ZigZag-encoded 32-bit value. ZigZag encodes signed integers + * into values that can be efficiently encoded with varint. (Otherwise, + * negative values must be sign-extended to 64 bits to be varint encoded, + * thus always taking 10 bytes on the wire.) + * + * @param n A signed 32-bit integer. + * @return An unsigned 32-bit integer, stored in a signed int because + * Java has no explicit unsigned support. + */ + public static int encodeZigZag32(int n) { + // Note: the right-shift must be arithmetic + return (n << 1) ^ (n >> 31); + } + + /** + * Encode a ZigZag-encoded 64-bit value. ZigZag encodes signed integers + * into values that can be efficiently encoded with varint. (Otherwise, + * negative values must be sign-extended to 64 bits to be varint encoded, + * thus always taking 10 bytes on the wire.) + * + * @param n A signed 64-bit integer. + * @return An unsigned 64-bit integer, stored in a signed int because + * Java has no explicit unsigned support. + */ + public static long encodeZigZag64(long n) { + // Note: the right-shift must be arithmetic + return (n << 1) ^ (n >> 63); + } +} diff --git a/java/src/main/java/com/google/protobuf/Descriptors.java b/java/src/main/java/com/google/protobuf/Descriptors.java new file mode 100644 index 00000000..9ad8e521 --- /dev/null +++ b/java/src/main/java/com/google/protobuf/Descriptors.java @@ -0,0 +1,1635 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.protobuf; + +import com.google.protobuf.DescriptorProtos.*; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Contains a collection of classes which describe protocol message types. + * + * Every message type has a {@link Descriptors.Descriptor}, which lists all + * its fields and other information about a type. You can get a message + * type's descriptor by calling {@code MessageType.getDescriptor()}, or + * (given a message object of the type) {@code message.getDescriptorForType()}. + * + * Descriptors are built from DescriptorProtos, as defined in + * {@code net/proto2/proto/descriptor.proto}. + * + * @author kenton@google.com Kenton Varda + */ +public final class Descriptors { + /** + * Describes a {@code .proto} file, including everything defined within. + */ + public static final class FileDescriptor { + /** Convert the descriptor to its protocol message representation. */ + public FileDescriptorProto toProto() { return proto; } + + /** Get the file name. */ + public String getName() { return proto.getName(); } + + /** + * Get the proto package name. This is the package name given by the + * {@code package} statement in the {@code .proto} file, which differs + * from the Java package. + */ + public String getPackage() { return proto.getPackage(); } + + /** Get the {@code FileOptions}, defined in {@code descriptor.proto}. */ + public FileOptions getOptions() { return proto.getOptions(); } + + /** Get a list of top-level message types declared in this file. */ + public List<Descriptor> getMessageTypes() { + return Collections.unmodifiableList(Arrays.asList(messageTypes)); + } + + /** Get a list of top-level enum types declared in this file. */ + public List<EnumDescriptor> getEnumTypes() { + return Collections.unmodifiableList(Arrays.asList(enumTypes)); + } + + /** Get a list of top-level services declared in this file. */ + public List<ServiceDescriptor> getServices() { + return Collections.unmodifiableList(Arrays.asList(services)); + } + + /** Get a list of top-level extensions declared in this file. */ + public List<FieldDescriptor> getExtensions() { + return Collections.unmodifiableList(Arrays.asList(extensions)); + } + + /** Get a list of this file's dependencies (imports). */ + public List<FileDescriptor> getDependencies() { + return Collections.unmodifiableList(Arrays.asList(dependencies)); + } + + /** + * Find a message type in the file by name. Does not find nested types. + * + * @param name The unqualified type name to look for. + * @return The message type's descriptor, or {@code null} if not found. + */ + public Descriptor findMessageTypeByName(String name) { + // Don't allow looking up nested types. This will make optimization + // easier later. + if (name.indexOf('.') != -1) return null; + if (getPackage().length() > 0) { + name = getPackage() + "." + name; + } + GenericDescriptor result = pool.findSymbol(name); + if (result != null && result instanceof Descriptor && + result.getFile() == this) { + return (Descriptor)result; + } else { + return null; + } + } + + /** + * Find an enum type in the file by name. Does not find nested types. + * + * @param name The unqualified type name to look for. + * @return The enum type's descriptor, or {@code null} if not found. + */ + public EnumDescriptor findEnumTypeByName(String name) { + // Don't allow looking up nested types. This will make optimization + // easier later. + if (name.indexOf('.') != -1) return null; + if (getPackage().length() > 0) { + name = getPackage() + "." + name; + } + GenericDescriptor result = pool.findSymbol(name); + if (result != null && result instanceof EnumDescriptor && + result.getFile() == this) { + return (EnumDescriptor)result; + } else { + return null; + } + } + + /** + * Find a service type in the file by name. + * + * @param name The unqualified type name to look for. + * @return The service type's descriptor, or {@code null} if not found. + */ + public ServiceDescriptor findServiceByName(String name) { + // Don't allow looking up nested types. This will make optimization + // easier later. + if (name.indexOf('.') != -1) return null; + if (getPackage().length() > 0) { + name = getPackage() + "." + name; + } + GenericDescriptor result = pool.findSymbol(name); + if (result != null && result instanceof ServiceDescriptor && + result.getFile() == this) { + return (ServiceDescriptor)result; + } else { + return null; + } + } + + /** + * Find an extension in the file by name. Does not find extensions nested + * inside message types. + * + * @param name The unqualified extension name to look for. + * @return The extension's descriptor, or {@code null} if not found. + */ + public FieldDescriptor findExtensionByName(String name) { + if (name.indexOf('.') != -1) return null; + if (getPackage().length() > 0) { + name = getPackage() + "." + name; + } + GenericDescriptor result = pool.findSymbol(name); + if (result != null && result instanceof FieldDescriptor && + result.getFile() == this) { + return (FieldDescriptor)result; + } else { + return null; + } + } + + /** + * Construct a {@code FileDescriptor}. + * + * @param proto The protocol message form of the FileDescriptor. + * @param dependencies {@code FileDescriptor}s corresponding to all of + * the file's dependencies, in the exact order listed + * in {@code proto}. + * @throws DescriptorValidationException {@code proto} is not a valid + * descriptor. This can occur for a number of reasons, e.g. + * because a field has an undefined type or because two messages + * were defined with the same name. + */ + public static FileDescriptor buildFrom(FileDescriptorProto proto, + FileDescriptor[] dependencies) + throws DescriptorValidationException { + // Building decsriptors involves two steps: translating and linking. + // In the translation step (implemented by FileDescriptor's + // constructor), we build an object tree mirroring the + // FileDescriptorProto's tree and put all of the descriptors into the + // DescriptorPool's lookup tables. In the linking step, we look up all + // type references in the DescriptorPool, so that, for example, a + // FieldDescriptor for an embedded message contains a pointer directly + // to the Descriptor for that message's type. We also detect undefined + // types in the linking step. + DescriptorPool pool = new DescriptorPool(dependencies); + FileDescriptor result = new FileDescriptor(proto, dependencies, pool); + + if (dependencies.length != proto.getDependencyCount()) { + throw new DescriptorValidationException(result, + "Dependencies passed to FileDescriptor.buildFrom() don't match " + + "those listed in the FileDescriptorProto."); + } + for (int i = 0; i < proto.getDependencyCount(); i++) { + if (!dependencies[i].getName().equals(proto.getDependency(i))) { + throw new DescriptorValidationException(result, + "Dependencies passed to FileDescriptor.buildFrom() don't match " + + "those listed in the FileDescriptorProto."); + } + } + + result.crossLink(); + return result; + } + + /** + * This method is to be called by generated code only. It is equivalent + * to {@code buildFrom} except that the {@code FileDescriptorProto} is + * encoded in protocol buffer wire format. + */ + public static FileDescriptor internalBuildGeneratedFileFrom( + String descriptorData, FileDescriptor[] dependencies) + throws DescriptorValidationException, + InvalidProtocolBufferException { + // Hack: We can't embed a raw byte array inside generated Java code + // (at least, not efficiently), but we can embed Strings. So, the + // protocol compiler embeds the FileDescriptorProto as a giant + // string literal which is passed to this function to construct the + // file's FileDescriptor. The string literal contains only 8-bit + // characters, each one representing a byte of the FileDescriptorProto's + // serialized form. So, if we convert it to bytes in ISO-8859-1, we + // should get the original bytes that we want. + try { + FileDescriptorProto proto = + FileDescriptorProto.parseFrom(descriptorData.getBytes("ISO-8859-1")); + return buildFrom(proto, dependencies); + } catch (java.io.UnsupportedEncodingException e) { + throw new RuntimeException( + "Standard encoding ISO-8859-1 not supported by JVM.", e); + } + } + + private final FileDescriptorProto proto; + private final Descriptor[] messageTypes; + private final EnumDescriptor[] enumTypes; + private final ServiceDescriptor[] services; + private final FieldDescriptor[] extensions; + private final FileDescriptor[] dependencies; + private final DescriptorPool pool; + + private FileDescriptor(FileDescriptorProto proto, + FileDescriptor[] dependencies, + DescriptorPool pool) + throws DescriptorValidationException { + this.pool = pool; + this.proto = proto; + this.dependencies = dependencies.clone(); + + pool.addPackage(getPackage(), this); + + messageTypes = new Descriptor[proto.getMessageTypeCount()]; + for (int i = 0; i < proto.getMessageTypeCount(); i++) { + messageTypes[i] = + new Descriptor(proto.getMessageType(i), this, null, i); + } + + enumTypes = new EnumDescriptor[proto.getEnumTypeCount()]; + for (int i = 0; i < proto.getEnumTypeCount(); i++) { + enumTypes[i] = new EnumDescriptor(proto.getEnumType(i), this, null, i); + } + + services = new ServiceDescriptor[proto.getServiceCount()]; + for (int i = 0; i < proto.getServiceCount(); i++) { + services[i] = new ServiceDescriptor(proto.getService(i), this, i); + } + + extensions = new FieldDescriptor[proto.getExtensionCount()]; + for (int i = 0; i < proto.getExtensionCount(); i++) { + extensions[i] = new FieldDescriptor( + proto.getExtension(i), this, null, i, true); + } + } + + /** Look up and cross-link all field types, etc. */ + private void crossLink() throws DescriptorValidationException { + for (int i = 0; i < messageTypes.length; i++) { + messageTypes[i].crossLink(); + } + + for (int i = 0; i < services.length; i++) { + services[i].crossLink(); + } + + for (int i = 0; i < extensions.length; i++) { + extensions[i].crossLink(); + } + } + } + + // ================================================================= + + /** Describes a message type. */ + public static final class Descriptor implements GenericDescriptor { + /** + * Get the index of this descriptor within its parent. In other words, + * given a {@link FileDescriptor} {@code file}, the following is true: + * <pre> + * for all i in [0, file.getMessageTypeCount()): + * file.getMessageType(i).getIndex() == i + * </pre> + * Similarly, for a {@link Descriptor} {@code messageType}: + * <pre> + * for all i in [0, messageType.getNestedTypeCount()): + * messageType.getNestedType(i).getIndex() == i + * </pre> + */ + public int getIndex() { return index; } + + /** Convert the descriptor to its protocol message representation. */ + public DescriptorProto toProto() { return proto; } + + /** Get the type's unqualified name. */ + public String getName() { return proto.getName(); } + + /** + * Get the type's fully-qualified name, within the proto language's + * namespace. This differs from the Java name. For example, given this + * {@code .proto}: + * <pre> + * package foo.bar; + * option java_package = "com.example.protos" + * message Baz {} + * </pre> + * {@code Baz}'s full name is "foo.bar.Baz". + */ + public String getFullName() { return fullName; } + + /** Get the {@link FileDescriptor} containing this descriptor. */ + public FileDescriptor getFile() { return file; } + + /** If this is a nested type, get the outer descriptor, otherwise null. */ + public Descriptor getContainingType() { return containingType; } + + /** Get the {@code MessageOptions}, defined in {@code descriptor.proto}. */ + public MessageOptions getOptions() { return proto.getOptions(); } + + /** Get a list of this message type's fields. */ + public List<FieldDescriptor> getFields() { + return Collections.unmodifiableList(Arrays.asList(fields)); + } + + /** Get a list of this message type's extensions. */ + public List<FieldDescriptor> getExtensions() { + return Collections.unmodifiableList(Arrays.asList(extensions)); + } + + /** Get a list of message types nested within this one. */ + public List<Descriptor> getNestedTypes() { + return Collections.unmodifiableList(Arrays.asList(nestedTypes)); + } + + /** Get a list of enum types nested within this one. */ + public List<EnumDescriptor> getEnumTypes() { + return Collections.unmodifiableList(Arrays.asList(enumTypes)); + } + + /** Determines if the given field number is an extension. */ + public boolean isExtensionNumber(int number) { + for (DescriptorProto.ExtensionRange range : proto.getExtensionRangeList()) { + if (range.getStart() <= number && number < range.getEnd()) { + return true; + } + } + return false; + } + + /** + * Finds a field by name. + * @param name The unqualified name of the field (e.g. "foo"). + * @return The field's descriptor, or {@code null} if not found. + */ + public FieldDescriptor findFieldByName(String name) { + GenericDescriptor result = file.pool.findSymbol(fullName + "." + name); + if (result != null && result instanceof FieldDescriptor) { + return (FieldDescriptor)result; + } else { + return null; + } + } + + /** + * Finds a field by field number. + * @param number The field number within this message type. + * @return The field's descriptor, or {@code null} if not found. + */ + public FieldDescriptor findFieldByNumber(int number) { + return file.pool.fieldsByNumber.get( + new DescriptorPool.DescriptorIntPair(this, number)); + } + + /** + * Finds a nested message type by name. + * @param name The unqualified name of the nested type (e.g. "Foo"). + * @return The types's descriptor, or {@code null} if not found. + */ + public Descriptor findNestedTypeByName(String name) { + GenericDescriptor result = file.pool.findSymbol(fullName + "." + name); + if (result != null && result instanceof Descriptor) { + return (Descriptor)result; + } else { + return null; + } + } + + /** + * Finds a nested enum type by name. + * @param name The unqualified name of the nested type (e.g. "Foo"). + * @return The types's descriptor, or {@code null} if not found. + */ + public EnumDescriptor findEnumTypeByName(String name) { + GenericDescriptor result = file.pool.findSymbol(fullName + "." + name); + if (result != null && result instanceof EnumDescriptor) { + return (EnumDescriptor)result; + } else { + return null; + } + } + + private final int index; + private final DescriptorProto proto; + private final String fullName; + private final FileDescriptor file; + private final Descriptor containingType; + private final Descriptor[] nestedTypes; + private final EnumDescriptor[] enumTypes; + private final FieldDescriptor[] fields; + private final FieldDescriptor[] extensions; + + private Descriptor(DescriptorProto proto, + FileDescriptor file, + Descriptor parent, + int index) + throws DescriptorValidationException { + this.index = index; + this.proto = proto; + this.fullName = computeFullName(file, parent, proto.getName()); + this.file = file; + this.containingType = parent; + + this.nestedTypes = new Descriptor[proto.getNestedTypeCount()]; + for (int i = 0; i < proto.getNestedTypeCount(); i++) { + this.nestedTypes[i] = new Descriptor( + proto.getNestedType(i), file, this, i); + } + + this.enumTypes = new EnumDescriptor[proto.getEnumTypeCount()]; + for (int i = 0; i < proto.getEnumTypeCount(); i++) { + this.enumTypes[i] = new EnumDescriptor( + proto.getEnumType(i), file, this, i); + } + + this.fields = new FieldDescriptor[proto.getFieldCount()]; + for (int i = 0; i < proto.getFieldCount(); i++) { + this.fields[i] = new FieldDescriptor( + proto.getField(i), file, this, i, false); + } + + this.extensions = new FieldDescriptor[proto.getExtensionCount()]; + for (int i = 0; i < proto.getExtensionCount(); i++) { + this.extensions[i] = new FieldDescriptor( + proto.getExtension(i), file, this, i, true); + } + + file.pool.addSymbol(this); + } + + /** Look up and cross-link all field types, etc. */ + private void crossLink() throws DescriptorValidationException { + for (int i = 0; i < nestedTypes.length; i++) { + nestedTypes[i].crossLink(); + } + + for (int i = 0; i < fields.length; i++) { + fields[i].crossLink(); + } + + for (int i = 0; i < extensions.length; i++) { + extensions[i].crossLink(); + } + } + } + + // ================================================================= + + /** Describes a field of a message type. */ + public static final class FieldDescriptor + implements GenericDescriptor, Comparable<FieldDescriptor> { + /** + * Get the index of this descriptor within its parent. + * @see Descriptors.Descriptor#getIndex() + */ + public int getIndex() { return index; } + + /** Convert the descriptor to its protocol message representation. */ + public FieldDescriptorProto toProto() { return proto; } + + /** Get the field's unqualified name. */ + public String getName() { return proto.getName(); } + + /** Get the field's number. */ + public int getNumber() { return proto.getNumber(); } + + /** + * Get the field's fully-qualified name. + * @see Descriptors.Descriptor#getFullName() + */ + public String getFullName() { return fullName; } + + /** + * Get the field's java type. This is just for convenience. Every + * {@code FieldDescriptorProto.Type} maps to exactly one Java type. + */ + public JavaType getJavaType() { return type.getJavaType(); } + + /** Get the {@code FileDescriptor} containing this descriptor. */ + public FileDescriptor getFile() { return file; } + + /** Get the field's declared type. */ + public Type getType() { return type; } + + /** Is this field declared required? */ + public boolean isRequired() { + return proto.getLabel() == FieldDescriptorProto.Label.LABEL_REQUIRED; + } + + /** Is this field declared optional? */ + public boolean isOptional() { + return proto.getLabel() == FieldDescriptorProto.Label.LABEL_OPTIONAL; + } + + /** Is this field declared repeated? */ + public boolean isRepeated() { + return proto.getLabel() == FieldDescriptorProto.Label.LABEL_REPEATED; + } + + /** Returns true if the field had an explicitly-defined default value. */ + public boolean hasDefaultValue() { return proto.hasDefaultValue(); } + + /** + * Returns the field's default value. Valid for all types except for + * messages and groups. For all other types, the object returned is of + * the same class that would returned by Message.getField(this). + */ + public Object getDefaultValue() { + if (getJavaType() == JavaType.MESSAGE) { + throw new UnsupportedOperationException( + "FieldDescriptor.getDefaultValue() called on an embedded message " + + "field."); + } + return defaultValue; + } + + /** Get the {@code FieldOptions}, defined in {@code descriptor.proto}. */ + public FieldOptions getOptions() { return proto.getOptions(); } + + /** Is this field an extension? */ + public boolean isExtension() { return proto.hasExtendee(); } + + /** + * Get the field's containing type. For extensions, this is the type being + * extended, not the location where the extension was defined. See + * {@link #getExtensionScope()}. + */ + public Descriptor getContainingType() { return containingType; } + + /** + * For extensions defined nested within message types, gets the outer + * type. Not valid for non-extension fields. For example, consider + * this {@code .proto} file: + * <pre> + * message Foo { + * extensions 1000 to max; + * } + * extend Foo { + * optional int32 baz = 1234; + * } + * message Bar { + * extend Foo { + * optional int32 qux = 4321; + * } + * } + * </pre> + * Both {@code baz}'s and {@code qux}'s containing type is {@code Foo}. + * However, {@code baz}'s extension scope is {@code null} while + * {@code qux}'s extension scope is {@code Bar}. + */ + public Descriptor getExtensionScope() { + if (!isExtension()) { + throw new UnsupportedOperationException( + "This field is not an extension."); + } + return extensionScope; + } + + /** For embedded message and group fields, gets the field's type. */ + public Descriptor getMessageType() { + if (getJavaType() != JavaType.MESSAGE) { + throw new UnsupportedOperationException( + "This field is not of message type."); + } + return messageType; + } + + /** For enum fields, gets the field's type. */ + public EnumDescriptor getEnumType() { + if (getJavaType() != JavaType.ENUM) { + throw new UnsupportedOperationException( + "This field is not of enum type."); + } + return enumType; + } + + /** + * Compare with another {@code FieldDescriptor}. This orders fields in + * "canonical" order, which simply means ascending order by field number. + * {@code other} must be a field of the same type -- i.e. + * {@code getContainingType()} must return the same {@code Descriptor} for + * both fields. + * + * @return negative, zero, or positive if {@code this} is less than, + * equal to, or greater than {@code other}, respectively. + */ + public int compareTo(FieldDescriptor other) { + if (other.containingType != containingType) { + throw new IllegalArgumentException( + "FieldDescriptors can only be compared to other FieldDescriptors " + + "for fields of the same message type."); + } + return getNumber() - other.getNumber(); + } + + private final int index; + + private final FieldDescriptorProto proto; + private final String fullName; + private final FileDescriptor file; + private final Descriptor extensionScope; + + // Possibly initialized during cross-linking. + private Type type; + private Descriptor containingType; + private Descriptor messageType; + private EnumDescriptor enumType; + private Object defaultValue; + + public static enum Type { + DOUBLE (FieldDescriptorProto.Type.TYPE_DOUBLE , JavaType.DOUBLE ), + FLOAT (FieldDescriptorProto.Type.TYPE_FLOAT , JavaType.FLOAT ), + INT64 (FieldDescriptorProto.Type.TYPE_INT64 , JavaType.LONG ), + UINT64 (FieldDescriptorProto.Type.TYPE_UINT64 , JavaType.LONG ), + INT32 (FieldDescriptorProto.Type.TYPE_INT32 , JavaType.INT ), + FIXED64 (FieldDescriptorProto.Type.TYPE_FIXED64 , JavaType.LONG ), + FIXED32 (FieldDescriptorProto.Type.TYPE_FIXED32 , JavaType.INT ), + BOOL (FieldDescriptorProto.Type.TYPE_BOOL , JavaType.BOOLEAN ), + STRING (FieldDescriptorProto.Type.TYPE_STRING , JavaType.STRING ), + GROUP (FieldDescriptorProto.Type.TYPE_GROUP , JavaType.MESSAGE ), + MESSAGE (FieldDescriptorProto.Type.TYPE_MESSAGE , JavaType.MESSAGE ), + BYTES (FieldDescriptorProto.Type.TYPE_BYTES , JavaType.BYTE_STRING), + UINT32 (FieldDescriptorProto.Type.TYPE_UINT32 , JavaType.INT ), + ENUM (FieldDescriptorProto.Type.TYPE_ENUM , JavaType.ENUM ), + SFIXED32(FieldDescriptorProto.Type.TYPE_SFIXED32, JavaType.INT ), + SFIXED64(FieldDescriptorProto.Type.TYPE_SFIXED64, JavaType.LONG ), + SINT32 (FieldDescriptorProto.Type.TYPE_SINT32 , JavaType.INT ), + SINT64 (FieldDescriptorProto.Type.TYPE_SINT64 , JavaType.LONG ); + + private Type(FieldDescriptorProto.Type proto, JavaType javaType) { + this.proto = proto; + this.javaType = javaType; + + if (this.ordinal() != proto.getNumber() - 1) { + throw new RuntimeException( + "descriptor.proto changed but Desrciptors.java wasn't updated."); + } + } + + private FieldDescriptorProto.Type proto; + private JavaType javaType; + + public FieldDescriptorProto.Type toProto() { return proto; } + public JavaType getJavaType() { return javaType; } + + public static Type valueOf(FieldDescriptorProto.Type type) { + return values()[type.getNumber() - 1]; + } + } + + static { + // Refuse to init if someone added a new declared type. + if (Type.values().length != FieldDescriptorProto.Type.values().length) { + throw new RuntimeException( + "descriptor.proto has a new declared type but Desrciptors.java " + + "wasn't updated."); + } + } + + public static enum JavaType { + INT(0), + LONG(0L), + FLOAT(0F), + DOUBLE(0D), + BOOLEAN(false), + STRING(""), + BYTE_STRING(ByteString.EMPTY), + ENUM(null), + MESSAGE(null); + + private JavaType(Object defaultDefault) { + this.defaultDefault = defaultDefault; + } + + /** + * The default default value for fields of this type, if it's a primitive + * type. This is meant for use inside this file only, hence is private. + */ + private Object defaultDefault; + } + + private FieldDescriptor(FieldDescriptorProto proto, + FileDescriptor file, + Descriptor parent, + int index, + boolean isExtension) + throws DescriptorValidationException { + this.index = index; + this.proto = proto; + this.fullName = computeFullName(file, parent, proto.getName()); + this.file = file; + + if (proto.hasType()) { + this.type = Type.valueOf(proto.getType()); + } + + if (getNumber() <= 0) { + throw new DescriptorValidationException(this, + "Field numbers must be positive integers."); + } + + if (isExtension) { + if (!proto.hasExtendee()) { + throw new DescriptorValidationException(this, + "FieldDescriptorProto.extendee not set for extension field."); + } + this.containingType = null; // Will be filled in when cross-linking + if (parent != null) { + this.extensionScope = parent; + } else { + this.extensionScope = null; + } + } else { + if (proto.hasExtendee()) { + throw new DescriptorValidationException(this, + "FieldDescriptorProto.extendee set for non-extension field."); + } + this.containingType = parent; + this.extensionScope = null; + } + + file.pool.addSymbol(this); + } + + /** Look up and cross-link all field types, etc. */ + private void crossLink() throws DescriptorValidationException { + if (proto.hasExtendee()) { + GenericDescriptor extendee = + file.pool.lookupSymbol(proto.getExtendee(), this); + if (!(extendee instanceof Descriptor)) { + throw new DescriptorValidationException(this, + "\"" + proto.getExtendee() + "\" is not a message type."); + } + this.containingType = (Descriptor)extendee; + + if (!getContainingType().isExtensionNumber(getNumber())) { + throw new DescriptorValidationException(this, + "\"" + getContainingType().getFullName() + "\" does not declare " + + getNumber() + " as an extension number."); + } + } + + if (proto.hasTypeName()) { + GenericDescriptor typeDescriptor = + file.pool.lookupSymbol(proto.getTypeName(), this); + + if (!proto.hasType()) { + // Choose field type based on symbol. + if (typeDescriptor instanceof Descriptor) { + this.type = Type.MESSAGE; + } else if (typeDescriptor instanceof EnumDescriptor) { + this.type = Type.ENUM; + } else { + throw new DescriptorValidationException(this, + "\"" + proto.getTypeName() + "\" is not a type."); + } + } + + if (getJavaType() == JavaType.MESSAGE) { + if (!(typeDescriptor instanceof Descriptor)) { + throw new DescriptorValidationException(this, + "\"" + proto.getTypeName() + "\" is not a message type."); + } + this.messageType = (Descriptor)typeDescriptor; + + if (proto.hasDefaultValue()) { + throw new DescriptorValidationException(this, + "Messages can't have default values."); + } + } else if (getJavaType() == JavaType.ENUM) { + if (!(typeDescriptor instanceof EnumDescriptor)) { + throw new DescriptorValidationException(this, + "\"" + proto.getTypeName() + "\" is not an enum type."); + } + this.enumType = (EnumDescriptor)typeDescriptor; + } else { + throw new DescriptorValidationException(this, + "Field with primitive type has type_name."); + } + } else { + if (getJavaType() == JavaType.MESSAGE || + getJavaType() == JavaType.ENUM) { + throw new DescriptorValidationException(this, + "Field with message or enum type missing type_name."); + } + } + + // We don't attempt to parse the default value until here because for + // enums we need the enum type's descriptor. + if (proto.hasDefaultValue()) { + if (isRepeated()) { + throw new DescriptorValidationException(this, + "Repeated fields cannot have default values."); + } + + try { + switch (getType()) { + case INT32: + case SINT32: + case SFIXED32: + defaultValue = TextFormat.parseInt32(proto.getDefaultValue()); + break; + case UINT32: + case FIXED32: { + defaultValue = TextFormat.parseUInt32(proto.getDefaultValue()); + break; + } + case INT64: + case SINT64: + case SFIXED64: + defaultValue = TextFormat.parseInt64(proto.getDefaultValue()); + break; + case UINT64: + case FIXED64: { + defaultValue = TextFormat.parseUInt64(proto.getDefaultValue()); + break; + } + case FLOAT: + defaultValue = Float.valueOf(proto.getDefaultValue()); + break; + case DOUBLE: + defaultValue = Double.valueOf(proto.getDefaultValue()); + break; + case BOOL: + defaultValue = Boolean.valueOf(proto.getDefaultValue()); + break; + case STRING: + defaultValue = proto.getDefaultValue(); + break; + case BYTES: + try { + defaultValue = + TextFormat.unescapeBytes(proto.getDefaultValue()); + } catch (TextFormat.InvalidEscapeSequence e) { + throw new DescriptorValidationException(this, + "Couldn't parse default value: " + e.getMessage()); + } + break; + case ENUM: + defaultValue = enumType.findValueByName(proto.getDefaultValue()); + if (defaultValue == null) { + throw new DescriptorValidationException(this, + "Unknown enum default value: \"" + + proto.getDefaultValue() + "\""); + } + break; + case MESSAGE: + case GROUP: + throw new DescriptorValidationException(this, + "Message type had default value."); + } + } catch (NumberFormatException e) { + DescriptorValidationException validationException = + new DescriptorValidationException(this, + "Could not parse default value: \"" + + proto.getDefaultValue() + "\""); + validationException.initCause(e); + throw validationException; + } + } else { + // Determine the default default for this field. + if (isRepeated()) { + defaultValue = Collections.EMPTY_LIST; + } else { + switch (getJavaType()) { + case ENUM: + // We guarantee elsewhere that an enum type always has at least + // one possible value. + defaultValue = enumType.getValues().get(0); + break; + case MESSAGE: + defaultValue = null; + break; + default: + defaultValue = getJavaType().defaultDefault; + break; + } + } + } + + if (!isExtension()) { + file.pool.addFieldByNumber(this); + } + + if (containingType != null && + containingType.getOptions().getMessageSetWireFormat()) { + if (isExtension()) { + if (!isOptional() || getType() != Type.MESSAGE) { + throw new DescriptorValidationException(this, + "Extensions of MessageSets must be optional messages."); + } + } else { + throw new DescriptorValidationException(this, + "MessageSets cannot have fields, only extensions."); + } + } + } + } + + // ================================================================= + + /** Describes an enum type. */ + public static final class EnumDescriptor implements GenericDescriptor { + /** + * Get the index of this descriptor within its parent. + * @see Descriptors.Descriptor#getIndex() + */ + public int getIndex() { return index; } + + /** Convert the descriptor to its protocol message representation. */ + public EnumDescriptorProto toProto() { return proto; } + + /** Get the type's unqualified name. */ + public String getName() { return proto.getName(); } + + /** + * Get the type's fully-qualified name. + * @see Descriptors.Descriptor#getFullName() + */ + public String getFullName() { return fullName; } + + /** Get the {@link FileDescriptor} containing this descriptor. */ + public FileDescriptor getFile() { return file; } + + /** If this is a nested type, get the outer descriptor, otherwise null. */ + public Descriptor getContainingType() { return containingType; } + + /** Get the {@code EnumOptions}, defined in {@code descriptor.proto}. */ + public EnumOptions getOptions() { return proto.getOptions(); } + + /** Get a list of defined values for this enum. */ + public List<EnumValueDescriptor> getValues() { + return Collections.unmodifiableList(Arrays.asList(values)); + } + + /** + * Find an enum value by name. + * @param name The unqualified name of the value (e.g. "FOO"). + * @return the value's decsriptor, or {@code null} if not found. + */ + public EnumValueDescriptor findValueByName(String name) { + GenericDescriptor result = file.pool.findSymbol(fullName + "." + name); + if (result != null && result instanceof EnumValueDescriptor) { + return (EnumValueDescriptor)result; + } else { + return null; + } + } + + /** + * Find an enum value by number. If multiple enum values have the same + * number, this returns the first defined value with that number. + * @param number The value's number. + * @return the value's decsriptor, or {@code null} if not found. + */ + public EnumValueDescriptor findValueByNumber(int number) { + return file.pool.enumValuesByNumber.get( + new DescriptorPool.DescriptorIntPair(this, number)); + } + + private final int index; + private final EnumDescriptorProto proto; + private final String fullName; + private final FileDescriptor file; + private final Descriptor containingType; + private EnumValueDescriptor[] values; + + private EnumDescriptor(EnumDescriptorProto proto, + FileDescriptor file, + Descriptor parent, + int index) + throws DescriptorValidationException { + this.index = index; + this.proto = proto; + this.fullName = computeFullName(file, parent, proto.getName()); + this.file = file; + this.containingType = parent; + + if (proto.getValueCount() == 0) { + // We cannot allow enums with no values because this would mean there + // would be no valid default value for fields of this type. + throw new DescriptorValidationException(this, + "Enums must contain at least one value."); + } + + values = new EnumValueDescriptor[proto.getValueCount()]; + for (int i = 0; i < proto.getValueCount(); i++) { + this.values[i] = new EnumValueDescriptor( + proto.getValue(i), file, this, i); + } + + file.pool.addSymbol(this); + } + } + + // ================================================================= + + /** + * Describes one value within an enum type. Note that multiple defined + * values may have the same number. In generated Java code, all values + * with the same number after the first become aliases of the first. + * However, they still have independent EnumValueDescriptors. + */ + public static final class EnumValueDescriptor implements GenericDescriptor { + /** + * Get the index of this descriptor within its parent. + * @see Descriptors.Descriptor#getIndex() + */ + public int getIndex() { return index; } + + /** Convert the descriptor to its protocol message representation. */ + public EnumValueDescriptorProto toProto() { return proto; } + + /** Get the value's unqualified name. */ + public String getName() { return proto.getName(); } + + /** Get the value's number. */ + public int getNumber() { return proto.getNumber(); } + + /** + * Get the value's fully-qualified name. + * @see Descriptors.Descriptor#getFullName() + */ + public String getFullName() { return fullName; } + + /** Get the {@link FileDescriptor} containing this descriptor. */ + public FileDescriptor getFile() { return file; } + + /** Get the value's enum type. */ + public EnumDescriptor getType() { return type; } + + /** + * Get the {@code EnumValueOptions}, defined in {@code descriptor.proto}. + */ + public EnumValueOptions getOptions() { return proto.getOptions(); } + + private final int index; + private final EnumValueDescriptorProto proto; + private final String fullName; + private final FileDescriptor file; + private final EnumDescriptor type; + + private EnumValueDescriptor(EnumValueDescriptorProto proto, + FileDescriptor file, + EnumDescriptor parent, + int index) + throws DescriptorValidationException { + this.index = index; + this.proto = proto; + this.file = file; + this.type = parent; + + this.fullName = parent.getFullName() + "." + proto.getName(); + + file.pool.addSymbol(this); + file.pool.addEnumValueByNumber(this); + } + } + + // ================================================================= + + /** Describes a service type. */ + public static final class ServiceDescriptor implements GenericDescriptor { + /** + * Get the index of this descriptor within its parent. + * * @see Descriptors.Descriptor#getIndex() + */ + public int getIndex() { return index; } + + /** Convert the descriptor to its protocol message representation. */ + public ServiceDescriptorProto toProto() { return proto; } + + /** Get the type's unqualified name. */ + public String getName() { return proto.getName(); } + + /** + * Get the type's fully-qualified name. + * @see Descriptors.Descriptor#getFullName() + */ + public String getFullName() { return fullName; } + + /** Get the {@link FileDescriptor} containing this descriptor. */ + public FileDescriptor getFile() { return file; } + + /** Get the {@code ServiceOptions}, defined in {@code descriptor.proto}. */ + public ServiceOptions getOptions() { return proto.getOptions(); } + + /** Get a list of methods for this service. */ + public List<MethodDescriptor> getMethods() { + return Collections.unmodifiableList(Arrays.asList(methods)); + } + + /** + * Find a method by name. + * @param name The unqualified name of the method (e.g. "Foo"). + * @return the method's decsriptor, or {@code null} if not found. + */ + public MethodDescriptor findMethodByName(String name) { + GenericDescriptor result = file.pool.findSymbol(fullName + "." + name); + if (result != null && result instanceof MethodDescriptor) { + return (MethodDescriptor)result; + } else { + return null; + } + } + + private final int index; + private final ServiceDescriptorProto proto; + private final String fullName; + private final FileDescriptor file; + private MethodDescriptor[] methods; + + private ServiceDescriptor(ServiceDescriptorProto proto, + FileDescriptor file, + int index) + throws DescriptorValidationException { + this.index = index; + this.proto = proto; + this.fullName = computeFullName(file, null, proto.getName()); + this.file = file; + + this.methods = new MethodDescriptor[proto.getMethodCount()]; + for (int i = 0; i < proto.getMethodCount(); i++) { + this.methods[i] = new MethodDescriptor( + proto.getMethod(i), file, this, i); + } + + file.pool.addSymbol(this); + } + + private void crossLink() throws DescriptorValidationException { + for (int i = 0; i < methods.length; i++) { + methods[i].crossLink(); + } + } + } + + // ================================================================= + + /** + * Describes one method within a service type. + */ + public static final class MethodDescriptor implements GenericDescriptor { + /** + * Get the index of this descriptor within its parent. + * * @see Descriptors.Descriptor#getIndex() + */ + public int getIndex() { return index; } + + /** Convert the descriptor to its protocol message representation. */ + public MethodDescriptorProto toProto() { return proto; } + + /** Get the method's unqualified name. */ + public String getName() { return proto.getName(); } + + /** + * Get the method's fully-qualified name. + * @see Descriptors.Descriptor#getFullName() + */ + public String getFullName() { return fullName; } + + /** Get the {@link FileDescriptor} containing this descriptor. */ + public FileDescriptor getFile() { return file; } + + /** Get the method's service type. */ + public ServiceDescriptor getService() { return service; } + + /** Get the method's input type. */ + public Descriptor getInputType() { return inputType; } + + /** Get the method's output type. */ + public Descriptor getOutputType() { return outputType; } + + /** + * Get the {@code MethodOptions}, defined in {@code descriptor.proto}. + */ + public MethodOptions getOptions() { return proto.getOptions(); } + + private final int index; + private final MethodDescriptorProto proto; + private final String fullName; + private final FileDescriptor file; + private final ServiceDescriptor service; + + // Initialized during cross-linking. + private Descriptor inputType; + private Descriptor outputType; + + private MethodDescriptor(MethodDescriptorProto proto, + FileDescriptor file, + ServiceDescriptor parent, + int index) + throws DescriptorValidationException { + this.index = index; + this.proto = proto; + this.file = file; + this.service = parent; + + this.fullName = parent.getFullName() + "." + proto.getName(); + + file.pool.addSymbol(this); + } + + private void crossLink() throws DescriptorValidationException { + GenericDescriptor inputType = + file.pool.lookupSymbol(proto.getInputType(), this); + if (!(inputType instanceof Descriptor)) { + throw new DescriptorValidationException(this, + "\"" + proto.getInputType() + "\" is not a message type."); + } + this.inputType = (Descriptor)inputType; + + GenericDescriptor outputType = + file.pool.lookupSymbol(proto.getOutputType(), this); + if (!(outputType instanceof Descriptor)) { + throw new DescriptorValidationException(this, + "\"" + proto.getOutputType() + "\" is not a message type."); + } + this.outputType = (Descriptor)outputType; + } + } + + // ================================================================= + + private static String computeFullName(FileDescriptor file, + Descriptor parent, + String name) { + if (parent != null) { + return parent.getFullName() + "." + name; + } else if (file.getPackage().length() > 0) { + return file.getPackage() + "." + name; + } else { + return name; + } + } + + // ================================================================= + + /** + * All descriptors except {@code FileDescriptor} implement this to make + * {@code DescriptorPool}'s life easier. + */ + private static interface GenericDescriptor { + Message toProto(); + String getName(); + String getFullName(); + FileDescriptor getFile(); + } + + /** + * Thrown when building descriptors fails because the source DescriptorProtos + * are not valid. + */ + public static class DescriptorValidationException extends Exception { + /** Gets the full name of the descriptor where the error occurred. */ + public String getProblemSymbolName() { return name; } + + /** + * Gets the the protocol message representation of the invalid descriptor. + */ + public Message getProblemProto() { return proto; } + + /** + * Gets a human-readable description of the error. + */ + public String getDescription() { return description; } + + private final String name; + private final Message proto; + private final String description; + + private DescriptorValidationException(GenericDescriptor problemDescriptor, + String description) { + super(problemDescriptor.getFullName() + ": " + description); + + // Note that problemDescriptor may be partially uninitialized, so we + // don't want to expose it directly to the user. So, we only provide + // the name and the original proto. + this.name = problemDescriptor.getFullName(); + this.proto = problemDescriptor.toProto(); + this.description = description; + } + + private DescriptorValidationException(FileDescriptor problemDescriptor, + String description) { + super(problemDescriptor.getName() + ": " + description); + + // Note that problemDescriptor may be partially uninitialized, so we + // don't want to expose it directly to the user. So, we only provide + // the name and the original proto. + this.name = problemDescriptor.getName(); + this.proto = problemDescriptor.toProto(); + this.description = description; + } + } + + // ================================================================= + + /** + * A private helper class which contains lookup tables containing all the + * descriptors defined in a particular file. + */ + private static final class DescriptorPool { + DescriptorPool(FileDescriptor[] dependencies) { + this.dependencies = new DescriptorPool[dependencies.length]; + + for (int i = 0; i < dependencies.length; i++) { + this.dependencies[i] = dependencies[i].pool; + } + + for (int i = 0; i < dependencies.length; i++) { + try { + addPackage(dependencies[i].getPackage(), dependencies[i]); + } catch (DescriptorValidationException e) { + // Can't happen, because addPackage() only fails when the name + // conflicts with a non-package, but we have not yet added any + // non-packages at this point. + assert false; + } + } + } + + final DescriptorPool[] dependencies; + + final Map<String, GenericDescriptor> descriptorsByName = + new HashMap<String, GenericDescriptor>(); + final Map<DescriptorIntPair, FieldDescriptor> fieldsByNumber = + new HashMap<DescriptorIntPair, FieldDescriptor>(); + final Map<DescriptorIntPair, EnumValueDescriptor> enumValuesByNumber = + new HashMap<DescriptorIntPair, EnumValueDescriptor>(); + + /** Find a generic descriptor by fully-qualified name. */ + GenericDescriptor findSymbol(String fullName) { + GenericDescriptor result = descriptorsByName.get(fullName); + if (result != null) return result; + + for (int i = 0; i < dependencies.length; i++) { + result = dependencies[i].descriptorsByName.get(fullName); + if (result != null) return result; + } + + return null; + } + + /** + * Look up a descriptor by name, relative to some other descriptor. + * The name may be fully-qualified (with a leading '.'), + * partially-qualified, or unqualified. C++-like name lookup semantics + * are used to search for the matching descriptor. + */ + GenericDescriptor lookupSymbol(String name, + GenericDescriptor relativeTo) + throws DescriptorValidationException { + // TODO(kenton): This could be optimized in a number of ways. + + GenericDescriptor result; + if (name.startsWith(".")) { + // Fully-qualified name. + result = findSymbol(name.substring(1)); + } else { + // If "name" is a compound identifier, we want to search for the + // first component of it, then search within it for the rest. + int firstPartLength = name.indexOf('.'); + String firstPart; + if (firstPartLength == -1) { + firstPart = name; + } else { + firstPart = name.substring(0, firstPartLength); + } + + // We will search each parent scope of "relativeTo" looking for the + // symbol. + StringBuilder scopeToTry = new StringBuilder(relativeTo.getFullName()); + + while (true) { + // Chop off the last component of the scope. + int dotpos = scopeToTry.lastIndexOf("."); + if (dotpos == -1) { + result = findSymbol(name); + break; + } else { + scopeToTry.setLength(dotpos + 1); + + // Append firstPart and try to find. + scopeToTry.append(firstPart); + result = findSymbol(scopeToTry.toString()); + + if (result != null) { + if (firstPartLength != -1) { + // We only found the first part of the symbol. Now look for + // the whole thing. If this fails, we *don't* want to keep + // searching parent scopes. + scopeToTry.setLength(dotpos + 1); + scopeToTry.append(name); + result = findSymbol(scopeToTry.toString()); + } + break; + } + + // Not found. Remove the name so we can try again. + scopeToTry.setLength(dotpos); + } + } + } + + if (result == null) { + throw new DescriptorValidationException(relativeTo, + "\"" + name + "\" is not defined."); + } else { + return result; + } + } + + /** + * Adds a symbol to the symbol table. If a symbol with the same name + * already exists, throws an error. + */ + void addSymbol(GenericDescriptor descriptor) + throws DescriptorValidationException { + validateSymbolName(descriptor); + + String fullName = descriptor.getFullName(); + int dotpos = fullName.lastIndexOf('.'); + + GenericDescriptor old = descriptorsByName.put(fullName, descriptor); + if (old != null) { + descriptorsByName.put(fullName, old); + + if (descriptor.getFile() == old.getFile()) { + if (dotpos == -1) { + throw new DescriptorValidationException(descriptor, + "\"" + fullName + "\" is already defined."); + } else { + throw new DescriptorValidationException(descriptor, + "\"" + fullName.substring(dotpos + 1) + + "\" is already defined in \"" + + fullName.substring(0, dotpos) + "\"."); + } + } else { + throw new DescriptorValidationException(descriptor, + "\"" + fullName + "\" is already defined in file \"" + + old.getFile().getName() + "\"."); + } + } + } + + /** + * Represents a package in the symbol table. We use PackageDescriptors + * just as placeholders so that someone cannot define, say, a message type + * that has the same name as an existing package. + */ + static final class PackageDescriptor implements GenericDescriptor { + public Message toProto() { return file.toProto(); } + public String getName() { return name; } + public String getFullName() { return fullName; } + public FileDescriptor getFile() { return file; } + + PackageDescriptor(String name, String fullName, FileDescriptor file) { + this.file = file; + this.fullName = fullName; + this.name = name; + } + + String name; + String fullName; + FileDescriptor file; + } + + /** + * Adds a package to the symbol tables. If a package by the same name + * already exists, that is fine, but if some other kind of symbol exists + * under the same name, an exception is thrown. If the package has + * multiple components, this also adds the parent package(s). + */ + void addPackage(String fullName, FileDescriptor file) + throws DescriptorValidationException { + int dotpos = fullName.lastIndexOf('.'); + String name; + if (dotpos != -1) { + addPackage(fullName.substring(0, dotpos), file); + name = fullName.substring(dotpos + 1); + } else { + name = fullName; + } + + GenericDescriptor old = + descriptorsByName.put(fullName, + new PackageDescriptor(fullName, name, file)); + if (old != null) { + descriptorsByName.put(fullName, old); + if (!(old instanceof PackageDescriptor)) { + throw new DescriptorValidationException(file, + "\"" + name + "\" is already defined (as something other than a " + + "package) in file \"" + old.getFile().getName() + "\"."); + } + } + } + + /** A (GenericDescriptor, int) pair, used as a map key. */ + static final class DescriptorIntPair { + final GenericDescriptor descriptor; + final int number; + + DescriptorIntPair(GenericDescriptor descriptor, int number) { + this.descriptor = descriptor; + this.number = number; + } + + public int hashCode() { + return descriptor.hashCode() * ((1 << 16) - 1) + number; + } + public boolean equals(Object obj) { + if (!(obj instanceof DescriptorIntPair)) return false; + DescriptorIntPair other = (DescriptorIntPair)obj; + return descriptor == other.descriptor && number == other.number; + } + } + + /** + * Adds a field to the fieldsByNumber table. Throws an exception if a + * field with hte same containing type and number already exists. + */ + void addFieldByNumber(FieldDescriptor field) + throws DescriptorValidationException { + DescriptorIntPair key = + new DescriptorIntPair(field.getContainingType(), field.getNumber()); + FieldDescriptor old = fieldsByNumber.put(key, field); + if (old != null) { + fieldsByNumber.put(key, old); + throw new DescriptorValidationException(field, + "Field number " + field.getNumber() + + "has already been used in \"" + + field.getContainingType().getFullName() + + "\" by field \"" + old.getName() + "\"."); + } + } + + /** + * Adds an enum value to the enumValuesByNumber table. If an enum value + * with the same type and number already exists, does nothing. (This is + * allowed; the first value define with the number takes precedence.) + */ + void addEnumValueByNumber(EnumValueDescriptor value) { + DescriptorIntPair key = + new DescriptorIntPair(value.getType(), value.getNumber()); + EnumValueDescriptor old = enumValuesByNumber.put(key, value); + if (old != null) { + enumValuesByNumber.put(key, old); + // Not an error: Multiple enum values may have the same number, but + // we only want the first one in the map. + } + } + + /** + * Verifies that the descriptor's name is valid (i.e. it contains only + * letters, digits, and underscores, and does not start with a digit). + */ + void validateSymbolName(GenericDescriptor descriptor) + throws DescriptorValidationException { + String name = descriptor.getName(); + if (name.length() == 0) { + throw new DescriptorValidationException(descriptor, "Missing name."); + } else { + boolean valid = true; + for (int i = 0; i < name.length(); i++) { + char c = name.charAt(i); + // Non-ASCII characters are not valid in protobuf identifiers, even + // if they are letters or digits. + if (c >= 128) { + valid = false; + } + // First character must be letter or _. Subsequent characters may + // be letters, numbers, or digits. + if (Character.isLetter(c) || c == '_' || + (Character.isDigit(c) && i > 0)) { + // Valid + } else { + valid = false; + } + } + if (!valid) { + throw new DescriptorValidationException(descriptor, + "\"" + name + "\" is not a valid identifier."); + } + } + } + } +} diff --git a/java/src/main/java/com/google/protobuf/DynamicMessage.java b/java/src/main/java/com/google/protobuf/DynamicMessage.java new file mode 100644 index 00000000..d9a39f0e --- /dev/null +++ b/java/src/main/java/com/google/protobuf/DynamicMessage.java @@ -0,0 +1,391 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.protobuf; + +import com.google.protobuf.Descriptors.Descriptor; +import com.google.protobuf.Descriptors.FieldDescriptor; + +import java.io.InputStream; +import java.io.IOException; +import java.util.Map; + +/** + * An implementation of {@link Message} that can represent arbitrary types, + * given a {@link Descriptors.Descriptor}. + * + * @author kenton@google.com Kenton Varda + */ +public final class DynamicMessage extends AbstractMessage { + private final Descriptor type; + private final FieldSet fields; + private final UnknownFieldSet unknownFields; + private int memoizedSize = -1; + + /** + * Construct a {@code DynamicMessage} using the given {@code FieldSet}. + */ + private DynamicMessage(Descriptor type, FieldSet fields, + UnknownFieldSet unknownFields) { + this.type = type; + this.fields = fields; + this.unknownFields = unknownFields; + } + + /** + * Get a {@code DynamicMessage} representing the default instance of the + * given type. + */ + public static DynamicMessage getDefaultInstance(Descriptor type) { + return new DynamicMessage(type, FieldSet.emptySet(), + UnknownFieldSet.getDefaultInstance()); + } + + /** Parse a message of the given type from the given input stream. */ + public static DynamicMessage parseFrom(Descriptor type, + CodedInputStream input) + throws IOException { + return newBuilder(type).mergeFrom(input).buildParsed(); + } + + /** Parse a message of the given type from the given input stream. */ + public static DynamicMessage parseFrom( + Descriptor type, + CodedInputStream input, + ExtensionRegistry extensionRegistry) + throws IOException { + return newBuilder(type).mergeFrom(input, extensionRegistry).buildParsed(); + } + + /** Parse {@code data} as a message of the given type and return it. */ + public static DynamicMessage parseFrom(Descriptor type, ByteString data) + throws InvalidProtocolBufferException { + return newBuilder(type).mergeFrom(data).buildParsed(); + } + + /** Parse {@code data} as a message of the given type and return it. */ + public static DynamicMessage parseFrom(Descriptor type, ByteString data, + ExtensionRegistry extensionRegistry) + throws InvalidProtocolBufferException { + return newBuilder(type).mergeFrom(data, extensionRegistry).buildParsed(); + } + + /** Parse {@code data} as a message of the given type and return it. */ + public static DynamicMessage parseFrom(Descriptor type, byte[] data) + throws InvalidProtocolBufferException { + return newBuilder(type).mergeFrom(data).buildParsed(); + } + + /** Parse {@code data} as a message of the given type and return it. */ + public static DynamicMessage parseFrom(Descriptor type, byte[] data, + ExtensionRegistry extensionRegistry) + throws InvalidProtocolBufferException { + return newBuilder(type).mergeFrom(data, extensionRegistry).buildParsed(); + } + + /** Parse a message of the given type from {@code input} and return it. */ + public static DynamicMessage parseFrom(Descriptor type, InputStream input) + throws IOException { + return newBuilder(type).mergeFrom(input).buildParsed(); + } + + /** Parse a message of the given type from {@code input} and return it. */ + public static DynamicMessage parseFrom(Descriptor type, InputStream input, + ExtensionRegistry extensionRegistry) + throws IOException { + return newBuilder(type).mergeFrom(input, extensionRegistry).buildParsed(); + } + + /** Construct a {@link Message.Builder} for the given type. */ + public static Builder newBuilder(Descriptor type) { + return new Builder(type); + } + + /** + * Construct a {@link Message.Builder} for a message of the same type as + * {@code prototype}, and initialize it with {@code prototype}'s contents. + */ + public static Builder newBuilder(Message prototype) { + return new Builder(prototype.getDescriptorForType()).mergeFrom(prototype); + } + + // ----------------------------------------------------------------- + // Implementation of Message interface. + + public Descriptor getDescriptorForType() { + return type; + } + + public DynamicMessage getDefaultInstanceForType() { + return getDefaultInstance(type); + } + + public Map<FieldDescriptor, Object> getAllFields() { + return fields.getAllFields(); + } + + public boolean hasField(FieldDescriptor field) { + verifyContainingType(field); + return fields.hasField(field); + } + + public Object getField(FieldDescriptor field) { + verifyContainingType(field); + Object result = fields.getField(field); + if (result == null) { + result = getDefaultInstance(field.getMessageType()); + } + return result; + } + + public int getRepeatedFieldCount(FieldDescriptor field) { + verifyContainingType(field); + return fields.getRepeatedFieldCount(field); + } + + public Object getRepeatedField(FieldDescriptor field, int index) { + verifyContainingType(field); + return fields.getRepeatedField(field, index); + } + + public UnknownFieldSet getUnknownFields() { + return unknownFields; + } + + public boolean isInitialized() { + return fields.isInitialized(type); + } + + public void writeTo(CodedOutputStream output) throws IOException { + fields.writeTo(output); + if (type.getOptions().getMessageSetWireFormat()) { + unknownFields.writeAsMessageSetTo(output); + } else { + unknownFields.writeTo(output); + } + } + + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = fields.getSerializedSize(); + if (type.getOptions().getMessageSetWireFormat()) { + size += unknownFields.getSerializedSizeAsMessageSet(); + } else { + size += unknownFields.getSerializedSize(); + } + + memoizedSize = size; + return size; + } + + public Builder newBuilderForType() { + return new Builder(type); + } + + /** Verifies that the field is a field of this message. */ + private void verifyContainingType(FieldDescriptor field) { + if (field.getContainingType() != type) { + throw new IllegalArgumentException( + "FieldDescriptor does not match message type."); + } + } + + // ================================================================= + + /** + * Builder for {@link DynamicMessage}s. + */ + public static final class Builder extends AbstractMessage.Builder<Builder> { + private final Descriptor type; + private FieldSet fields; + private UnknownFieldSet unknownFields; + + /** Construct a {@code Builder} for the given type. */ + private Builder(Descriptor type) { + this.type = type; + this.fields = FieldSet.newFieldSet(); + this.unknownFields = UnknownFieldSet.getDefaultInstance(); + } + + // --------------------------------------------------------------- + // Implementation of Message.Builder interface. + + public Builder clear() { + fields.clear(); + return this; + } + + public Builder mergeFrom(Message other) { + if (other.getDescriptorForType() != type) { + throw new IllegalArgumentException( + "mergeFrom(Message) can only merge messages of the same type."); + } + + fields.mergeFrom(other); + return this; + } + + public DynamicMessage build() { + if (!isInitialized()) { + throw new UninitializedMessageException( + new DynamicMessage(type, fields, unknownFields)); + } + return buildPartial(); + } + + /** + * Helper for DynamicMessage.parseFrom() methods to call. Throws + * {@link InvalidProtocolBufferException} instead of + * {@link UninitializedMessageException}. + */ + private DynamicMessage buildParsed() throws InvalidProtocolBufferException { + if (!isInitialized()) { + throw new UninitializedMessageException( + new DynamicMessage(type, fields, unknownFields)) + .asInvalidProtocolBufferException(); + } + return buildPartial(); + } + + public DynamicMessage buildPartial() { + fields.makeImmutable(); + DynamicMessage result = + new DynamicMessage(type, fields, unknownFields); + fields = null; + unknownFields = null; + return result; + } + + public Builder clone() { + Builder result = new Builder(type); + result.fields.mergeFrom(fields); + return result; + } + + public boolean isInitialized() { + return fields.isInitialized(type); + } + + public Builder mergeFrom(CodedInputStream input, + ExtensionRegistry extensionRegistry) + throws IOException { + UnknownFieldSet.Builder unknownFieldsBuilder = + UnknownFieldSet.newBuilder(unknownFields); + fields.mergeFrom(input, unknownFieldsBuilder, extensionRegistry, this); + unknownFields = unknownFieldsBuilder.build(); + return this; + } + + public Descriptor getDescriptorForType() { + return type; + } + + public DynamicMessage getDefaultInstanceForType() { + return getDefaultInstance(type); + } + + public Map<FieldDescriptor, Object> getAllFields() { + return fields.getAllFields(); + } + + public Builder newBuilderForField(FieldDescriptor field) { + verifyContainingType(field); + + if (field.getJavaType() != FieldDescriptor.JavaType.MESSAGE) { + throw new IllegalArgumentException( + "newBuilderForField is only valid for fields with message type."); + } + + return new Builder(field.getMessageType()); + } + + public boolean hasField(FieldDescriptor field) { + verifyContainingType(field); + return fields.hasField(field); + } + + public Object getField(FieldDescriptor field) { + verifyContainingType(field); + Object result = fields.getField(field); + if (result == null) { + result = getDefaultInstance(field.getMessageType()); + } + return result; + } + + public Builder setField(FieldDescriptor field, Object value) { + verifyContainingType(field); + fields.setField(field, value); + return this; + } + + public Builder clearField(FieldDescriptor field) { + verifyContainingType(field); + fields.clearField(field); + return this; + } + + public int getRepeatedFieldCount(FieldDescriptor field) { + verifyContainingType(field); + return fields.getRepeatedFieldCount(field); + } + + public Object getRepeatedField(FieldDescriptor field, int index) { + verifyContainingType(field); + return fields.getRepeatedField(field, index); + } + + public Builder setRepeatedField(FieldDescriptor field, + int index, Object value) { + verifyContainingType(field); + fields.setRepeatedField(field, index, value); + return this; + } + + public Builder addRepeatedField(FieldDescriptor field, Object value) { + verifyContainingType(field); + fields.addRepeatedField(field, value); + return this; + } + + public UnknownFieldSet getUnknownFields() { + return unknownFields; + } + + public Builder setUnknownFields(UnknownFieldSet unknownFields) { + this.unknownFields = unknownFields; + return this; + } + + public Builder mergeUnknownFields(UnknownFieldSet unknownFields) { + this.unknownFields = + UnknownFieldSet.newBuilder(this.unknownFields) + .mergeFrom(unknownFields) + .build(); + return this; + } + + /** Verifies that the field is a field of this message. */ + private void verifyContainingType(FieldDescriptor field) { + if (field.getContainingType() != type) { + throw new IllegalArgumentException( + "FieldDescriptor does not match message type."); + } + } + } +} diff --git a/java/src/main/java/com/google/protobuf/ExtensionRegistry.java b/java/src/main/java/com/google/protobuf/ExtensionRegistry.java new file mode 100644 index 00000000..6954b3b6 --- /dev/null +++ b/java/src/main/java/com/google/protobuf/ExtensionRegistry.java @@ -0,0 +1,237 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.protobuf; + +import com.google.protobuf.Descriptors.Descriptor; +import com.google.protobuf.Descriptors.FieldDescriptor; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * A table of known extensions, searchable by name or field number. When + * parsing a protocol message that might have extensions, you must provide + * an {@code ExtensionRegistry} in which you have registered any extensions + * that you want to be able to parse. Otherwise, those extensions will just + * be treated like unknown fields. + * + * <p>For example, if you had the {@code .proto} file: + * + * <pre> + * option java_class = "MyProto"; + * + * message Foo { + * extensions 1000 to max; + * } + * + * extend Foo { + * optional int32 bar; + * } + * </pre> + * + * Then you might write code like: + * + * <pre> + * ExtensionRegistry registry = ExtensionRegistry.newInstance(); + * registry.add(MyProto.bar); + * MyProto.Foo message = MyProto.Foo.parseFrom(input, registry); + * </pre> + * + * <p>Background: + * + * <p>You might wonder why this is necessary. Two alternatives might come to + * mind. First, you might imagine a system where generated extensions are + * automatically registered when their containing classes are loaded. This + * is a popular technique, but is bad design; among other things, it creates a + * situation where behavior can change depending on what classes happen to be + * loaded. It also introduces a security vulnerability, because an + * unprivileged class could cause its code to be called unexpectedly from a + * privileged class by registering itself as an extension of the right type. + * + * <p>Another option you might consider is lazy parsing: do not parse an + * extension until it is first requested, at which point the caller must + * provide a type to use. This introduces a different set of problems. First, + * it would require a mutex lock any time an extension was accessed, which + * would be slow. Second, corrupt data would not be detected until first + * access, at which point it would be much harder to deal with it. Third, it + * could violate the expectation that message objects are immutable, since the + * type provided could be any arbitrary message class. An unpriviledged user + * could take advantage of this to inject a mutable object into a message + * belonging to priviledged code and create mischief. + * + * @author kenton@google.com Kenton Varda + */ +public final class ExtensionRegistry { + /** Construct a new, empty instance. */ + public static ExtensionRegistry newInstance() { + return new ExtensionRegistry( + new HashMap<String, ExtensionInfo>(), + new HashMap<DescriptorIntPair, ExtensionInfo>()); + } + + /** Get the unmodifiable singleton empty instance. */ + public static ExtensionRegistry getEmptyRegistry() { + return EMPTY; + } + + /** Returns an unmodifiable view of the registry. */ + public ExtensionRegistry getUnmodifiable() { + return new ExtensionRegistry( + Collections.unmodifiableMap(extensionsByName), + Collections.unmodifiableMap(extensionsByNumber)); + } + + /** A (Descriptor, Message) pair, returned by lookup methods. */ + public static final class ExtensionInfo { + /** The extension's descriptor. */ + public final FieldDescriptor descriptor; + + /** + * A default instance of the extension's type, if it has a message type. + * Otherwise, {@code null}. + */ + public final Message defaultInstance; + + private ExtensionInfo(FieldDescriptor descriptor) { + this.descriptor = descriptor; + this.defaultInstance = null; + } + private ExtensionInfo(FieldDescriptor descriptor, Message defaultInstance) { + this.descriptor = descriptor; + this.defaultInstance = defaultInstance; + } + } + + /** + * Find an extension by fully-qualified field name, in the proto namespace. + * I.e. {@code result.descriptor.fullName()} will match {@code fullName} if + * a match is found. + * + * @return Information about the extension if found, or {@code null} + * otherwise. + */ + public ExtensionInfo findExtensionByName(String fullName) { + return extensionsByName.get(fullName); + } + + /** + * Find an extension by containing type and field number. + * + * @return Information about the extension if found, or {@code null} + * otherwise. + */ + public ExtensionInfo findExtensionByNumber(Descriptor containingType, + int fieldNumber) { + return extensionsByNumber.get( + new DescriptorIntPair(containingType, fieldNumber)); + } + + /** Add an extension from a generated file to the registry. */ + public void add(GeneratedMessage.GeneratedExtension<?, ?> extension) { + if (extension.getDescriptor().getJavaType() == + FieldDescriptor.JavaType.MESSAGE) { + add(new ExtensionInfo(extension.getDescriptor(), + extension.getMessageDefaultInstance())); + } else { + add(new ExtensionInfo(extension.getDescriptor(), null)); + } + } + + /** Add a non-message-type extension to the registry by descriptor. */ + public void add(FieldDescriptor type) { + if (type.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + throw new IllegalArgumentException( + "ExtensionRegistry.add() must be provided a default instance when " + + "adding an embedded message extension."); + } + add(new ExtensionInfo(type, null)); + } + + /** Add a message-type extension to the registry by descriptor. */ + public void add(FieldDescriptor type, Message defaultInstance) { + if (type.getJavaType() != FieldDescriptor.JavaType.MESSAGE) { + throw new IllegalArgumentException( + "ExtensionRegistry.add() provided a default instance for a " + + "non-message extension."); + } + add(new ExtensionInfo(type, defaultInstance)); + } + + // ================================================================= + // Private stuff. + + private ExtensionRegistry( + Map<String, ExtensionInfo> extensionsByName, + Map<DescriptorIntPair, ExtensionInfo> extensionsByNumber) { + this.extensionsByName = extensionsByName; + this.extensionsByNumber = extensionsByNumber; + } + + private final Map<String, ExtensionInfo> extensionsByName; + private final Map<DescriptorIntPair, ExtensionInfo> extensionsByNumber; + + private static final ExtensionRegistry EMPTY = + new ExtensionRegistry( + Collections.<String, ExtensionInfo>emptyMap(), + Collections.<DescriptorIntPair, ExtensionInfo>emptyMap()); + + private void add(ExtensionInfo extension) { + if (!extension.descriptor.isExtension()) { + throw new IllegalArgumentException( + "ExtensionRegistry.add() was given a FieldDescriptor for a regular " + + "(non-extension) field."); + } + + extensionsByName.put(extension.descriptor.getFullName(), extension); + extensionsByNumber.put( + new DescriptorIntPair(extension.descriptor.getContainingType(), + extension.descriptor.getNumber()), + extension); + + FieldDescriptor field = extension.descriptor; + if (field.getContainingType().getOptions().getMessageSetWireFormat() && + field.getType() == FieldDescriptor.Type.MESSAGE && + field.isOptional() && + field.getExtensionScope() == field.getMessageType()) { + // This is an extension of a MessageSet type defined within the extension + // type's own scope. For backwards-compatibility, allow it to be looked + // up by type name. + extensionsByName.put(field.getMessageType().getFullName(), extension); + } + } + + /** A (GenericDescriptor, int) pair, used as a map key. */ + private static final class DescriptorIntPair { + final Descriptor descriptor; + final int number; + + DescriptorIntPair(Descriptor descriptor, int number) { + this.descriptor = descriptor; + this.number = number; + } + + public int hashCode() { + return descriptor.hashCode() * ((1 << 16) - 1) + number; + } + public boolean equals(Object obj) { + if (!(obj instanceof DescriptorIntPair)) return false; + DescriptorIntPair other = (DescriptorIntPair)obj; + return descriptor == other.descriptor && number == other.number; + } + } +} diff --git a/java/src/main/java/com/google/protobuf/FieldSet.java b/java/src/main/java/com/google/protobuf/FieldSet.java new file mode 100644 index 00000000..3a5dc488 --- /dev/null +++ b/java/src/main/java/com/google/protobuf/FieldSet.java @@ -0,0 +1,662 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.protobuf; + +import com.google.protobuf.Descriptors.Descriptor; +import com.google.protobuf.Descriptors.FieldDescriptor; +import com.google.protobuf.Descriptors.EnumValueDescriptor; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.TreeMap; +import java.util.List; +import java.util.Map; + +/** + * A class which represents an arbitrary set of fields of some message type. + * This is used to implement {@link DynamicMessage}, and also to represent + * extensions in {@link GeneratedMessage}. This class is package-private, + * since outside users should probably be using {@link DynamicMessage}. + * + * @author kenton@google.com Kenton Varda + */ +final class FieldSet { + private Map<FieldDescriptor, Object> fields; + + /** Construct a new FieldSet. */ + private FieldSet() { + // Use a TreeMap because fields need to be in canonical order when + // serializing. + this.fields = new TreeMap<FieldDescriptor, Object>(); + } + + /** + * Construct a new FieldSet with the given map. This is only used by + * DEFAULT_INSTANCE, to pass in an immutable empty map. + */ + private FieldSet(Map<FieldDescriptor, Object> fields) { + this.fields = fields; + } + + /** Construct a new FieldSet. */ + public static FieldSet newFieldSet() { + return new FieldSet(); + } + + /** Get an immutable empty FieldSet. */ + public static FieldSet emptySet() { + return DEFAULT_INSTANCE; + } + private static final FieldSet DEFAULT_INSTANCE = + new FieldSet(Collections.<FieldDescriptor, Object>emptyMap()); + + /** Make this FieldSet immutable from this point forward. */ + @SuppressWarnings("unchecked") + public void makeImmutable() { + for (Map.Entry<FieldDescriptor, Object> entry: fields.entrySet()) { + if (entry.getKey().isRepeated()) { + List value = (List)entry.getValue(); + entry.setValue(Collections.unmodifiableList(value)); + } + } + fields = Collections.unmodifiableMap(fields); + } + + // ================================================================= + + /** See {@link Message.Builder#clear()}. */ + public void clear() { + fields.clear(); + } + + /** See {@link Message#getAllFields()}. */ + public Map<Descriptors.FieldDescriptor, Object> getAllFields() { + return Collections.unmodifiableMap(fields); + } + + /** + * Get an interator to the field map. This iterator should not be leaked + * out of the protobuf library as it is not protected from mutation. + */ + public Iterator<Map.Entry<Descriptors.FieldDescriptor, Object>> iterator() { + return fields.entrySet().iterator(); + } + + /** See {@link Message#hasField(Descriptors.FieldDescriptor)}. */ + public boolean hasField(Descriptors.FieldDescriptor field) { + if (field.isRepeated()) { + throw new IllegalArgumentException( + "hasField() can only be called on non-repeated fields."); + } + + return fields.containsKey(field); + } + + /** + * See {@link Message#getField(Descriptors.FieldDescriptor)}. This method + * returns {@code null} if the field is a singular message type and is not + * set; in this case it is up to the caller to fetch the message's default + * instance. + */ + public Object getField(Descriptors.FieldDescriptor field) { + Object result = fields.get(field); + if (result == null) { + if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + if (field.isRepeated()) { + return Collections.emptyList(); + } else { + return null; + } + } else { + return field.getDefaultValue(); + } + } else { + return result; + } + } + + /** See {@link Message.Builder#setField(Descriptors.FieldDescriptor,Object)}. */ + @SuppressWarnings("unchecked") + public void setField(Descriptors.FieldDescriptor field, Object value) { + if (field.isRepeated()) { + if (!(value instanceof List)) { + throw new IllegalArgumentException( + "Wrong object type used with protocol message reflection."); + } + + // Wrap the contents in a new list so that the caller cannot change + // the list's contents after setting it. + List newList = new ArrayList(); + newList.addAll((List)value); + for (Object element : newList) { + verifyType(field, element); + } + value = newList; + } else { + verifyType(field, value); + } + + fields.put(field, value); + } + + /** See {@link Message.Builder#clearField(Descriptors.FieldDescriptor)}. */ + public void clearField(Descriptors.FieldDescriptor field) { + fields.remove(field); + } + + /** See {@link Message#getRepeatedFieldCount(Descriptors.FieldDescriptor)}. */ + public int getRepeatedFieldCount(Descriptors.FieldDescriptor field) { + if (!field.isRepeated()) { + throw new IllegalArgumentException( + "getRepeatedFieldCount() can only be called on repeated fields."); + } + + return ((List)getField(field)).size(); + } + + /** See {@link Message#getRepeatedField(Descriptors.FieldDescriptor,int)}. */ + public Object getRepeatedField(Descriptors.FieldDescriptor field, int index) { + if (!field.isRepeated()) { + throw new IllegalArgumentException( + "getRepeatedField() can only be called on repeated fields."); + } + + return ((List)getField(field)).get(index); + } + + /** See {@link Message.Builder#setRepeatedField(Descriptors.FieldDescriptor,int,Object)}. */ + @SuppressWarnings("unchecked") + public void setRepeatedField(Descriptors.FieldDescriptor field, int index, + Object value) { + if (!field.isRepeated()) { + throw new IllegalArgumentException( + "setRepeatedField() can only be called on repeated fields."); + } + + verifyType(field, value); + + List list = (List)fields.get(field); + if (list == null) { + throw new IndexOutOfBoundsException(); + } + + list.set(index, value); + } + + /** See {@link Message.Builder#addRepeatedField(Descriptors.FieldDescriptor,Object)}. */ + @SuppressWarnings("unchecked") + public void addRepeatedField(Descriptors.FieldDescriptor field, + Object value) { + if (!field.isRepeated()) { + throw new IllegalArgumentException( + "setRepeatedField() can only be called on repeated fields."); + } + + verifyType(field, value); + + List list = (List)fields.get(field); + if (list == null) { + list = new ArrayList(); + fields.put(field, list); + } + + list.add(value); + } + + /** + * Verifies that the given object is of the correct type to be a valid + * value for the given field. (For repeated fields, this checks if the + * object is the right type to be one element of the field.) + * + * @throws IllegalArgumentException The value is not of the right type. + */ + private void verifyType(FieldDescriptor field, Object value) { + boolean isValid = false; + switch (field.getJavaType()) { + case INT: isValid = value instanceof Integer ; break; + case LONG: isValid = value instanceof Long ; break; + case FLOAT: isValid = value instanceof Float ; break; + case DOUBLE: isValid = value instanceof Double ; break; + case BOOLEAN: isValid = value instanceof Boolean ; break; + case STRING: isValid = value instanceof String ; break; + case BYTE_STRING: isValid = value instanceof ByteString; break; + case ENUM: + isValid = value instanceof EnumValueDescriptor && + ((EnumValueDescriptor)value).getType() == field.getEnumType(); + break; + case MESSAGE: + isValid = value instanceof Message && + ((Message)value).getDescriptorForType() == field.getMessageType(); + break; + } + + if (!isValid) { + // When chaining calls to setField(), it can be hard to tell from + // the stack trace which exact call failed, since the whole chain is + // considered one line of code. So, let's make sure to include the + // field name and other useful info in the exception. + throw new IllegalArgumentException( + "Wrong object type used with protocol message reflection. " + + "Message type \"" + field.getContainingType().getFullName() + + "\", field \"" + + (field.isExtension() ? field.getFullName() : field.getName()) + + "\", value was type \"" + value.getClass().getName() + "\"."); + } + } + + // ================================================================= + // Parsing and serialization + + /** + * See {@link Message#isInitialized()}. Note: Since {@code FieldSet} + * itself does not have any way of knowing about required fields that + * aren't actually present in the set, it is up to the caller to check + * that all required fields are present. + */ + @SuppressWarnings("unchecked") + public boolean isInitialized() { + for (Map.Entry<FieldDescriptor, Object> entry : fields.entrySet()) { + FieldDescriptor field = entry.getKey(); + if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + if (field.isRepeated()) { + for (Message element : (List<Message>) entry.getValue()) { + if (!element.isInitialized()) { + return false; + } + } + } else { + if (!((Message) entry.getValue()).isInitialized()) { + return false; + } + } + } + } + + return true; + } + + /** + * Like {@link #isInitialized()}, but also checks for the presence of + * all required fields in the given type. + */ + @SuppressWarnings("unchecked") + public boolean isInitialized(Descriptor type) { + // Check that all required fields are present. + for (FieldDescriptor field : type.getFields()) { + if (field.isRequired()) { + if (!hasField(field)) { + return false; + } + } + } + + // Check that embedded messages are initialized. + return isInitialized(); + } + + /** See {@link Message.Builder#mergeFrom(Message)}. */ + @SuppressWarnings("unchecked") + public void mergeFrom(Message other) { + // Note: We don't attempt to verify that other's fields have valid + // types. Doing so would be a losing battle. We'd have to verify + // all sub-messages as well, and we'd have to make copies of all of + // them to insure that they don't change after verification (since + // the Message interface itself cannot enforce immutability of + // implementations). + // TODO(kenton): Provide a function somewhere called makeDeepCopy() + // which allows people to make secure deep copies of messages. + + for (Map.Entry<FieldDescriptor, Object> entry : + other.getAllFields().entrySet()) { + FieldDescriptor field = entry.getKey(); + if (field.isRepeated()) { + List existingValue = (List)fields.get(field); + if (existingValue == null) { + existingValue = new ArrayList(); + fields.put(field, existingValue); + } + existingValue.addAll((List)entry.getValue()); + } else if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + Message existingValue = (Message)fields.get(field); + if (existingValue == null) { + setField(field, entry.getValue()); + } else { + setField(field, + existingValue.newBuilderForType() + .mergeFrom(existingValue) + .mergeFrom((Message)entry.getValue()) + .build()); + } + } else { + setField(field, entry.getValue()); + } + } + } + + /** + * Like {@link #mergeFrom(Message)}, but merges from another {@link FieldSet}. + */ + @SuppressWarnings("unchecked") + public void mergeFrom(FieldSet other) { + for (Map.Entry<FieldDescriptor, Object> entry : other.fields.entrySet()) { + FieldDescriptor field = entry.getKey(); + Object value = entry.getValue(); + + if (field.isRepeated()) { + List existingValue = (List)fields.get(field); + if (existingValue == null) { + existingValue = new ArrayList(); + fields.put(field, existingValue); + } + existingValue.addAll((List)value); + } else if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + Message existingValue = (Message)fields.get(field); + if (existingValue == null) { + setField(field, value); + } else { + setField(field, + existingValue.newBuilderForType() + .mergeFrom(existingValue) + .mergeFrom((Message)value) + .build()); + } + } else { + setField(field, value); + } + } + } + + // TODO(kenton): Move parsing code into AbstractMessage, since it no longer + // uses any special knowledge from FieldSet. + + /** + * See {@link Message.Builder#mergeFrom(CodedInputStream)}. + * @param builder The {@code Builder} for the target message. + */ + public static void mergeFrom(CodedInputStream input, + UnknownFieldSet.Builder unknownFields, + ExtensionRegistry extensionRegistry, + Message.Builder builder) + throws java.io.IOException { + while (true) { + int tag = input.readTag(); + if (tag == 0) { + break; + } + + if (!mergeFieldFrom(input, unknownFields, extensionRegistry, + builder, tag)) { + // end group tag + break; + } + } + } + + /** + * Like {@link #mergeFrom(CodedInputStream, UnknownFieldSet.Builder, + * ExtensionRegistry, Message.Builder)}, but parses a single field. + * @param tag The tag, which should have already been read. + * @return {@code true} unless the tag is an end-group tag. + */ + @SuppressWarnings("unchecked") + public static boolean mergeFieldFrom( + CodedInputStream input, + UnknownFieldSet.Builder unknownFields, + ExtensionRegistry extensionRegistry, + Message.Builder builder, + int tag) throws java.io.IOException { + Descriptor type = builder.getDescriptorForType(); + + if (type.getOptions().getMessageSetWireFormat() && + tag == WireFormat.MESSAGE_SET_ITEM_TAG) { + mergeMessageSetExtensionFromCodedStream( + input, unknownFields, extensionRegistry, builder); + return true; + } + + int wireType = WireFormat.getTagWireType(tag); + int fieldNumber = WireFormat.getTagFieldNumber(tag); + + FieldDescriptor field; + Message defaultInstance = null; + + if (type.isExtensionNumber(fieldNumber)) { + ExtensionRegistry.ExtensionInfo extension = + extensionRegistry.findExtensionByNumber(type, fieldNumber); + if (extension == null) { + field = null; + } else { + field = extension.descriptor; + defaultInstance = extension.defaultInstance; + } + } else { + field = type.findFieldByNumber(fieldNumber); + } + + if (field == null || + wireType != WireFormat.getWireFormatForFieldType(field.getType())) { + // Unknown field or wrong wire type. Skip. + return unknownFields.mergeFieldFrom(tag, input); + } else { + Object value; + switch (field.getType()) { + case GROUP: { + Message.Builder subBuilder; + if (defaultInstance != null) { + subBuilder = defaultInstance.newBuilderForType(); + } else { + subBuilder = builder.newBuilderForField(field); + } + if (!field.isRepeated()) { + subBuilder.mergeFrom((Message) builder.getField(field)); + } + input.readGroup(field.getNumber(), subBuilder, extensionRegistry); + value = subBuilder.build(); + break; + } + case MESSAGE: { + Message.Builder subBuilder; + if (defaultInstance != null) { + subBuilder = defaultInstance.newBuilderForType(); + } else { + subBuilder = builder.newBuilderForField(field); + } + if (!field.isRepeated()) { + subBuilder.mergeFrom((Message) builder.getField(field)); + } + input.readMessage(subBuilder, extensionRegistry); + value = subBuilder.build(); + break; + } + case ENUM: { + int rawValue = input.readEnum(); + value = field.getEnumType().findValueByNumber(rawValue); + // If the number isn't recognized as a valid value for this enum, + // drop it. + if (value == null) { + unknownFields.mergeVarintField(fieldNumber, rawValue); + return true; + } + break; + } + default: + value = input.readPrimitiveField(field.getType()); + break; + } + + if (field.isRepeated()) { + builder.addRepeatedField(field, value); + } else { + builder.setField(field, value); + } + } + + return true; + } + + /** Called by {@code #mergeFieldFrom()} to parse a MessageSet extension. */ + private static void mergeMessageSetExtensionFromCodedStream( + CodedInputStream input, + UnknownFieldSet.Builder unknownFields, + ExtensionRegistry extensionRegistry, + Message.Builder builder) throws java.io.IOException { + Descriptor type = builder.getDescriptorForType(); + + // The wire format for MessageSet is: + // message MessageSet { + // repeated group Item = 1 { + // required int32 typeId = 2; + // required bytes message = 3; + // } + // } + // "typeId" is the extension's field number. The extension can only be + // a message type, where "message" contains the encoded bytes of that + // message. + // + // In practice, we will probably never see a MessageSet item in which + // the message appears before the type ID, or where either field does not + // appear exactly once. However, in theory such cases are valid, so we + // should be prepared to accept them. + + int typeId = 0; + ByteString rawBytes = null; // If we encounter "message" before "typeId" + Message.Builder subBuilder = null; + FieldDescriptor field = null; + + while (true) { + int tag = input.readTag(); + if (tag == 0) { + break; + } + + if (tag == WireFormat.MESSAGE_SET_TYPE_ID_TAG) { + typeId = input.readUInt32(); + // Zero is not a valid type ID. + if (typeId != 0) { + ExtensionRegistry.ExtensionInfo extension = + extensionRegistry.findExtensionByNumber(type, typeId); + if (extension != null) { + field = extension.descriptor; + subBuilder = extension.defaultInstance.newBuilderForType(); + Message originalMessage = (Message)builder.getField(field); + if (originalMessage != null) { + subBuilder.mergeFrom(originalMessage); + } + if (rawBytes != null) { + // We already encountered the message. Parse it now. + subBuilder.mergeFrom( + CodedInputStream.newInstance(rawBytes.newInput())); + rawBytes = null; + } + } else { + // Unknown extension number. If we already saw data, put it + // in rawBytes. + if (rawBytes != null) { + unknownFields.mergeField(typeId, + UnknownFieldSet.Field.newBuilder() + .addLengthDelimited(rawBytes) + .build()); + rawBytes = null; + } + } + } + } else if (tag == WireFormat.MESSAGE_SET_MESSAGE_TAG) { + if (typeId == 0) { + // We haven't seen a type ID yet, so we have to store the raw bytes + // for now. + rawBytes = input.readBytes(); + } else if (subBuilder == null) { + // We don't know how to parse this. Ignore it. + unknownFields.mergeField(typeId, + UnknownFieldSet.Field.newBuilder() + .addLengthDelimited(input.readBytes()) + .build()); + } else { + // We already know the type, so we can parse directly from the input + // with no copying. Hooray! + input.readMessage(subBuilder, extensionRegistry); + } + } else { + // Unknown tag. Skip it. + if (!input.skipField(tag)) { + break; // end of group + } + } + } + + input.checkLastTagWas(WireFormat.MESSAGE_SET_ITEM_END_TAG); + + if (subBuilder != null) { + builder.setField(field, subBuilder.build()); + } + } + + /** See {@link Message#writeTo(CodedOutputStream)}. */ + public void writeTo(CodedOutputStream output) + throws java.io.IOException { + for (Map.Entry<FieldDescriptor, Object> entry : fields.entrySet()) { + writeField(entry.getKey(), entry.getValue(), output); + } + } + + /** Write a single field. */ + public void writeField(FieldDescriptor field, Object value, + CodedOutputStream output) throws java.io.IOException { + if (field.isExtension() && + field.getContainingType().getOptions().getMessageSetWireFormat()) { + output.writeMessageSetExtension(field.getNumber(), (Message)value); + } else { + if (field.isRepeated()) { + for (Object element : (List)value) { + output.writeField(field.getType(), field.getNumber(), element); + } + } else { + output.writeField(field.getType(), field.getNumber(), value); + } + } + } + + /** + * See {@link Message#getSerializedSize()}. It's up to the caller to cache + * the resulting size if desired. + */ + public int getSerializedSize() { + int size = 0; + for (Map.Entry<FieldDescriptor, Object> entry : fields.entrySet()) { + FieldDescriptor field = entry.getKey(); + Object value = entry.getValue(); + + if (field.isExtension() && + field.getContainingType().getOptions().getMessageSetWireFormat()) { + size += CodedOutputStream.computeMessageSetExtensionSize( + field.getNumber(), (Message)value); + } else { + if (field.isRepeated()) { + for (Object element : (List)value) { + size += CodedOutputStream.computeFieldSize( + field.getType(), field.getNumber(), element); + } + } else { + size += CodedOutputStream.computeFieldSize( + field.getType(), field.getNumber(), value); + } + } + } + return size; + } +} diff --git a/java/src/main/java/com/google/protobuf/GeneratedMessage.java b/java/src/main/java/com/google/protobuf/GeneratedMessage.java new file mode 100644 index 00000000..957965b7 --- /dev/null +++ b/java/src/main/java/com/google/protobuf/GeneratedMessage.java @@ -0,0 +1,1219 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.protobuf; + +import com.google.protobuf.Descriptors.Descriptor; +import com.google.protobuf.Descriptors.FieldDescriptor; +import com.google.protobuf.Descriptors.EnumValueDescriptor; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +/** + * All generated protocol message classes extend this class. This class + * implements most of the Message and Builder interfaces using Java reflection. + * Users can ignore this class and pretend that generated messages implement + * the Message interface directly. + * + * @author kenton@google.com Kenton Varda + */ +public abstract class GeneratedMessage extends AbstractMessage { + protected GeneratedMessage() {} + + private UnknownFieldSet unknownFields = UnknownFieldSet.getDefaultInstance(); + + /** + * Get the FieldAccessorTable for this type. We can't have the message + * class pass this in to the constructor because of bootstrapping trouble + * with DescriptorProtos. + */ + protected abstract FieldAccessorTable internalGetFieldAccessorTable(); + + public Descriptor getDescriptorForType() { + return internalGetFieldAccessorTable().descriptor; + } + + /** Internal helper which returns a mutable map. */ + private final Map<FieldDescriptor, Object> getAllFieldsMutable() { + TreeMap<FieldDescriptor, Object> result = + new TreeMap<FieldDescriptor, Object>(); + Descriptor descriptor = internalGetFieldAccessorTable().descriptor; + for (FieldDescriptor field : descriptor.getFields()) { + if (field.isRepeated()) { + List value = (List)getField(field); + if (!value.isEmpty()) { + result.put(field, value); + } + } else { + if (hasField(field)) { + result.put(field, getField(field)); + } + } + } + return result; + } + + public Map<FieldDescriptor, Object> getAllFields() { + return Collections.unmodifiableMap(getAllFieldsMutable()); + } + + public boolean hasField(Descriptors.FieldDescriptor field) { + return internalGetFieldAccessorTable().getField(field).has(this); + } + + public Object getField(FieldDescriptor field) { + return internalGetFieldAccessorTable().getField(field).get(this); + } + + public int getRepeatedFieldCount(FieldDescriptor field) { + return internalGetFieldAccessorTable().getField(field) + .getRepeatedCount(this); + } + + public Object getRepeatedField(FieldDescriptor field, int index) { + return internalGetFieldAccessorTable().getField(field) + .getRepeated(this, index); + } + + public final UnknownFieldSet getUnknownFields() { + return unknownFields; + } + + @SuppressWarnings("unchecked") + public abstract static class Builder <BuilderType extends Builder> + extends AbstractMessage.Builder<BuilderType> { + protected Builder() {} + + /** + * Get the message being built. We don't just pass this to the + * constructor because it becomes null when build() is called. + */ + protected abstract GeneratedMessage internalGetResult(); + + /** + * Get the FieldAccessorTable for this type. We can't have the message + * class pass this in to the constructor because of bootstrapping trouble + * with DescriptorProtos. + */ + private FieldAccessorTable internalGetFieldAccessorTable() { + return internalGetResult().internalGetFieldAccessorTable(); + } + + public BuilderType mergeFrom(Message other) { + if (other.getDescriptorForType() != + internalGetFieldAccessorTable().descriptor) { + throw new IllegalArgumentException("Message type mismatch."); + } + + for (Map.Entry<FieldDescriptor, Object> entry : + other.getAllFields().entrySet()) { + FieldDescriptor field = entry.getKey(); + if (field.isRepeated()) { + // Concatenate repeated fields. + for (Object element : (List) entry.getValue()) { + addRepeatedField(field, element); + } + } else if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE && + hasField(field)) { + // Merge singular embedded messages. + Message oldValue = (Message) getField(field); + setField(field, + oldValue.newBuilderForType() + .mergeFrom(oldValue) + .mergeFrom((Message) entry.getValue()) + .buildPartial()); + } else { + // Just overwrite. + setField(field, entry.getValue()); + } + } + return (BuilderType)this; + } + + public Descriptor getDescriptorForType() { + return internalGetFieldAccessorTable().descriptor; + } + + public Map<Descriptors.FieldDescriptor, Object> getAllFields() { + return internalGetResult().getAllFields(); + } + + public Message.Builder newBuilderForField( + Descriptors.FieldDescriptor field) { + return internalGetFieldAccessorTable().getField(field).newBuilder(); + } + + public boolean hasField(Descriptors.FieldDescriptor field) { + return internalGetResult().hasField(field); + } + + public Object getField(Descriptors.FieldDescriptor field) { + if (field.isRepeated()) { + // The underlying list object is still modifiable at this point. + // Make sure not to expose the modifiable list to the caller. + return Collections.unmodifiableList( + (List)internalGetResult().getField(field)); + } else { + return internalGetResult().getField(field); + } + } + + public BuilderType setField(Descriptors.FieldDescriptor field, + Object value) { + internalGetFieldAccessorTable().getField(field).set(this, value); + return (BuilderType)this; + } + + public BuilderType clearField(Descriptors.FieldDescriptor field) { + internalGetFieldAccessorTable().getField(field).clear(this); + return (BuilderType)this; + } + + public int getRepeatedFieldCount(Descriptors.FieldDescriptor field) { + return internalGetResult().getRepeatedFieldCount(field); + } + + public Object getRepeatedField(Descriptors.FieldDescriptor field, + int index) { + return internalGetResult().getRepeatedField(field, index); + } + + public BuilderType setRepeatedField(Descriptors.FieldDescriptor field, + int index, Object value) { + internalGetFieldAccessorTable().getField(field) + .setRepeated(this, index, value); + return (BuilderType)this; + } + + public BuilderType addRepeatedField(Descriptors.FieldDescriptor field, + Object value) { + internalGetFieldAccessorTable().getField(field).addRepeated(this, value); + return (BuilderType)this; + } + + public final UnknownFieldSet getUnknownFields() { + return internalGetResult().unknownFields; + } + + public final BuilderType setUnknownFields(UnknownFieldSet unknownFields) { + internalGetResult().unknownFields = unknownFields; + return (BuilderType)this; + } + + public final BuilderType mergeUnknownFields(UnknownFieldSet unknownFields) { + GeneratedMessage result = internalGetResult(); + result.unknownFields = + UnknownFieldSet.newBuilder(result.unknownFields) + .mergeFrom(unknownFields) + .build(); + return (BuilderType)this; + } + + public boolean isInitialized() { + return internalGetResult().isInitialized(); + } + + /** + * Called by subclasses to parse an unknown field. + * @return {@code true} unless the tag is an end-group tag. + */ + protected boolean parseUnknownField(CodedInputStream input, + UnknownFieldSet.Builder unknownFields, + ExtensionRegistry extensionRegistry, + int tag) + throws IOException { + return unknownFields.mergeFieldFrom(tag, input); + } + + protected <T> void addAll(Iterable<T> values, Collection<? super T> list) { + if (values instanceof Collection) { + @SuppressWarnings("unsafe") + Collection<T> collection = (Collection<T>) values; + list.addAll(collection); + } else { + for (T value : values) { + list.add(value); + } + } + } + } + + // ================================================================= + // Extensions-related stuff + + /** + * Generated message classes for message types that contain extension ranges + * subclass this. + * + * <p>This class implements type-safe accessors for extensions. They + * implement all the same operations that you can do with normal fields -- + * e.g. "has", "get", and "getCount" -- but for extensions. The extensions + * are identified using instances of the class {@link GeneratedExtension}; + * the protocol compiler generates a static instance of this class for every + * extension in its input. Through the magic of generics, all is made + * type-safe. + * + * <p>For example, imagine you have the {@code .proto} file: + * + * <pre> + * option java_class = "MyProto"; + * + * message Foo { + * extensions 1000 to max; + * } + * + * extend Foo { + * optional int32 bar; + * } + * </pre> + * + * <p>Then you might write code like: + * + * <pre> + * MyProto.Foo foo = getFoo(); + * int i = foo.getExtension(MyProto.bar); + * </pre> + * + * <p>See also {@link ExtendableBuilder}. + */ + public abstract static class ExtendableMessage< + MessageType extends ExtendableMessage> + extends GeneratedMessage { + protected ExtendableMessage() {} + private final FieldSet extensions = FieldSet.newFieldSet(); + + private final void verifyExtensionContainingType( + GeneratedExtension<MessageType, ?> extension) { + if (extension.getDescriptor().getContainingType() != + getDescriptorForType()) { + // This can only happen if someone uses unchecked operations. + throw new IllegalArgumentException( + "Extension is for type \"" + + extension.getDescriptor().getContainingType().getFullName() + + "\" which does not match message type \"" + + getDescriptorForType().getFullName() + "\"."); + } + } + + /** Check if a singular extension is present. */ + public final boolean hasExtension( + GeneratedExtension<MessageType, ?> extension) { + verifyExtensionContainingType(extension); + return extensions.hasField(extension.getDescriptor()); + } + + /** Get the number of elements in a repeated extension. */ + public final <Type> int getExtensionCount( + GeneratedExtension<MessageType, List<Type>> extension) { + verifyExtensionContainingType(extension); + return extensions.getRepeatedFieldCount(extension.getDescriptor()); + } + + /** Get the value of an extension. */ + @SuppressWarnings("unchecked") + public final <Type> Type getExtension( + GeneratedExtension<MessageType, Type> extension) { + verifyExtensionContainingType(extension); + Object value = extensions.getField(extension.getDescriptor()); + if (value == null) { + return (Type)extension.getMessageDefaultInstance(); + } else { + return (Type)extension.fromReflectionType(value); + } + } + + /** Get one element of a repeated extension. */ + @SuppressWarnings("unchecked") + public final <Type> Type getExtension( + GeneratedExtension<MessageType, List<Type>> extension, int index) { + verifyExtensionContainingType(extension); + return (Type)extension.singularFromReflectionType( + extensions.getRepeatedField(extension.getDescriptor(), index)); + } + + /** Called by subclasses to check if all extensions are initialized. */ + protected boolean extensionsAreInitialized() { + return extensions.isInitialized(); + } + + /** + * Used by subclasses to serialize extensions. Extension ranges may be + * interleaved with field numbers, but we must write them in canonical + * (sorted by field number) order. ExtensionWriter helps us write + * individual ranges of extensions at once. + */ + protected class ExtensionWriter { + // Imagine how much simpler this code would be if Java iterators had + // a way to get the next element without advancing the iterator. + + final Iterator<Map.Entry<FieldDescriptor, Object>> iter = + extensions.iterator(); + Map.Entry<FieldDescriptor, Object> next = null; + + private ExtensionWriter() { + if (iter.hasNext()) { + next = iter.next(); + } + } + + public void writeUntil(int end, CodedOutputStream output) + throws IOException { + while (next != null && next.getKey().getNumber() < end) { + extensions.writeField(next.getKey(), next.getValue(), output); + if (iter.hasNext()) { + next = iter.next(); + } else { + next = null; + } + } + } + } + + protected ExtensionWriter newExtensionWriter() { + return new ExtensionWriter(); + } + + /** Called by subclasses to compute the size of extensions. */ + protected int extensionsSerializedSize() { + return extensions.getSerializedSize(); + } + + // --------------------------------------------------------------- + // Reflection + + public Map<Descriptors.FieldDescriptor, Object> getAllFields() { + Map<FieldDescriptor, Object> result = super.getAllFieldsMutable(); + result.putAll(extensions.getAllFields()); + return Collections.unmodifiableMap(result); + } + + public boolean hasField(FieldDescriptor field) { + if (field.isExtension()) { + verifyContainingType(field); + return extensions.hasField(field); + } else { + return super.hasField(field); + } + } + + public Object getField(FieldDescriptor field) { + if (field.isExtension()) { + verifyContainingType(field); + Object value = extensions.getField(field); + if (value == null) { + // Lacking an ExtensionRegistry, we have no way to determine the + // extension's real type, so we return a DynamicMessage. + return DynamicMessage.getDefaultInstance(field.getMessageType()); + } else { + return value; + } + } else { + return super.getField(field); + } + } + + public int getRepeatedFieldCount(FieldDescriptor field) { + if (field.isExtension()) { + verifyContainingType(field); + return extensions.getRepeatedFieldCount(field); + } else { + return super.getRepeatedFieldCount(field); + } + } + + public Object getRepeatedField(FieldDescriptor field, int index) { + if (field.isExtension()) { + verifyContainingType(field); + return extensions.getRepeatedField(field, index); + } else { + return super.getRepeatedField(field, index); + } + } + + private void verifyContainingType(FieldDescriptor field) { + if (field.getContainingType() != getDescriptorForType()) { + throw new IllegalArgumentException( + "FieldDescriptor does not match message type."); + } + } + } + + /** + * Generated message builders for message types that contain extension ranges + * subclass this. + * + * <p>This class implements type-safe accessors for extensions. They + * implement all the same operations that you can do with normal fields -- + * e.g. "get", "set", and "add" -- but for extensions. The extensions are + * identified using instances of the class {@link GeneratedExtension}; the + * protocol compiler generates a static instance of this class for every + * extension in its input. Through the magic of generics, all is made + * type-safe. + * + * <p>For example, imagine you have the {@code .proto} file: + * + * <pre> + * option java_class = "MyProto"; + * + * message Foo { + * extensions 1000 to max; + * } + * + * extend Foo { + * optional int32 bar; + * } + * </pre> + * + * <p>Then you might write code like: + * + * <pre> + * MyProto.Foo foo = + * MyProto.Foo.newBuilder() + * .setExtension(MyProto.bar, 123) + * .build(); + * </pre> + * + * <p>See also {@link ExtendableMessage}. + */ + @SuppressWarnings("unchecked") + public abstract static class ExtendableBuilder< + MessageType extends ExtendableMessage, + BuilderType extends ExtendableBuilder> + extends GeneratedMessage.Builder<BuilderType> { + protected ExtendableBuilder() {} + protected abstract ExtendableMessage<MessageType> internalGetResult(); + + /** Check if a singular extension is present. */ + public final boolean hasExtension( + GeneratedExtension<MessageType, ?> extension) { + return internalGetResult().hasExtension(extension); + } + + /** Get the number of elements in a repeated extension. */ + public final <Type> int getExtensionCount( + GeneratedExtension<MessageType, List<Type>> extension) { + return internalGetResult().getExtensionCount(extension); + } + + /** Get the value of an extension. */ + public final <Type> Type getExtension( + GeneratedExtension<MessageType, Type> extension) { + return internalGetResult().getExtension(extension); + } + + /** Get one element of a repeated extension. */ + public final <Type> Type getExtension( + GeneratedExtension<MessageType, List<Type>> extension, int index) { + return internalGetResult().getExtension(extension, index); + } + + /** Set the value of an extension. */ + public final <Type> BuilderType setExtension( + GeneratedExtension<MessageType, Type> extension, Type value) { + ExtendableMessage<MessageType> message = internalGetResult(); + message.verifyExtensionContainingType(extension); + message.extensions.setField(extension.getDescriptor(), + extension.toReflectionType(value)); + return (BuilderType)this; + } + + /** Set the value of one element of a repeated extension. */ + public final <Type> BuilderType setExtension( + GeneratedExtension<MessageType, List<Type>> extension, + int index, Type value) { + ExtendableMessage<MessageType> message = internalGetResult(); + message.verifyExtensionContainingType(extension); + message.extensions.setRepeatedField( + extension.getDescriptor(), index, + extension.singularToReflectionType(value)); + return (BuilderType)this; + } + + /** Append a value to a repeated extension. */ + public final <Type> BuilderType addExtension( + GeneratedExtension<MessageType, List<Type>> extension, Type value) { + ExtendableMessage<MessageType> message = internalGetResult(); + message.verifyExtensionContainingType(extension); + message.extensions.addRepeatedField( + extension.getDescriptor(), extension.singularToReflectionType(value)); + return (BuilderType)this; + } + + /** Clear an extension. */ + public final <Type> BuilderType clearExtension( + GeneratedExtension<MessageType, ?> extension) { + ExtendableMessage<MessageType> message = internalGetResult(); + message.verifyExtensionContainingType(extension); + message.extensions.clearField(extension.getDescriptor()); + return (BuilderType)this; + } + + /** + * Called by subclasses to parse an unknown field or an extension. + * @return {@code true} unless the tag is an end-group tag. + */ + protected boolean parseUnknownField(CodedInputStream input, + UnknownFieldSet.Builder unknownFields, + ExtensionRegistry extensionRegistry, + int tag) + throws IOException { + ExtendableMessage<MessageType> message = internalGetResult(); + return message.extensions.mergeFieldFrom( + input, unknownFields, extensionRegistry, this, tag); + } + + // --------------------------------------------------------------- + // Reflection + + // We don't have to override the get*() methods here because they already + // just forward to the underlying message. + + public BuilderType setField(FieldDescriptor field, Object value) { + if (field.isExtension()) { + ExtendableMessage<MessageType> message = internalGetResult(); + message.verifyContainingType(field); + message.extensions.setField(field, value); + return (BuilderType)this; + } else { + return super.setField(field, value); + } + } + + public BuilderType clearField(Descriptors.FieldDescriptor field) { + if (field.isExtension()) { + ExtendableMessage<MessageType> message = internalGetResult(); + message.verifyContainingType(field); + message.extensions.clearField(field); + return (BuilderType)this; + } else { + return super.clearField(field); + } + } + + public BuilderType setRepeatedField(Descriptors.FieldDescriptor field, + int index, Object value) { + if (field.isExtension()) { + ExtendableMessage<MessageType> message = internalGetResult(); + message.verifyContainingType(field); + message.extensions.setRepeatedField(field, index, value); + return (BuilderType)this; + } else { + return super.setRepeatedField(field, index, value); + } + } + + public BuilderType addRepeatedField(Descriptors.FieldDescriptor field, + Object value) { + if (field.isExtension()) { + ExtendableMessage<MessageType> message = internalGetResult(); + message.verifyContainingType(field); + message.extensions.addRepeatedField(field, value); + return (BuilderType)this; + } else { + return super.addRepeatedField(field, value); + } + } + } + + // ----------------------------------------------------------------- + + /** For use by generated code only. */ + public static <ContainingType extends Message, Type> + GeneratedExtension<ContainingType, Type> + newGeneratedExtension(FieldDescriptor descriptor, Class<Type> type) { + if (descriptor.isRepeated()) { + throw new IllegalArgumentException( + "Must call newRepeatedGeneratedExtension() for repeated types."); + } + return new GeneratedExtension<ContainingType, Type>(descriptor, type); + } + + /** For use by generated code only. */ + public static <ContainingType extends Message, Type> + GeneratedExtension<ContainingType, List<Type>> + newRepeatedGeneratedExtension( + FieldDescriptor descriptor, Class<Type> type) { + if (!descriptor.isRepeated()) { + throw new IllegalArgumentException( + "Must call newGeneratedExtension() for non-repeated types."); + } + return new GeneratedExtension<ContainingType, List<Type>>(descriptor, type); + } + + /** + * Type used to represent generated extensions. The protocol compiler + * generates a static singleton instance of this class for each extension. + * + * <p>For example, imagine you have the {@code .proto} file: + * + * <pre> + * option java_class = "MyProto"; + * + * message Foo { + * extensions 1000 to max; + * } + * + * extend Foo { + * optional int32 bar; + * } + * </pre> + * + * <p>Then, {@code MyProto.Foo.bar} has type + * {@code GeneratedExtension<MyProto.Foo, Integer>}. + * + * <p>In general, users should ignore the details of this type, and simply use + * these static singletons as parameters to the extension accessors defined + * in {@link ExtendableMessage} and {@link ExtendableBuilder}. + */ + public static final class GeneratedExtension< + ContainingType extends Message, Type> { + // TODO(kenton): Find ways to avoid using Java reflection within this + // class. Also try to avoid suppressing unchecked warnings. + + private GeneratedExtension(FieldDescriptor descriptor, Class type) { + if (!descriptor.isExtension()) { + throw new IllegalArgumentException( + "GeneratedExtension given a regular (non-extension) field."); + } + + this.descriptor = descriptor; + this.type = type; + + switch (descriptor.getJavaType()) { + case MESSAGE: + enumValueOf = null; + enumGetValueDescriptor = null; + messageDefaultInstance = + (Message)invokeOrDie(getMethodOrDie(type, "getDefaultInstance"), + null); + break; + case ENUM: + enumValueOf = getMethodOrDie(type, "valueOf", + EnumValueDescriptor.class); + enumGetValueDescriptor = getMethodOrDie(type, "getValueDescriptor"); + messageDefaultInstance = null; + break; + default: + enumValueOf = null; + enumGetValueDescriptor = null; + messageDefaultInstance = null; + break; + } + } + + private final FieldDescriptor descriptor; + private final Class type; + private final Method enumValueOf; + private final Method enumGetValueDescriptor; + private final Message messageDefaultInstance; + + public FieldDescriptor getDescriptor() { return descriptor; } + + /** + * If the extension is an embedded message or group, returns the default + * instance of the message. + */ + @SuppressWarnings("unchecked") + public Message getMessageDefaultInstance() { + return messageDefaultInstance; + } + + /** + * Convert from the type used by the reflection accessors to the type used + * by native accessors. E.g., for enums, the reflection accessors use + * EnumValueDescriptors but the native accessors use the generated enum + * type. + */ + @SuppressWarnings("unchecked") + private Object fromReflectionType(Object value) { + if (descriptor.isRepeated()) { + if (descriptor.getJavaType() == FieldDescriptor.JavaType.MESSAGE || + descriptor.getJavaType() == FieldDescriptor.JavaType.ENUM) { + // Must convert the whole list. + List result = new ArrayList(); + for (Object element : (List)value) { + result.add(singularFromReflectionType(element)); + } + return result; + } else { + return value; + } + } else { + return singularFromReflectionType(value); + } + } + + /** + * Like {@link #fromReflectionType(Object)}, but if the type is a repeated + * type, this converts a single element. + */ + private Object singularFromReflectionType(Object value) { + switch (descriptor.getJavaType()) { + case MESSAGE: + if (type.isInstance(value)) { + return value; + } else { + // It seems the copy of the embedded message stored inside the + // extended message is not of the exact type the user was + // expecting. This can happen if a user defines a + // GeneratedExtension manually and gives it a different type. + // This should not happen in normal use. But, to be nice, we'll + // copy the message to whatever type the caller was expecting. + return messageDefaultInstance.newBuilderForType() + .mergeFrom((Message)value).build(); + } + case ENUM: + return invokeOrDie(enumValueOf, null, (EnumValueDescriptor)value); + default: + return value; + } + } + + /** + * Convert from the type used by the native accessors to the type used + * by reflection accessors. E.g., for enums, the reflection accessors use + * EnumValueDescriptors but the native accessors use the generated enum + * type. + */ + @SuppressWarnings("unchecked") + private Object toReflectionType(Object value) { + if (descriptor.isRepeated()) { + if (descriptor.getJavaType() == FieldDescriptor.JavaType.ENUM) { + // Must convert the whole list. + List result = new ArrayList(); + for (Object element : (List)value) { + result.add(singularToReflectionType(element)); + } + return result; + } else { + return value; + } + } else { + return singularToReflectionType(value); + } + } + + /** + * Like {@link #toReflectionType(Object)}, but if the type is a repeated + * type, this converts a single element. + */ + private Object singularToReflectionType(Object value) { + switch (descriptor.getJavaType()) { + case ENUM: + return invokeOrDie(enumGetValueDescriptor, value); + default: + return value; + } + } + } + + // ================================================================= + + /** Calls Class.getMethod and throws a RuntimeException if it fails. */ + @SuppressWarnings("unchecked") + private static Method getMethodOrDie( + Class clazz, String name, Class... params) { + try { + return clazz.getMethod(name, params); + } catch (NoSuchMethodException e) { + throw new RuntimeException( + "Generated message class \"" + clazz.getName() + + "\" missing method \"" + name + "\".", e); + } + } + + /** Calls invoke and throws a RuntimeException if it fails. */ + private static Object invokeOrDie( + Method method, Object object, Object... params) { + try { + return method.invoke(object, params); + } catch (IllegalAccessException e) { + throw new RuntimeException( + "Couldn't use Java reflection to implement protocol message " + + "reflection.", e); + } catch (java.lang.reflect.InvocationTargetException e) { + Throwable cause = e.getCause(); + if (cause instanceof RuntimeException) { + throw (RuntimeException)cause; + } else if (cause instanceof Error) { + throw (Error)cause; + } else { + throw new RuntimeException( + "Unexpected exception thrown by generated accessor method.", cause); + } + } + } + + /** + * Users should ignore this class. This class provides the implementation + * with access to the fields of a message object using Java reflection. + */ + public static final class FieldAccessorTable { + /** + * Construct a FieldAccessorTable for a particular message class. Only + * one FieldAccessorTable should ever be constructed per class. + * + * @param descriptor The type's descriptor. + * @param camelCaseNames The camelcase names of all fields in the message. + * These are used to derive the accessor method names. + * @param messageClass The message type. + * @param builderClass The builder type. + */ + public FieldAccessorTable( + Descriptor descriptor, + String[] camelCaseNames, + Class<? extends GeneratedMessage> messageClass, + Class<? extends GeneratedMessage.Builder> builderClass) { + this.descriptor = descriptor; + fields = new FieldAccessor[descriptor.getFields().size()]; + + for (int i = 0; i < fields.length; i++) { + FieldDescriptor field = descriptor.getFields().get(i); + if (field.isRepeated()) { + if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + fields[i] = new RepeatedMessageFieldAccessor( + field, camelCaseNames[i], messageClass, builderClass); + } else if (field.getJavaType() == FieldDescriptor.JavaType.ENUM) { + fields[i] = new RepeatedEnumFieldAccessor( + field, camelCaseNames[i], messageClass, builderClass); + } else { + fields[i] = new RepeatedFieldAccessor( + field, camelCaseNames[i], messageClass, builderClass); + } + } else { + if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + fields[i] = new SingularMessageFieldAccessor( + field, camelCaseNames[i], messageClass, builderClass); + } else if (field.getJavaType() == FieldDescriptor.JavaType.ENUM) { + fields[i] = new SingularEnumFieldAccessor( + field, camelCaseNames[i], messageClass, builderClass); + } else { + fields[i] = new SingularFieldAccessor( + field, camelCaseNames[i], messageClass, builderClass); + } + } + } + } + + private final Descriptor descriptor; + private final FieldAccessor[] fields; + + /** Get the FieldAccessor for a particular field. */ + private FieldAccessor getField(FieldDescriptor field) { + if (field.getContainingType() != descriptor) { + throw new IllegalArgumentException( + "FieldDescriptor does not match message type."); + } else if (field.isExtension()) { + // If this type had extensions, it would subclass ExtendableMessage, + // which overrides the reflection interface to handle extensions. + throw new IllegalArgumentException( + "This type does not have extensions."); + } + return fields[field.getIndex()]; + } + + /** + * Abstract interface that provides access to a single field. This is + * implemented differently depending on the field type and cardinality. + */ + private static interface FieldAccessor { + Object get(GeneratedMessage message); + void set(GeneratedMessage.Builder builder, Object value); + Object getRepeated(GeneratedMessage message, int index); + void setRepeated(GeneratedMessage.Builder builder, + int index, Object value); + void addRepeated(GeneratedMessage.Builder builder, Object value); + boolean has(GeneratedMessage message); + int getRepeatedCount(GeneratedMessage message); + void clear(GeneratedMessage.Builder builder); + Message.Builder newBuilder(); + } + + // --------------------------------------------------------------- + + private static class SingularFieldAccessor implements FieldAccessor { + SingularFieldAccessor( + FieldDescriptor descriptor, String camelCaseName, + Class<? extends GeneratedMessage> messageClass, + Class<? extends GeneratedMessage.Builder> builderClass) { + getMethod = getMethodOrDie(messageClass, "get" + camelCaseName); + type = getMethod.getReturnType(); + setMethod = getMethodOrDie(builderClass, "set" + camelCaseName, type); + hasMethod = + getMethodOrDie(messageClass, "has" + camelCaseName); + clearMethod = getMethodOrDie(builderClass, "clear" + camelCaseName); + } + + // Note: We use Java reflection to call public methods rather than + // access private fields directly as this avoids runtime security + // checks. + Class type; + Method getMethod; + Method setMethod; + Method hasMethod; + Method clearMethod; + + public Object get(GeneratedMessage message) { + return invokeOrDie(getMethod, message); + } + public void set(GeneratedMessage.Builder builder, Object value) { + invokeOrDie(setMethod, builder, value); + } + public Object getRepeated(GeneratedMessage message, int index) { + throw new UnsupportedOperationException( + "getRepeatedField() called on a singular field."); + } + public void setRepeated(GeneratedMessage.Builder builder, + int index, Object value) { + throw new UnsupportedOperationException( + "setRepeatedField() called on a singular field."); + } + public void addRepeated(GeneratedMessage.Builder builder, Object value) { + throw new UnsupportedOperationException( + "addRepeatedField() called on a singular field."); + } + public boolean has(GeneratedMessage message) { + return (Boolean)invokeOrDie(hasMethod, message); + } + public int getRepeatedCount(GeneratedMessage message) { + throw new UnsupportedOperationException( + "getRepeatedFieldSize() called on a singular field."); + } + public void clear(GeneratedMessage.Builder builder) { + invokeOrDie(clearMethod, builder); + } + public Message.Builder newBuilder() { + throw new UnsupportedOperationException( + "newBuilderForField() called on a non-Message type."); + } + } + + private static class RepeatedFieldAccessor implements FieldAccessor { + RepeatedFieldAccessor( + FieldDescriptor descriptor, String camelCaseName, + Class<? extends GeneratedMessage> messageClass, + Class<? extends GeneratedMessage.Builder> builderClass) { + getMethod = getMethodOrDie(messageClass, "get" + camelCaseName + "List"); + + getRepeatedMethod = + getMethodOrDie(messageClass, "get" + camelCaseName, Integer.TYPE); + type = getRepeatedMethod.getReturnType(); + setRepeatedMethod = + getMethodOrDie(builderClass, "set" + camelCaseName, + Integer.TYPE, type); + addRepeatedMethod = + getMethodOrDie(builderClass, "add" + camelCaseName, type); + getCountMethod = + getMethodOrDie(messageClass, "get" + camelCaseName + "Count"); + + clearMethod = getMethodOrDie(builderClass, "clear" + camelCaseName); + } + + Class type; + Method getMethod; + Method getRepeatedMethod; + Method setRepeatedMethod; + Method addRepeatedMethod; + Method getCountMethod; + Method clearMethod; + + public Object get(GeneratedMessage message) { + return invokeOrDie(getMethod, message); + } + public void set(GeneratedMessage.Builder builder, Object value) { + // Add all the elements individually. This serves two purposes: + // 1) Verifies that each element has the correct type. + // 2) Insures that the caller cannot modify the list later on and + // have the modifications be reflected in the message. + clear(builder); + for (Object element : (List)value) { + addRepeated(builder, element); + } + } + public Object getRepeated(GeneratedMessage message, int index) { + return invokeOrDie(getRepeatedMethod, message, index); + } + public void setRepeated(GeneratedMessage.Builder builder, + int index, Object value) { + invokeOrDie(setRepeatedMethod, builder, index, value); + } + public void addRepeated(GeneratedMessage.Builder builder, Object value) { + invokeOrDie(addRepeatedMethod, builder, value); + } + public boolean has(GeneratedMessage message) { + throw new UnsupportedOperationException( + "hasField() called on a singular field."); + } + public int getRepeatedCount(GeneratedMessage message) { + return (Integer)invokeOrDie(getCountMethod, message); + } + public void clear(GeneratedMessage.Builder builder) { + invokeOrDie(clearMethod, builder); + } + public Message.Builder newBuilder() { + throw new UnsupportedOperationException( + "newBuilderForField() called on a non-Message type."); + } + } + + // --------------------------------------------------------------- + + private static final class SingularEnumFieldAccessor + extends SingularFieldAccessor { + SingularEnumFieldAccessor( + FieldDescriptor descriptor, String camelCaseName, + Class<? extends GeneratedMessage> messageClass, + Class<? extends GeneratedMessage.Builder> builderClass) { + super(descriptor, camelCaseName, messageClass, builderClass); + + valueOfMethod = getMethodOrDie(type, "valueOf", + EnumValueDescriptor.class); + getValueDescriptorMethod = + getMethodOrDie(type, "getValueDescriptor"); + } + + private Method valueOfMethod; + private Method getValueDescriptorMethod; + + public Object get(GeneratedMessage message) { + return invokeOrDie(getValueDescriptorMethod, super.get(message)); + } + public void set(GeneratedMessage.Builder builder, Object value) { + super.set(builder, invokeOrDie(valueOfMethod, null, value)); + } + } + + private static final class RepeatedEnumFieldAccessor + extends RepeatedFieldAccessor { + RepeatedEnumFieldAccessor( + FieldDescriptor descriptor, String camelCaseName, + Class<? extends GeneratedMessage> messageClass, + Class<? extends GeneratedMessage.Builder> builderClass) { + super(descriptor, camelCaseName, messageClass, builderClass); + + valueOfMethod = getMethodOrDie(type, "valueOf", + EnumValueDescriptor.class); + getValueDescriptorMethod = + getMethodOrDie(type, "getValueDescriptor"); + } + + private Method valueOfMethod; + private Method getValueDescriptorMethod; + + @SuppressWarnings("unchecked") + public Object get(GeneratedMessage message) { + List newList = new ArrayList(); + for (Object element : (List)super.get(message)) { + newList.add(invokeOrDie(getValueDescriptorMethod, element)); + } + return Collections.unmodifiableList(newList); + } + public Object getRepeated(GeneratedMessage message, int index) { + return invokeOrDie(getValueDescriptorMethod, + super.getRepeated(message, index)); + } + public void setRepeated(GeneratedMessage.Builder builder, + int index, Object value) { + super.setRepeated(builder, index, invokeOrDie(valueOfMethod, null, value)); + } + public void addRepeated(GeneratedMessage.Builder builder, Object value) { + super.addRepeated(builder, invokeOrDie(valueOfMethod, null, value)); + } + } + + // --------------------------------------------------------------- + + private static final class SingularMessageFieldAccessor + extends SingularFieldAccessor { + SingularMessageFieldAccessor( + FieldDescriptor descriptor, String camelCaseName, + Class<? extends GeneratedMessage> messageClass, + Class<? extends GeneratedMessage.Builder> builderClass) { + super(descriptor, camelCaseName, messageClass, builderClass); + + newBuilderMethod = getMethodOrDie(type, "newBuilder"); + } + + private Method newBuilderMethod; + + private Object coerceType(Object value) { + if (type.isInstance(value)) { + return value; + } else { + // The value is not the exact right message type. However, if it + // is an alternative implementation of the same type -- e.g. a + // DynamicMessage -- we should accept it. In this case we can make + // a copy of the message. + return ((Message.Builder)invokeOrDie(newBuilderMethod, null)) + .mergeFrom((Message)value).build(); + } + } + + public void set(GeneratedMessage.Builder builder, Object value) { + super.set(builder, coerceType(value)); + } + public Message.Builder newBuilder() { + return (Message.Builder)invokeOrDie(newBuilderMethod, null); + } + } + + private static final class RepeatedMessageFieldAccessor + extends RepeatedFieldAccessor { + RepeatedMessageFieldAccessor( + FieldDescriptor descriptor, String camelCaseName, + Class<? extends GeneratedMessage> messageClass, + Class<? extends GeneratedMessage.Builder> builderClass) { + super(descriptor, camelCaseName, messageClass, builderClass); + + newBuilderMethod = getMethodOrDie(type, "newBuilder"); + } + + private Method newBuilderMethod; + + private Object coerceType(Object value) { + if (type.isInstance(value)) { + return value; + } else { + // The value is not the exact right message type. However, if it + // is an alternative implementation of the same type -- e.g. a + // DynamicMessage -- we should accept it. In this case we can make + // a copy of the message. + return ((Message.Builder)invokeOrDie(newBuilderMethod, null)) + .mergeFrom((Message)value).build(); + } + } + + public void setRepeated(GeneratedMessage.Builder builder, + int index, Object value) { + super.setRepeated(builder, index, coerceType(value)); + } + public void addRepeated(GeneratedMessage.Builder builder, Object value) { + super.addRepeated(builder, coerceType(value)); + } + public Message.Builder newBuilder() { + return (Message.Builder)invokeOrDie(newBuilderMethod, null); + } + } + } +} diff --git a/java/src/main/java/com/google/protobuf/InvalidProtocolBufferException.java b/java/src/main/java/com/google/protobuf/InvalidProtocolBufferException.java new file mode 100644 index 00000000..d57da4ed --- /dev/null +++ b/java/src/main/java/com/google/protobuf/InvalidProtocolBufferException.java @@ -0,0 +1,77 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.protobuf; + +import java.io.IOException; + +/** + * Thrown when a protocol message being parsed is invalid in some way, + * e.g. it contains a malformed varint or a negative byte length. + * + * @author kenton@google.com Kenton Varda + */ +public class InvalidProtocolBufferException extends IOException { + public InvalidProtocolBufferException(String description) { + super(description); + } + + static InvalidProtocolBufferException truncatedMessage() { + return new InvalidProtocolBufferException( + "While parsing a protocol message, the input ended unexpectedly " + + "in the middle of a field. This could mean either than the " + + "input has been truncated or that an embedded message " + + "misreported its own length."); + } + + static InvalidProtocolBufferException negativeSize() { + return new InvalidProtocolBufferException( + "CodedInputStream encountered an embedded string or message " + + "which claimed to have negative size."); + } + + static InvalidProtocolBufferException malformedVarint() { + return new InvalidProtocolBufferException( + "CodedInputStream encountered a malformed varint."); + } + + static InvalidProtocolBufferException invalidTag() { + return new InvalidProtocolBufferException( + "Protocol message contained an invalid tag (zero)."); + } + + static InvalidProtocolBufferException invalidEndTag() { + return new InvalidProtocolBufferException( + "Protocol message end-group tag did not match expected tag."); + } + + static InvalidProtocolBufferException invalidWireType() { + return new InvalidProtocolBufferException( + "Protocol message tag had invalid wire type."); + } + + static InvalidProtocolBufferException recursionLimitExceeded() { + return new InvalidProtocolBufferException( + "Protocol message had too many levels of nesting. May be malicious. " + + "Use CodedInputStream.setRecursionLimit() to increase the depth limit."); + } + + static InvalidProtocolBufferException sizeLimitExceeded() { + return new InvalidProtocolBufferException( + "Protocol message was too large. May be malicious. " + + "Use CodedInputStream.setSizeLimit() to increase the size limit."); + } +} diff --git a/java/src/main/java/com/google/protobuf/Message.java b/java/src/main/java/com/google/protobuf/Message.java new file mode 100644 index 00000000..f45f7df9 --- /dev/null +++ b/java/src/main/java/com/google/protobuf/Message.java @@ -0,0 +1,415 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// TODO(kenton): Use generics? E.g. Builder<BuilderType extends Builder>, then +// mergeFrom*() could return BuilderType for better type-safety. + +package com.google.protobuf; + +import java.io.InputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Map; + +/** + * Abstract interface implemented by Protocol Message objects. + * + * @author kenton@google.com Kenton Varda + */ +public interface Message { + /** + * Get the message's type's descriptor. This differs from the + * {@code getDescriptor()} method of generated message classes in that + * this method is an abstract method of the {@code Message} interface + * whereas {@code getDescriptor()} is a static method of a specific class. + * They return the same thing. + */ + Descriptors.Descriptor getDescriptorForType(); + + /** + * Get an instance of the type with all fields set to their default values. + * This may or may not be a singleton. This differs from the + * {@code getDefaultInstance()} method of generated message classes in that + * this method is an abstract method of the {@code Message} interface + * whereas {@code getDefaultInstance()} is a static method of a specific + * class. They return the same thing. + */ + Message getDefaultInstanceForType(); + + /** + * 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 getRepeatedFieldSize() is greater than zero. The + * values are exactly what would be returned by calling + * {@link #getField(Descriptors.FieldDescriptor)} for each field. The map + * is guaranteed to be a sorted map, so iterating over it will return fields + * in order by field number. + */ + Map<Descriptors.FieldDescriptor, Object> getAllFields(); + + /** + * Returns true if the given field is set. This is exactly equivalent to + * calling the generated "has" accessor method corresponding to the field. + * @throws IllegalArgumentException The field is a repeated field, or + * {@code field.getContainingType() != getDescriptorForType()}. + */ + boolean hasField(Descriptors.FieldDescriptor field); + + /** + * Obtains the value of the given field, or the default value if it is + * not set. For primitive fields, the boxed primitive value is returned. + * For enum fields, the EnumValueDescriptor for the value is returend. For + * embedded message fields, the sub-message is returned. For repeated + * fields, a java.util.List is returned. + */ + Object getField(Descriptors.FieldDescriptor field); + + /** + * Gets the number of elements of a repeated field. This is exactly + * equivalent to calling the generated "Count" accessor method corresponding + * to the field. + * @throws IllegalArgumentException The field is not a repeated field, or + * {@code field.getContainingType() != getDescriptorForType()}. + */ + int getRepeatedFieldCount(Descriptors.FieldDescriptor field); + + /** + * Gets an element of a repeated field. For primitive fields, the boxed + * primitive value is returned. For enum fields, the EnumValueDescriptor + * for the value is returend. For embedded message fields, the sub-message + * is returned. + * @throws IllegalArgumentException The field is not a repeated field, or + * {@code field.getContainingType() != getDescriptorForType()}. + */ + Object getRepeatedField(Descriptors.FieldDescriptor field, int index); + + /** Get the {@link UnknownFieldSet} for this message. */ + UnknownFieldSet getUnknownFields(); + + /** + * Returns true if all required fields in the message and all embedded + * messages are set, false otherwise. + */ + boolean isInitialized(); + + /** + * Serializes the message and writes it to {@code output}. This does not + * flush or close the stream. + */ + void writeTo(CodedOutputStream output) throws IOException; + + /** + * Get the number of bytes required to encode this message. The result + * is only computed on the first call and memoized after that. + */ + int getSerializedSize(); + + // ----------------------------------------------------------------- + // Comparison and hashing + + /** + * Compares the specified object with this message for equality. Returns + * <tt>true</tt> if the given object is a message of the same type (as + * defined by {@code getDescriptorForType()}) and has identical values for + * all of its fields. + * + * @param other object to be compared for equality with this message + * @return <tt>true</tt> if the specified object is equal to this message + */ + boolean equals(Object other); + + /** + * Returns the hash code value for this message. The hash code of a message + * is defined to be <tt>getDescriptor().hashCode() ^ map.hashCode()</tt>, + * where <tt>map</tt> is a map of field numbers to field values. + * + * @return the hash code value for this message + * @see Map#hashCode() + */ + int hashCode(); + + // ----------------------------------------------------------------- + // Convenience methods. + + /** + * Converts the message to a string in protocol buffer text format. This is + * just a trivial wrapper around {@link TextFormat#printToString(Message)}. + */ + String toString(); + + /** + * Serializes the message to a {@code ByteString} and returns it. This is + * just a trivial wrapper around + * {@link #writeTo(CodedOutputStream)}. + */ + ByteString toByteString(); + + /** + * Serializes the message to a {@code byte} array and returns it. This is + * just a trivial wrapper around + * {@link #writeTo(CodedOutputStream)}. + */ + byte[] toByteArray(); + + /** + * Serializes the message and writes it to {@code output}. This is just a + * trivial wrapper around {@link #writeTo(CodedOutputStream)}. This does + * not flush or close the stream. + */ + void writeTo(OutputStream output) throws IOException; + + // ================================================================= + // Builders + + /** + * Constructs a new builder for a message of the same type as this message. + */ + Builder newBuilderForType(); + + /** + * Abstract interface implemented by Protocol Message builders. + */ + public static interface Builder extends Cloneable { + /** Resets all fields to their default values. */ + Builder clear(); + + /** + * Merge {@code other} into the message being built. {@code other} must + * have the exact same type as {@code this} (i.e. + * {@code getDescriptorForType() == other.getDescriptorForType()}). + * + * Merging occurs as follows. For each field:<br> + * * For singular primitive fields, if the field is set in {@code other}, + * then {@code other}'s value overwrites the value in this message.<br> + * * For singular message fields, if the field is set in {@code other}, + * it is merged into the corresponding sub-message of this message + * using the same merging rules.<br> + * * For repeated fields, the elements in {@code other} are concatenated + * with the elements in this message. + * + * This is equivalent to the {@code Message::MergeFrom} method in C++. + */ + Builder mergeFrom(Message other); + + /** + * Construct the final message. Once this is called, the Builder is no + * longer valid, and calling any other method may throw a + * NullPointerException. If you need to continue working with the builder + * after calling {@code build()}, {@code clone()} it first. + * @throws UninitializedMessageException The message is missing one or more + * required fields (i.e. {@link #isInitialized()} returns false). + * Use {@link #buildPartial()} to bypass this check. + */ + Message build(); + + /** + * Like {@link #build()}, but does not throw an exception if the message + * is missing required fields. Instead, a partial message is returned. + */ + Message buildPartial(); + + /** + * Clones the Builder. + * @see Object#clone() + */ + Builder clone(); + + /** + * Returns true if all required fields in the message and all embedded + * messages are set, false otherwise. + */ + boolean isInitialized(); + + /** + * Parses a message of this type from the input and merges it with this + * message, as if using {@link Builder#mergeFrom(Message)}. + * + * <p>Warning: This does not verify that all required fields are present in + * the input message. If you call {@link #build()} without setting all + * required fields, it will throw an {@link UninitializedMessageException}, + * which is a {@code RuntimeException} and thus might not be caught. There + * are a few good ways to deal with this: + * <ul> + * <li>Call {@link #isInitialized()} to verify that all required fields + * are set before building. + * <li>Parse the message separately using one of the static + * {@code parseFrom} methods, then use {@link #mergeFrom(Message)} + * to merge it with this one. {@code parseFrom} will throw an + * {@link InvalidProtocolBufferException} (an {@code IOException}) + * if some required fields are missing. + * <li>Use {@code buildPartial()} to build, which ignores missing + * required fields. + * </ul> + * + * <p>Note: The caller should call + * {@link CodedInputStream#checkLastTagWas(int)} after calling this to + * verify that the last tag seen was the appropriate end-group tag, + * or zero for EOF. + */ + Builder mergeFrom(CodedInputStream input) throws IOException; + + /** + * Like {@link Builder#mergeFrom(CodedInputStream)}, but also + * parses extensions. The extensions that you want to be able to parse + * must be registered in {@code extensionRegistry}. Extensions not in + * the registry will be treated as unknown fields. + */ + Builder mergeFrom(CodedInputStream input, + ExtensionRegistry extensionRegistry) + throws IOException; + + /** + * Get the message's type's descriptor. + * See {@link Message#getDescriptorForType()}. + */ + Descriptors.Descriptor getDescriptorForType(); + + /** + * Get the message's type's default instance. + * See {@link Message#getDefaultInstanceForType()}. + */ + Message getDefaultInstanceForType(); + + /** + * Like {@link Message#getAllFields()}. The returned map may or may not + * reflect future changes to the builder. Either way, the returned map is + * itself unmodifiable. + */ + Map<Descriptors.FieldDescriptor, Object> getAllFields(); + + /** + * Create a Builder for messages of the appropriate type for the given + * field. Messages built with this can then be passed to setField(), + * setRepeatedField(), or addRepeatedField(). + */ + Builder newBuilderForField(Descriptors.FieldDescriptor field); + + /** Like {@link Message#hasField(Descriptors.FieldDescriptor)} */ + boolean hasField(Descriptors.FieldDescriptor field); + + /** Like {@link Message#getField(Descriptors.FieldDescriptor)} */ + Object getField(Descriptors.FieldDescriptor field); + + /** + * Sets a field to the given value. The value must be of the correct type + * for this field, i.e. the same type that + * {@link Message#getField(Descriptors.FieldDescriptor)} would return. + */ + Builder setField(Descriptors.FieldDescriptor field, Object value); + + /** + * Clears the field. This is exactly equivalent to calling the generated + * "clear" accessor method corresponding to the field. + */ + Builder clearField(Descriptors.FieldDescriptor field); + + /** + * Like {@link Message#getRepeatedFieldCount(Descriptors.FieldDescriptor)} + */ + int getRepeatedFieldCount(Descriptors.FieldDescriptor field); + + /** + * Like {@link Message#getRepeatedField(Descriptors.FieldDescriptor,int)} + */ + Object getRepeatedField(Descriptors.FieldDescriptor field, int index); + + /** + * Sets an element of a repeated field to the given value. The value must + * be of the correct type for this field, i.e. the same type that + * {@link Message#getRepeatedField(Descriptors.FieldDescriptor,int)} would + * return. + * @throws IllegalArgumentException The field is not a repeated field, or + * {@code field.getContainingType() != getDescriptorForType()}. + */ + Builder setRepeatedField(Descriptors.FieldDescriptor field, + int index, Object value); + + /** + * Like {@code setRepeatedField}, but appends the value as a new element. + * @throws IllegalArgumentException The field is not a repeated field, or + * {@code field.getContainingType() != getDescriptorForType()}. + */ + Builder addRepeatedField(Descriptors.FieldDescriptor field, Object value); + + /** Get the {@link UnknownFieldSet} for this message. */ + UnknownFieldSet getUnknownFields(); + + /** Set the {@link UnknownFieldSet} for this message. */ + Builder setUnknownFields(UnknownFieldSet unknownFields); + + /** + * Merge some unknown fields into the {@link UnknownFieldSet} for this + * message. + */ + Builder mergeUnknownFields(UnknownFieldSet unknownFields); + + // --------------------------------------------------------------- + // Convenience methods. + + /** + * Parse {@code data} as a message of this type and merge it with the + * message being built. This is just a small wrapper around + * {@link #mergeFrom(CodedInputStream)}. + */ + Builder mergeFrom(ByteString data) throws InvalidProtocolBufferException; + + /** + * Parse {@code data} as a message of this type and merge it with the + * message being built. This is just a small wrapper around + * {@link #mergeFrom(CodedInputStream,ExtensionRegistry)}. + */ + Builder mergeFrom(ByteString data, + ExtensionRegistry extensionRegistry) + throws InvalidProtocolBufferException; + + /** + * Parse {@code data} as a message of this type and merge it with the + * message being built. This is just a small wrapper around + * {@link #mergeFrom(CodedInputStream)}. + */ + public Builder mergeFrom(byte[] data) throws InvalidProtocolBufferException; + + /** + * Parse {@code data} as a message of this type and merge it with the + * message being built. This is just a small wrapper around + * {@link #mergeFrom(CodedInputStream,ExtensionRegistry)}. + */ + Builder mergeFrom(byte[] data, + ExtensionRegistry extensionRegistry) + throws InvalidProtocolBufferException; + + /** + * Parse a message of this type from {@code input} and merge it with the + * message being built. This is just a small wrapper around + * {@link #mergeFrom(CodedInputStream)}. Note that this method always + * reads the <i>entire</i> input (unless it throws an exception). If you + * want it to stop earlier, you will need to wrap your input in some + * wrapper stream that limits reading. Despite usually reading the entire + * input, this does not close the stream. + */ + Builder mergeFrom(InputStream input) throws IOException; + + /** + * Parse a message of this type from {@code input} and merge it with the + * message being built. This is just a small wrapper around + * {@link #mergeFrom(CodedInputStream,ExtensionRegistry)}. + */ + Builder mergeFrom(InputStream input, + ExtensionRegistry extensionRegistry) + throws IOException; + } +} diff --git a/java/src/main/java/com/google/protobuf/RpcCallback.java b/java/src/main/java/com/google/protobuf/RpcCallback.java new file mode 100644 index 00000000..a99077fa --- /dev/null +++ b/java/src/main/java/com/google/protobuf/RpcCallback.java @@ -0,0 +1,27 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.protobuf; + +/** + * Interface for an RPC callback, normally called when an RPC completes. + * {@code ParameterType} is normally the method's response message type. + * + * @author kenton@google.com Kenton Varda + */ +public interface RpcCallback<ParameterType> { + void run(ParameterType parameter); +} diff --git a/java/src/main/java/com/google/protobuf/RpcChannel.java b/java/src/main/java/com/google/protobuf/RpcChannel.java new file mode 100644 index 00000000..1c7dcfc0 --- /dev/null +++ b/java/src/main/java/com/google/protobuf/RpcChannel.java @@ -0,0 +1,51 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.protobuf; + +/** + * <p>Abstract interface for an RPC channel. An {@code RpcChannel} represents a + * communication line to a {@link Service} which can be used to call that + * {@link Service}'s methods. The {@link Service} may be running on another + * machine. Normally, you should not call an {@code RpcChannel} directly, but + * instead construct a stub {@link Service} wrapping it. Example: + * + * <pre> + * RpcChannel channel = rpcImpl.newChannel("remotehost.example.com:1234"); + * RpcController controller = rpcImpl.newController(); + * MyService service = MyService.newStub(channel); + * service.myMethod(controller, request, callback); + * </pre> + * + * @author kenton@google.com Kenton Varda + */ +public interface RpcChannel { + /** + * Call the given method of the remote service. This method is similar to + * {@code Service.callMethod()} with one important difference: the caller + * decides the types of the {@code Message} objects, not the callee. The + * request may be of any type as long as + * {@code request.getDescriptor() == method.getInputType()}. + * The response passed to the callback will be of the same type as + * {@code responsePrototype} (which must have + * {@code getDescriptor() == method.getOutputType()}). + */ + void callMethod(Descriptors.MethodDescriptor method, + RpcController controller, + Message request, + Message responsePrototype, + RpcCallback<Message> done); +} diff --git a/java/src/main/java/com/google/protobuf/RpcController.java b/java/src/main/java/com/google/protobuf/RpcController.java new file mode 100644 index 00000000..7495bb8a --- /dev/null +++ b/java/src/main/java/com/google/protobuf/RpcController.java @@ -0,0 +1,98 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.protobuf; + +/** + * <p>An {@code RpcController} mediates a single method call. The primary + * purpose of the controller is to provide a way to manipulate settings + * specific to the RPC implementation and to find out about RPC-level errors. + * + * <p>The methods provided by the {@code RpcController} interface are intended + * to be a "least common denominator" set of features which we expect all + * implementations to support. Specific implementations may provide more + * advanced features (e.g. deadline propagation). + * + * @author kenton@google.com Kenton Varda + */ +public interface RpcController { + // ----------------------------------------------------------------- + // These calls may be made from the client side only. Their results + // are undefined on the server side (may throw RuntimeExceptions). + + /** + * Resets the RpcController to its initial state so that it may be reused in + * a new call. This can be called from the client side only. It must not + * be called while an RPC is in progress. + */ + void reset(); + + /** + * After a call has finished, returns true if the call failed. The possible + * reasons for failure depend on the RPC implementation. {@code failed()} + * most only be called on the client side, and must not be called before a + * call has finished. + */ + boolean failed(); + + /** + * If {@code failed()} is {@code true}, returns a human-readable description + * of the error. + */ + String errorText(); + + /** + * Advises the RPC system that the caller desires that the RPC call be + * canceled. The RPC system may cancel it immediately, may wait awhile and + * then cancel it, or may not even cancel the call at all. If the call is + * canceled, the "done" callback will still be called and the RpcController + * will indicate that the call failed at that time. + */ + void startCancel(); + + // ----------------------------------------------------------------- + // These calls may be made from the server side only. Their results + // are undefined on the client side (may throw RuntimeExceptions). + + /** + * Causes {@code failed()} to return true on the client side. {@code reason} + * will be incorporated into the message returned by {@code errorText()}. + * If you find you need to return machine-readable information about + * failures, you should incorporate it into your response protocol buffer + * and should NOT call {@code setFailed()}. + */ + void setFailed(String reason); + + /** + * If {@code true}, indicates that the client canceled the RPC, so the server + * may as well give up on replying to it. This method must be called on the + * server side only. The server should still call the final "done" callback. + */ + boolean isCanceled(); + + /** + * Asks that the given callback be called when the RPC is canceled. The + * parameter passed to the callback will always be {@code null}. The + * callback will always be called exactly once. If the RPC completes without + * being canceled, the callback will be called after completion. If the RPC + * has already been canceled when NotifyOnCancel() is called, the callback + * will be called immediately. + * + * <p>{@code notifyOnCancel()} must be called no more than once per request. + * It must be called on the server side only. + */ + void notifyOnCancel(RpcCallback<Object> callback); +} diff --git a/java/src/main/java/com/google/protobuf/RpcUtil.java b/java/src/main/java/com/google/protobuf/RpcUtil.java new file mode 100644 index 00000000..13cd4aca --- /dev/null +++ b/java/src/main/java/com/google/protobuf/RpcUtil.java @@ -0,0 +1,118 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.protobuf; + +/** + * Grab-bag of utility functions useful when dealing with RPCs. + * + * @author kenton@google.com Kenton Varda + */ +public final class RpcUtil { + private RpcUtil() {} + + /** + * Take an {@code RcpCallabck<Message>} and convert it to an + * {@code RpcCallback} accepting a specific message type. This is always + * type-safe (parameter type contravariance). + */ + @SuppressWarnings("unchecked") + public static <Type extends Message> RpcCallback<Type> + specializeCallback(final RpcCallback<Message> originalCallback) { + return (RpcCallback<Type>)originalCallback; + // The above cast works, but only due to technical details of the Java + // implementation. A more theoretically correct -- but less efficient -- + // implementation would be as follows: + // return new RpcCallback<Type>() { + // public void run(Type parameter) { + // originalCallback.run(parameter); + // } + // }; + } + + /** + * Take an {@code RcpCallabck} accepting a specific message type and convert + * it to an {@code RcpCallabck<Message>}. The generalized callback will + * accept any message object which has the same descriptor, and will convert + * it to the correct class before calling the original callback. However, + * if the generalized callback is given a message with a different descriptor, + * an exception will be thrown. + */ + public static <Type extends Message> + RpcCallback<Message> generalizeCallback( + final RpcCallback<Type> originalCallback, + final Class<Type> originalClass, + final Type defaultInstance) { + return new RpcCallback<Message>() { + public void run(Message parameter) { + Type typedParameter; + try { + typedParameter = originalClass.cast(parameter); + } catch (ClassCastException e) { + typedParameter = copyAsType(defaultInstance, parameter); + } + originalCallback.run(typedParameter); + } + }; + } + + /** + * Creates a new message of type "Type" which is a copy of "source". "source" + * must have the same descriptor but may be a different class (e.g. + * DynamicMessage). + */ + @SuppressWarnings("unchecked") + private static <Type extends Message> Type copyAsType( + Type typeDefaultInstance, Message source) { + return (Type)typeDefaultInstance.newBuilderForType() + .mergeFrom(source) + .build(); + } + + /** + * Creates a callback which can only be called once. This may be useful for + * security, when passing a callback to untrusted code: most callbacks do + * not expect to be called more than once, so doing so may expose bugs if it + * is not prevented. + */ + public static <ParameterType> + RpcCallback<ParameterType> newOneTimeCallback( + final RpcCallback<ParameterType> originalCallback) { + return new RpcCallback<ParameterType>() { + boolean alreadyCalled = false; + public void run(ParameterType parameter) { + synchronized(this) { + if (alreadyCalled) { + throw new AlreadyCalledException(); + } + alreadyCalled = true; + } + + originalCallback.run(parameter); + } + }; + } + + /** + * Exception thrown when a one-time callback is called more than once. + */ + public static final class AlreadyCalledException extends RuntimeException { + public AlreadyCalledException() { + super("This RpcCallback was already called and cannot be called " + + "multiple times."); + } + } +} diff --git a/java/src/main/java/com/google/protobuf/Service.java b/java/src/main/java/com/google/protobuf/Service.java new file mode 100644 index 00000000..53b2557e --- /dev/null +++ b/java/src/main/java/com/google/protobuf/Service.java @@ -0,0 +1,97 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.protobuf; + +/** + * Abstract base interface for protocol-buffer-based RPC services. Services + * themselves are abstract classes (implemented either by servers or as + * stubs), but they subclass this base interface. The methods of this + * interface can be used to call the methods of the service without knowing + * its exact type at compile time (analogous to the Message interface). + * + * @author kenton@google.com Kenton Varda + */ +public interface Service { + /** + * Get the {@code ServiceDescriptor} describing this service and its methods. + */ + Descriptors.ServiceDescriptor getDescriptorForType(); + + /** + * <p>Call a method of the service specified by MethodDescriptor. This is + * normally implemented as a simple {@code switch()} that calls the standard + * definitions of the service's methods. + * + * <p>Preconditions: + * <ul> + * <li>{@code method.getService() == getDescriptorForType()} + * <li>{@code request} is of the exact same class as the object returned by + * {@code getRequestPrototype(method)}. + * <li>{@code controller} is of the correct type for the RPC implementation + * being used by this Service. For stubs, the "correct type" depends + * on the RpcChannel which the stub is using. Server-side Service + * implementations are expected to accept whatever type of + * {@code RpcController} the server-side RPC implementation uses. + * </ul> + * + * <p>Postconditions: + * <ul> + * <li>{@code done} will be called when the method is complete. This may be + * before {@code callMethod()} returns or it may be at some point in + * the future. + * <li>The parameter to {@code done} is the response. It must be of the + * exact same type as would be returned by + * {@code getResponsePrototype(method)}. + * <li>If the RPC failed, the parameter to {@code done} will be + * {@code null}. Further details about the failure can be found by + * querying {@code controller}. + * </ul> + */ + void callMethod(Descriptors.MethodDescriptor method, + RpcController controller, + Message request, + RpcCallback<Message> done); + + /** + * <p>{@code callMethod()} requires that the request passed in is of a + * particular subclass of {@code Message}. {@code getRequestPrototype()} + * gets the default instances of this type for a given method. You can then + * call {@code Message.newBuilderForType()} on this instance to + * construct a builder to build an object which you can then pass to + * {@code callMethod()}. + * + * <p>Example: + * <pre> + * MethodDescriptor method = + * service.getDescriptorForType().findMethodByName("Foo"); + * Message request = + * stub.getRequestPrototype(method).newBuilderForType() + * .mergeFrom(input).build(); + * service.callMethod(method, request, callback); + * </pre> + */ + Message getRequestPrototype(Descriptors.MethodDescriptor method); + + /** + * Like {@code getRequestPrototype()}, but gets a prototype of the response + * message. {@code getResponsePrototype()} is generally not needed because + * the {@code Service} implementation constructs the response message itself, + * but it may be useful in some cases to know ahead of time what type of + * object will be returned. + */ + Message getResponsePrototype(Descriptors.MethodDescriptor method); +} diff --git a/java/src/main/java/com/google/protobuf/TextFormat.java b/java/src/main/java/com/google/protobuf/TextFormat.java new file mode 100644 index 00000000..2d949b65 --- /dev/null +++ b/java/src/main/java/com/google/protobuf/TextFormat.java @@ -0,0 +1,1276 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.protobuf; + +import com.google.protobuf.Descriptors.Descriptor; +import com.google.protobuf.Descriptors.FieldDescriptor; +import com.google.protobuf.Descriptors.EnumDescriptor; +import com.google.protobuf.Descriptors.EnumValueDescriptor; + +import java.io.IOException; +import java.nio.CharBuffer; +import java.math.BigInteger; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Provide ascii text parsing and formatting support for proto2 instances. + * The implementation largely follows google/protobuf/text_format.cc. + * + * @author wenboz@google.com Wenbo Zhu + * @author kenton@google.com Kenton Varda + */ +public final class TextFormat { + + /** + * Outputs a textual representation of the Protocol Message supplied into + * the parameter output. (This representation is the new version of the + * classic "ProtocolPrinter" output from the original Protocol Buffer system) + */ + public static void print(Message message, Appendable output) + throws IOException { + TextGenerator generator = new TextGenerator(output); + print(message, generator); + } + + /** Outputs a textual representation of {@code fields} to {@code output}. */ + public static void print(UnknownFieldSet fields, Appendable output) + throws IOException { + TextGenerator generator = new TextGenerator(output); + printUnknownFields(fields, generator); + } + + /** + * Like {@code print()}, but writes directly to a {@code String} and + * returns it. + */ + public static String printToString(Message message) { + try { + StringBuilder text = new StringBuilder(); + print(message, text); + return text.toString(); + } catch (IOException e) { + throw new RuntimeException( + "Writing to a StringBuilder threw an IOException (should never " + + "happen).", e); + } + } + + /** + * Like {@code print()}, but writes directly to a {@code String} and + * returns it. + */ + public static String printToString(UnknownFieldSet fields) { + try { + StringBuilder text = new StringBuilder(); + print(fields, text); + return text.toString(); + } catch (IOException e) { + throw new RuntimeException( + "Writing to a StringBuilder threw an IOException (should never " + + "happen).", e); + } + } + + private static void print(Message message, TextGenerator generator) + throws IOException { + Descriptor descriptor = message.getDescriptorForType(); + for (Map.Entry<FieldDescriptor, Object> field : + message.getAllFields().entrySet()) { + printField(field.getKey(), field.getValue(), generator); + } + printUnknownFields(message.getUnknownFields(), generator); + } + + public static void printField(FieldDescriptor field, + Object value, + TextGenerator generator) + throws IOException { + if (field.isRepeated()) { + // Repeated field. Print each element. + for (Object element : (List) value) { + printSingleField(field, element, generator); + } + } else { + printSingleField(field, value, generator); + } + } + + private static void printSingleField(FieldDescriptor field, + Object value, + TextGenerator generator) + throws IOException { + if (field.isExtension()) { + generator.print("["); + // We special-case MessageSet elements for compatibility with proto1. + if (field.getContainingType().getOptions().getMessageSetWireFormat() + && (field.getType() == FieldDescriptor.Type.MESSAGE) + && (field.isOptional()) + // object equality + && (field.getExtensionScope() == field.getMessageType())) { + generator.print(field.getMessageType().getFullName()); + } else { + generator.print(field.getFullName()); + } + generator.print("]"); + } else { + if (field.getType() == FieldDescriptor.Type.GROUP) { + // Groups must be serialized with their original capitalization. + generator.print(field.getMessageType().getName()); + } else { + generator.print(field.getName()); + } + } + + if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + generator.print(" {\n"); + generator.indent(); + } else { + generator.print(": "); + } + + printFieldValue(field, value, generator); + + if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + generator.outdent(); + generator.print("}"); + } + generator.print("\n"); + } + + private static void printFieldValue(FieldDescriptor field, + Object value, + TextGenerator generator) + throws IOException { + switch (field.getType()) { + case INT32: + case INT64: + case SINT32: + case SINT64: + case SFIXED32: + case SFIXED64: + case FLOAT: + case DOUBLE: + case BOOL: + // Good old toString() does what we want for these types. + generator.print(value.toString()); + break; + + case UINT32: + case FIXED32: + generator.print(unsignedToString((Integer) value)); + break; + + case UINT64: + case FIXED64: + generator.print(unsignedToString((Long) value)); + break; + + case STRING: + generator.print("\""); + generator.print(escapeText((String) value)); + generator.print("\""); + break; + + case BYTES: { + generator.print("\""); + generator.print(escapeBytes((ByteString) value)); + generator.print("\""); + break; + } + + case ENUM: { + generator.print(((EnumValueDescriptor) value).getName()); + break; + } + + case MESSAGE: + case GROUP: + print((Message) value, generator); + break; + } + } + + private static void printUnknownFields(UnknownFieldSet unknownFields, + TextGenerator generator) + throws IOException { + for (Map.Entry<Integer, UnknownFieldSet.Field> entry : + unknownFields.asMap().entrySet()) { + String prefix = entry.getKey().toString() + ": "; + UnknownFieldSet.Field field = entry.getValue(); + + for (long value : field.getVarintList()) { + generator.print(entry.getKey().toString()); + generator.print(": "); + generator.print(unsignedToString(value)); + generator.print("\n"); + } + for (int value : field.getFixed32List()) { + generator.print(entry.getKey().toString()); + generator.print(": "); + generator.print(String.format((Locale) null, "0x%08x", value)); + generator.print("\n"); + } + for (long value : field.getFixed64List()) { + generator.print(entry.getKey().toString()); + generator.print(": "); + generator.print(String.format((Locale) null, "0x%016x", value)); + generator.print("\n"); + } + for (ByteString value : field.getLengthDelimitedList()) { + generator.print(entry.getKey().toString()); + generator.print(": \""); + generator.print(escapeBytes(value)); + generator.print("\"\n"); + } + for (UnknownFieldSet value : field.getGroupList()) { + generator.print(entry.getKey().toString()); + generator.print(" {\n"); + generator.indent(); + printUnknownFields(value, generator); + generator.outdent(); + generator.print("}\n"); + } + } + } + + /** Convert an unsigned 32-bit integer to a string. */ + private static String unsignedToString(int value) { + if (value >= 0) { + return Integer.toString(value); + } else { + return Long.toString(((long) value) & 0x00000000FFFFFFFFL); + } + } + + /** Convert an unsigned 64-bit integer to a string. */ + private static String unsignedToString(long value) { + if (value >= 0) { + return Long.toString(value); + } else { + // Pull off the most-significant bit so that BigInteger doesn't think + // the number is negative, then set it again using setBit(). + return BigInteger.valueOf(value & 0x7FFFFFFFFFFFFFFFL) + .setBit(63).toString(); + } + } + + /** + * An inner class for writing text to the output stream. + */ + static private final class TextGenerator { + + Appendable output; + boolean atStartOfLine = true; + StringBuilder indent = new StringBuilder(); + + public TextGenerator(Appendable output) { + this.output = output; + } + + /** + * Indent text by two spaces. After calling Indent(), two spaces will be + * inserted at the beginning of each line of text. Indent() may be called + * multiple times to produce deeper indents. + */ + public void indent() { + indent.append(" "); + } + + /** + * Reduces the current indent level by two spaces, or crashes if the indent + * level is zero. + */ + public void outdent() { + int length = indent.length(); + if (length == 0) { + throw new IllegalArgumentException( + " Outdent() without matching Indent()."); + } + indent.delete(length - 2, length); + } + + /** + * Print text to the output stream. + */ + public void print(CharSequence text) throws IOException { + int size = text.length(); + int pos = 0; + + for (int i = 0; i < size; i++) { + if (text.charAt(i) == '\n') { + write(text.subSequence(pos, size), i - pos + 1); + pos = i + 1; + atStartOfLine = true; + } + } + write(text.subSequence(pos, size), size - pos); + } + + private void write(CharSequence data, int size) throws IOException { + if (size == 0) { + return; + } + if (atStartOfLine) { + atStartOfLine = false; + output.append(indent); + } + output.append(data); + } + } + + // ================================================================= + // Parsing + + /** + * Represents a stream of tokens parsed from a {@code String}. + * + * <p>The Java standard library provides many classes that you might think + * would be useful for implementing this, but aren't. For example: + * + * <ul> + * <li>{@code java.io.StreamTokenizer}: This almost does what we want -- or, + * at least, something that would get us close to what we want -- except + * for one fatal flaw: It automatically un-escapes strings using Java + * escape sequences, which do not include all the escape sequences we + * need to support (e.g. '\x'). + * <li>{@code java.util.Scanner}: This seems like a great way at least to + * parse regular expressions out of a stream (so we wouldn't have to load + * the entire input into a single string before parsing). Sadly, + * {@code Scanner} requires that tokens be delimited with some delimiter. + * Thus, although the text "foo:" should parse to two tokens ("foo" and + * ":"), {@code Scanner} would recognize it only as a single token. + * Furthermore, {@code Scanner} provides no way to inspect the contents + * of delimiters, making it impossible to keep track of line and column + * numbers. + * </ul> + * + * <p>Luckily, Java's regular expression support does manage to be useful to + * us. (Barely: We need {@code Matcher.usePattern()}, which is new in + * Java 1.5.) So, we can use that, at least. Unfortunately, this implies + * that we need to have the entire input in one contiguous string. + */ + private static final class Tokenizer { + private final CharSequence text; + private final Matcher matcher; + private String currentToken; + + // The character index within this.text at which the current token begins. + private int pos = 0; + + // The line and column numbers of the current token. + private int line = 0; + private int column = 0; + + // The line and column numbers of the previous token (allows throwing + // errors *after* consuming). + private int previousLine = 0; + private int previousColumn = 0; + + private static Pattern WHITESPACE = + Pattern.compile("(\\s|(#.*$))+", Pattern.MULTILINE); + private static Pattern TOKEN = Pattern.compile( + "[a-zA-Z_][0-9a-zA-Z_+-]*|" + // an identifier + "[0-9+-][0-9a-zA-Z_.+-]*|" + // a number + "\"([^\"\n\\\\]|\\\\.)*(\"|\\\\?$)|" + // a double-quoted string + "\'([^\"\n\\\\]|\\\\.)*(\'|\\\\?$)", // a single-quoted string + Pattern.MULTILINE); + + private static Pattern DOUBLE_INFINITY = Pattern.compile( + "-?inf(inity)?", + Pattern.CASE_INSENSITIVE); + private static Pattern FLOAT_INFINITY = Pattern.compile( + "-?inf(inity)?f?", + Pattern.CASE_INSENSITIVE); + private static Pattern FLOAT_NAN = Pattern.compile( + "nanf?", + Pattern.CASE_INSENSITIVE); + + /** Construct a tokenizer that parses tokens from the given text. */ + public Tokenizer(CharSequence text) { + this.text = text; + this.matcher = WHITESPACE.matcher(text); + skipWhitespace(); + nextToken(); + } + + /** Are we at the end of the input? */ + public boolean atEnd() { + return currentToken.length() == 0; + } + + /** Advance to the next token. */ + public void nextToken() { + previousLine = line; + previousColumn = column; + + // Advance the line counter to the current position. + while (pos < matcher.regionStart()) { + if (text.charAt(pos) == '\n') { + ++line; + column = 0; + } else { + ++column; + } + ++pos; + } + + // Match the next token. + if (matcher.regionStart() == matcher.regionEnd()) { + // EOF + currentToken = ""; + } else { + matcher.usePattern(TOKEN); + if (matcher.lookingAt()) { + currentToken = matcher.group(); + matcher.region(matcher.end(), matcher.regionEnd()); + } else { + // Take one character. + currentToken = String.valueOf(text.charAt(pos)); + matcher.region(pos + 1, matcher.regionEnd()); + } + + skipWhitespace(); + } + } + + /** + * Skip over any whitespace so that the matcher region starts at the next + * token. + */ + private void skipWhitespace() { + matcher.usePattern(WHITESPACE); + if (matcher.lookingAt()) { + matcher.region(matcher.end(), matcher.regionEnd()); + } + } + + /** + * If the next token exactly matches {@code token}, consume it and return + * {@code true}. Otherwise, return {@code false} without doing anything. + */ + public boolean tryConsume(String token) { + if (currentToken.equals(token)) { + nextToken(); + return true; + } else { + return false; + } + } + + /** + * If the next token exactly matches {@code token}, consume it. Otherwise, + * throw a {@link ParseException}. + */ + public void consume(String token) throws ParseException { + if (!tryConsume(token)) { + throw parseException("Expected \"" + token + "\"."); + } + } + + /** + * Returns {@code true} if the next token is an integer, but does + * not consume it. + */ + public boolean lookingAtInteger() { + if (currentToken.length() == 0) { + return false; + } + + char c = currentToken.charAt(0); + return ('0' <= c && c <= '9') || + c == '-' || c == '+'; + } + + /** + * If the next token is an identifier, consume it and return its value. + * Otherwise, throw a {@link ParseException}. + */ + public String consumeIdentifier() throws ParseException { + for (int i = 0; i < currentToken.length(); i++) { + char c = currentToken.charAt(i); + if (('a' <= c && c <= 'z') || + ('A' <= c && c <= 'Z') || + ('0' <= c && c <= '9') || + (c == '_') || (c == '.')) { + // OK + } else { + throw parseException("Expected identifier."); + } + } + + String result = currentToken; + nextToken(); + return result; + } + + /** + * If the next token is a 32-bit signed integer, consume it and return its + * value. Otherwise, throw a {@link ParseException}. + */ + public int consumeInt32() throws ParseException { + try { + int result = parseInt32(currentToken); + nextToken(); + return result; + } catch (NumberFormatException e) { + throw integerParseException(e); + } + } + + /** + * If the next token is a 32-bit unsigned integer, consume it and return its + * value. Otherwise, throw a {@link ParseException}. + */ + public int consumeUInt32() throws ParseException { + try { + int result = parseUInt32(currentToken); + nextToken(); + return result; + } catch (NumberFormatException e) { + throw integerParseException(e); + } + } + + /** + * If the next token is a 64-bit signed integer, consume it and return its + * value. Otherwise, throw a {@link ParseException}. + */ + public long consumeInt64() throws ParseException { + try { + long result = parseInt64(currentToken); + nextToken(); + return result; + } catch (NumberFormatException e) { + throw integerParseException(e); + } + } + + /** + * If the next token is a 64-bit unsigned integer, consume it and return its + * value. Otherwise, throw a {@link ParseException}. + */ + public long consumeUInt64() throws ParseException { + try { + long result = parseUInt64(currentToken); + nextToken(); + return result; + } catch (NumberFormatException e) { + throw integerParseException(e); + } + } + + /** + * If the next token is a double, consume it and return its value. + * Otherwise, throw a {@link ParseException}. + */ + public double consumeDouble() throws ParseException { + // We need to parse infinity and nan separately because + // Double.parseDouble() does not accept "inf", "infinity", or "nan". + if (DOUBLE_INFINITY.matcher(currentToken).matches()) { + boolean negative = currentToken.startsWith("-"); + nextToken(); + return negative ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY; + } + if (currentToken.equalsIgnoreCase("nan")) { + nextToken(); + return Double.NaN; + } + try { + double result = Double.parseDouble(currentToken); + nextToken(); + return result; + } catch (NumberFormatException e) { + throw floatParseException(e); + } + } + + /** + * If the next token is a float, consume it and return its value. + * Otherwise, throw a {@link ParseException}. + */ + public float consumeFloat() throws ParseException { + // We need to parse infinity and nan separately because + // Float.parseFloat() does not accept "inf", "infinity", or "nan". + if (FLOAT_INFINITY.matcher(currentToken).matches()) { + boolean negative = currentToken.startsWith("-"); + nextToken(); + return negative ? Float.NEGATIVE_INFINITY : Float.POSITIVE_INFINITY; + } + if (FLOAT_NAN.matcher(currentToken).matches()) { + nextToken(); + return Float.NaN; + } + try { + float result = Float.parseFloat(currentToken); + nextToken(); + return result; + } catch (NumberFormatException e) { + throw floatParseException(e); + } + } + + /** + * If the next token is a boolean, consume it and return its value. + * Otherwise, throw a {@link ParseException}. + */ + public boolean consumeBoolean() throws ParseException { + if (currentToken.equals("true")) { + nextToken(); + return true; + } else if (currentToken.equals("false")) { + nextToken(); + return false; + } else { + throw parseException("Expected \"true\" or \"false\"."); + } + } + + /** + * If the next token is a string, consume it and return its (unescaped) + * value. Otherwise, throw a {@link ParseException}. + */ + public String consumeString() throws ParseException { + return consumeByteString().toStringUtf8(); + } + + /** + * If the next token is a string, consume it, unescape it as a + * {@link ByteString}, and return it. Otherwise, throw a + * {@link ParseException}. + */ + public ByteString consumeByteString() throws ParseException { + char quote = currentToken.length() > 0 ? currentToken.charAt(0) : '\0'; + if (quote != '\"' && quote != '\'') { + throw parseException("Expected string."); + } + + if (currentToken.length() < 2 || + currentToken.charAt(currentToken.length() - 1) != quote) { + throw parseException("String missing ending quote."); + } + + try { + String escaped = currentToken.substring(1, currentToken.length() - 1); + ByteString result = unescapeBytes(escaped); + nextToken(); + return result; + } catch (InvalidEscapeSequence e) { + throw parseException(e.getMessage()); + } + } + + /** + * Returns a {@link ParseException} with the current line and column + * numbers in the description, suitable for throwing. + */ + public ParseException parseException(String description) { + // Note: People generally prefer one-based line and column numbers. + return new ParseException( + (line + 1) + ":" + (column + 1) + ": " + description); + } + + /** + * Returns a {@link ParseException} with the line and column numbers of + * the previous token in the description, suitable for throwing. + */ + public ParseException parseExceptionPreviousToken(String description) { + // Note: People generally prefer one-based line and column numbers. + return new ParseException( + (previousLine + 1) + ":" + (previousColumn + 1) + ": " + description); + } + + /** + * Constructs an appropriate {@link ParseException} for the given + * {@code NumberFormatException} when trying to parse an integer. + */ + private ParseException integerParseException(NumberFormatException e) { + return parseException("Couldn't parse integer: " + e.getMessage()); + } + + /** + * Constructs an appropriate {@link ParseException} for the given + * {@code NumberFormatException} when trying to parse a float or double. + */ + private ParseException floatParseException(NumberFormatException e) { + return parseException("Couldn't parse number: " + e.getMessage()); + } + } + + /** Thrown when parsing an invalid text format message. */ + public static class ParseException extends IOException { + public ParseException(String message) { + super(message); + } + } + + /** + * Parse a text-format message from {@code input} and merge the contents + * into {@code builder}. + */ + public static void merge(Readable input, + Message.Builder builder) + throws ParseException, IOException { + merge(input, ExtensionRegistry.getEmptyRegistry(), builder); + } + + /** + * Parse a text-format message from {@code input} and merge the contents + * into {@code builder}. + */ + public static void merge(CharSequence input, + Message.Builder builder) + throws ParseException { + merge(input, ExtensionRegistry.getEmptyRegistry(), builder); + } + + /** + * Parse a text-format message from {@code input} and merge the contents + * into {@code builder}. Extensions will be recognized if they are + * registered in {@code extensionRegistry}. + */ + public static void merge(Readable input, + ExtensionRegistry extensionRegistry, + Message.Builder builder) + throws ParseException, IOException { + // Read the entire input to a String then parse that. + + // If StreamTokenizer were not quite so crippled, or if there were a kind + // of Reader that could read in chunks that match some particular regex, + // or if we wanted to write a custom Reader to tokenize our stream, then + // we would not have to read to one big String. Alas, none of these is + // the case. Oh well. + + merge(toStringBuilder(input), extensionRegistry, builder); + } + + private static final int BUFFER_SIZE = 4096; + + // TODO(chrisn): See if working around java.io.Reader#read(CharBuffer) + // overhead is worthwhile + private static StringBuilder toStringBuilder(Readable input) + throws IOException { + StringBuilder text = new StringBuilder(); + CharBuffer buffer = CharBuffer.allocate(BUFFER_SIZE); + while (true) { + int n = input.read(buffer); + if (n == -1) { + break; + } + buffer.flip(); + text.append(buffer, 0, n); + } + return text; + } + + /** + * Parse a text-format message from {@code input} and merge the contents + * into {@code builder}. Extensions will be recognized if they are + * registered in {@code extensionRegistry}. + */ + public static void merge(CharSequence input, + ExtensionRegistry extensionRegistry, + Message.Builder builder) + throws ParseException { + Tokenizer tokenizer = new Tokenizer(input); + + while (!tokenizer.atEnd()) { + mergeField(tokenizer, extensionRegistry, builder); + } + } + + /** + * Parse a single field from {@code tokenizer} and merge it into + * {@code builder}. + */ + private static void mergeField(Tokenizer tokenizer, + ExtensionRegistry extensionRegistry, + Message.Builder builder) + throws ParseException { + FieldDescriptor field; + Descriptor type = builder.getDescriptorForType(); + ExtensionRegistry.ExtensionInfo extension = null; + + if (tokenizer.tryConsume("[")) { + // An extension. + StringBuilder name = new StringBuilder(tokenizer.consumeIdentifier()); + while (tokenizer.tryConsume(".")) { + name.append("."); + name.append(tokenizer.consumeIdentifier()); + } + + extension = extensionRegistry.findExtensionByName(name.toString()); + + if (extension == null) { + throw tokenizer.parseExceptionPreviousToken( + "Extension \"" + name + "\" not found in the ExtensionRegistry."); + } else if (extension.descriptor.getContainingType() != type) { + throw tokenizer.parseExceptionPreviousToken( + "Extension \"" + name + "\" does not extend message type \"" + + type.getFullName() + "\"."); + } + + tokenizer.consume("]"); + + field = extension.descriptor; + } else { + String name = tokenizer.consumeIdentifier(); + field = type.findFieldByName(name); + + // Group names are expected to be capitalized as they appear in the + // .proto file, which actually matches their type names, not their field + // names. + if (field == null) { + // Explicitly specify US locale so that this code does not break when + // executing in Turkey. + String lowerName = name.toLowerCase(Locale.US); + field = type.findFieldByName(lowerName); + // If the case-insensitive match worked but the field is NOT a group, + if (field != null && field.getType() != FieldDescriptor.Type.GROUP) { + field = null; + } + } + // Again, special-case group names as described above. + if (field != null && field.getType() == FieldDescriptor.Type.GROUP && + !field.getMessageType().getName().equals(name)) { + field = null; + } + + if (field == null) { + throw tokenizer.parseExceptionPreviousToken( + "Message type \"" + type.getFullName() + + "\" has no field named \"" + name + "\"."); + } + } + + Object value = null; + + if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + tokenizer.tryConsume(":"); // optional + + String endToken; + if (tokenizer.tryConsume("<")) { + endToken = ">"; + } else { + tokenizer.consume("{"); + endToken = "}"; + } + + Message.Builder subBuilder; + if (extension == null) { + subBuilder = builder.newBuilderForField(field); + } else { + subBuilder = extension.defaultInstance.newBuilderForType(); + } + + while (!tokenizer.tryConsume(endToken)) { + if (tokenizer.atEnd()) { + throw tokenizer.parseException( + "Expected \"" + endToken + "\"."); + } + mergeField(tokenizer, extensionRegistry, subBuilder); + } + + value = subBuilder.build(); + + } else { + tokenizer.consume(":"); + + switch (field.getType()) { + case INT32: + case SINT32: + case SFIXED32: + value = tokenizer.consumeInt32(); + break; + + case INT64: + case SINT64: + case SFIXED64: + value = tokenizer.consumeInt64(); + break; + + case UINT32: + case FIXED32: + value = tokenizer.consumeUInt32(); + break; + + case UINT64: + case FIXED64: + value = tokenizer.consumeUInt64(); + break; + + case FLOAT: + value = tokenizer.consumeFloat(); + break; + + case DOUBLE: + value = tokenizer.consumeDouble(); + break; + + case BOOL: + value = tokenizer.consumeBoolean(); + break; + + case STRING: + value = tokenizer.consumeString(); + break; + + case BYTES: + value = tokenizer.consumeByteString(); + break; + + case ENUM: { + EnumDescriptor enumType = field.getEnumType(); + + if (tokenizer.lookingAtInteger()) { + int number = tokenizer.consumeInt32(); + value = enumType.findValueByNumber(number); + if (value == null) { + throw tokenizer.parseExceptionPreviousToken( + "Enum type \"" + enumType.getFullName() + + "\" has no value with number " + number + "."); + } + } else { + String id = tokenizer.consumeIdentifier(); + value = enumType.findValueByName(id); + if (value == null) { + throw tokenizer.parseExceptionPreviousToken( + "Enum type \"" + enumType.getFullName() + + "\" has no value named \"" + id + "\"."); + } + } + + break; + } + + case MESSAGE: + case GROUP: + throw new RuntimeException("Can't get here."); + } + } + + if (field.isRepeated()) { + builder.addRepeatedField(field, value); + } else { + builder.setField(field, value); + } + } + + // ================================================================= + // Utility functions + // + // Some of these methods are package-private because Descriptors.java uses + // them. + + /** + * Escapes bytes in the format used in protocol buffer text format, which + * is the same as the format used for C string literals. All bytes + * that are not printable 7-bit ASCII characters are escaped, as well as + * backslash, single-quote, and double-quote characters. Characters for + * which no defined short-hand escape sequence is defined will be escaped + * using 3-digit octal sequences. + */ + static String escapeBytes(ByteString input) { + StringBuilder builder = new StringBuilder(input.size()); + for (int i = 0; i < input.size(); i++) { + byte b = input.byteAt(i); + switch (b) { + // Java does not recognize \a or \v, apparently. + case 0x07: builder.append("\\a" ); break; + case '\b': builder.append("\\b" ); break; + case '\f': builder.append("\\f" ); break; + case '\n': builder.append("\\n" ); break; + case '\r': builder.append("\\r" ); break; + case '\t': builder.append("\\t" ); break; + case 0x0b: builder.append("\\v" ); break; + case '\\': builder.append("\\\\"); break; + case '\'': builder.append("\\\'"); break; + case '"' : builder.append("\\\""); break; + default: + if (b >= 0x20) { + builder.append((char) b); + } else { + builder.append('\\'); + builder.append((char) ('0' + ((b >>> 6) & 3))); + builder.append((char) ('0' + ((b >>> 3) & 7))); + builder.append((char) ('0' + (b & 7))); + } + break; + } + } + return builder.toString(); + } + + /** + * Un-escape a byte sequence as escaped using + * {@link #escapeBytes(ByteString)}. Two-digit hex escapes (starting with + * "\x") are also recognized. + */ + static ByteString unescapeBytes(CharSequence input) + throws InvalidEscapeSequence { + byte[] result = new byte[input.length()]; + int pos = 0; + for (int i = 0; i < input.length(); i++) { + char c = input.charAt(i); + if (c == '\\') { + if (i + 1 < input.length()) { + ++i; + c = input.charAt(i); + if (isOctal(c)) { + // Octal escape. + int code = digitValue(c); + if (i + 1 < input.length() && isOctal(input.charAt(i + 1))) { + ++i; + code = code * 8 + digitValue(input.charAt(i)); + } + if (i + 1 < input.length() && isOctal(input.charAt(i + 1))) { + ++i; + code = code * 8 + digitValue(input.charAt(i)); + } + result[pos++] = (byte)code; + } else { + switch (c) { + case 'a' : result[pos++] = 0x07; break; + case 'b' : result[pos++] = '\b'; break; + case 'f' : result[pos++] = '\f'; break; + case 'n' : result[pos++] = '\n'; break; + case 'r' : result[pos++] = '\r'; break; + case 't' : result[pos++] = '\t'; break; + case 'v' : result[pos++] = 0x0b; break; + case '\\': result[pos++] = '\\'; break; + case '\'': result[pos++] = '\''; break; + case '"' : result[pos++] = '\"'; break; + + case 'x': + // hex escape + int code = 0; + if (i + 1 < input.length() && isHex(input.charAt(i + 1))) { + ++i; + code = digitValue(input.charAt(i)); + } else { + throw new InvalidEscapeSequence( + "Invalid escape sequence: '\\x' with no digits"); + } + if (i + 1 < input.length() && isHex(input.charAt(i + 1))) { + ++i; + code = code * 16 + digitValue(input.charAt(i)); + } + result[pos++] = (byte)code; + break; + + default: + throw new InvalidEscapeSequence( + "Invalid escape sequence: '\\" + c + "'"); + } + } + } else { + throw new InvalidEscapeSequence( + "Invalid escape sequence: '\\' at end of string."); + } + } else { + result[pos++] = (byte)c; + } + } + + return ByteString.copyFrom(result, 0, pos); + } + + /** + * Thrown by {@link TextFormat#unescapeBytes} and + * {@link TextFormat#unescapeText} when an invalid escape sequence is seen. + */ + static class InvalidEscapeSequence extends IOException { + public InvalidEscapeSequence(String description) { + super(description); + } + } + + /** + * Like {@link #escapeBytes(ByteString)}, but escapes a text string. + * Non-ASCII characters are first encoded as UTF-8, then each byte is escaped + * individually as a 3-digit octal escape. Yes, it's weird. + */ + static String escapeText(String input) { + return escapeBytes(ByteString.copyFromUtf8(input)); + } + + /** + * Un-escape a text string as escaped using {@link #escapeText(String)}. + * Two-digit hex escapes (starting with "\x") are also recognized. + */ + static String unescapeText(String input) throws InvalidEscapeSequence { + return unescapeBytes(input).toStringUtf8(); + } + + /** Is this an octal digit? */ + private static boolean isOctal(char c) { + return '0' <= c && c <= '7'; + } + + /** Is this a hex digit? */ + private static boolean isHex(char c) { + return ('0' <= c && c <= '9') || + ('a' <= c && c <= 'f') || + ('A' <= c && c <= 'F'); + } + + /** + * Interpret a character as a digit (in any base up to 36) and return the + * numeric value. This is like {@code Character.digit()} but we don't accept + * non-ASCII digits. + */ + private static int digitValue(char c) { + if ('0' <= c && c <= '9') { + return c - '0'; + } else if ('a' <= c && c <= 'z') { + return c - 'a' + 10; + } else { + return c - 'A' + 10; + } + } + + /** + * Parse a 32-bit signed integer from the text. Unlike the Java standard + * {@code Integer.parseInt()}, this function recognizes the prefixes "0x" + * and "0" to signify hexidecimal and octal numbers, respectively. + */ + static int parseInt32(String text) throws NumberFormatException { + return (int) parseInteger(text, true, false); + } + + /** + * Parse a 32-bit unsigned integer from the text. Unlike the Java standard + * {@code Integer.parseInt()}, this function recognizes the prefixes "0x" + * and "0" to signify hexidecimal and octal numbers, respectively. The + * result is coerced to a (signed) {@code int} when returned since Java has + * no unsigned integer type. + */ + static int parseUInt32(String text) throws NumberFormatException { + return (int) parseInteger(text, false, false); + } + + /** + * Parse a 64-bit signed integer from the text. Unlike the Java standard + * {@code Integer.parseInt()}, this function recognizes the prefixes "0x" + * and "0" to signify hexidecimal and octal numbers, respectively. + */ + static long parseInt64(String text) throws NumberFormatException { + return parseInteger(text, true, true); + } + + /** + * Parse a 64-bit unsigned integer from the text. Unlike the Java standard + * {@code Integer.parseInt()}, this function recognizes the prefixes "0x" + * and "0" to signify hexidecimal and octal numbers, respectively. The + * result is coerced to a (signed) {@code long} when returned since Java has + * no unsigned long type. + */ + static long parseUInt64(String text) throws NumberFormatException { + return parseInteger(text, false, true); + } + + private static long parseInteger(String text, + boolean isSigned, + boolean isLong) + throws NumberFormatException { + int pos = 0; + + boolean negative = false; + if (text.startsWith("-", pos)) { + if (!isSigned) { + throw new NumberFormatException("Number must be positive: " + text); + } + ++pos; + negative = true; + } + + int radix = 10; + if (text.startsWith("0x", pos)) { + pos += 2; + radix = 16; + } else if (text.startsWith("0", pos)) { + radix = 8; + } + + String numberText = text.substring(pos); + + long result = 0; + if (numberText.length() < 16) { + // Can safely assume no overflow. + result = Long.parseLong(numberText, radix); + if (negative) { + result = -result; + } + + // Check bounds. + // No need to check for 64-bit numbers since they'd have to be 16 chars + // or longer to overflow. + if (!isLong) { + if (isSigned) { + if (result > Integer.MAX_VALUE || result < Integer.MIN_VALUE) { + throw new NumberFormatException( + "Number out of range for 32-bit signed integer: " + text); + } + } else { + if (result >= (1L << 32) || result < 0) { + throw new NumberFormatException( + "Number out of range for 32-bit unsigned integer: " + text); + } + } + } + } else { + BigInteger bigValue = new BigInteger(numberText, radix); + if (negative) { + bigValue = bigValue.negate(); + } + + // Check bounds. + if (!isLong) { + if (isSigned) { + if (bigValue.bitLength() > 31) { + throw new NumberFormatException( + "Number out of range for 32-bit signed integer: " + text); + } + } else { + if (bigValue.bitLength() > 32) { + throw new NumberFormatException( + "Number out of range for 32-bit unsigned integer: " + text); + } + } + } else { + if (isSigned) { + if (bigValue.bitLength() > 63) { + throw new NumberFormatException( + "Number out of range for 64-bit signed integer: " + text); + } + } else { + if (bigValue.bitLength() > 64) { + throw new NumberFormatException( + "Number out of range for 64-bit unsigned integer: " + text); + } + } + } + + result = bigValue.longValue(); + } + + return result; + } +} diff --git a/java/src/main/java/com/google/protobuf/UninitializedMessageException.java b/java/src/main/java/com/google/protobuf/UninitializedMessageException.java new file mode 100644 index 00000000..61c3e248 --- /dev/null +++ b/java/src/main/java/com/google/protobuf/UninitializedMessageException.java @@ -0,0 +1,146 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.protobuf; + +import com.google.protobuf.Descriptors.FieldDescriptor; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * Thrown when attempting to build a protocol message that is missing required + * fields. This is a {@code RuntimeException} because it normally represents + * a programming error: it happens when some code which constructs a message + * fails to set all the fields. {@code parseFrom()} methods <b>do not</b> + * throw this; they throw an {@link InvalidProtocolBufferException} if + * required fields are missing, because it is not a programming error to + * receive an incomplete message. In other words, + * {@code UninitializedMessageException} should never be thrown by correct + * code, but {@code InvalidProtocolBufferException} might be. + * + * @author kenton@google.com Kenton Varda + */ +public class UninitializedMessageException extends RuntimeException { + public UninitializedMessageException(Message message) { + this(findMissingFields(message)); + } + + private UninitializedMessageException(List<String> missingFields) { + super(buildDescription(missingFields)); + this.missingFields = missingFields; + } + + private final List<String> missingFields; + + /** + * Get a list of human-readable names of required fields missing from this + * message. Each name is a full path to a field, e.g. "foo.bar[5].baz". + */ + public List<String> getMissingFields() { + return Collections.unmodifiableList(missingFields); + } + + /** + * Converts this exception to an {@link InvalidProtocolBufferException}. + * When a parsed message is missing required fields, this should be thrown + * instead of {@code UninitializedMessageException}. + */ + public InvalidProtocolBufferException asInvalidProtocolBufferException() { + return new InvalidProtocolBufferException(getMessage()); + } + + /** Construct the description string for this exception. */ + private static String buildDescription(List<String> missingFields) { + StringBuilder description = + new StringBuilder("Message missing required fields: "); + boolean first = true; + for (String field : missingFields) { + if (first) { + first = false; + } else { + description.append(", "); + } + description.append(field); + } + return description.toString(); + } + + /** + * Populates {@code this.missingFields} with the full "path" of each + * missing required field in the given message. + */ + private static List<String> findMissingFields(Message message) { + List<String> results = new ArrayList<String>(); + findMissingFields(message, "", results); + return results; + } + + /** Recursive helper implementing {@link #findMissingFields(Message)}. */ + private static void findMissingFields(Message message, String prefix, + List<String> results) { + for (FieldDescriptor field : message.getDescriptorForType().getFields()) { + if (field.isRequired() && !message.hasField(field)) { + results.add(prefix + field.getName()); + } + } + + for (Map.Entry<FieldDescriptor, Object> entry : + message.getAllFields().entrySet()) { + FieldDescriptor field = entry.getKey(); + Object value = entry.getValue(); + + if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + if (field.isRepeated()) { + int i = 0; + for (Object element : (List) value) { + findMissingFields((Message) element, + subMessagePrefix(prefix, field, i++), + results); + } + } else { + if (message.hasField(field)) { + findMissingFields((Message) value, + subMessagePrefix(prefix, field, -1), + results); + } + } + } + } + } + + private static String subMessagePrefix(String prefix, + FieldDescriptor field, + int index) { + StringBuilder result = new StringBuilder(prefix); + if (field.isExtension()) { + result.append('(') + .append(field.getFullName()) + .append(')'); + } else { + result.append(field.getName()); + } + if (index != -1) { + result.append('[') + .append(index) + .append(']'); + } + result.append('.'); + return result.toString(); + } +} diff --git a/java/src/main/java/com/google/protobuf/UnknownFieldSet.java b/java/src/main/java/com/google/protobuf/UnknownFieldSet.java new file mode 100644 index 00000000..ba4dd5ad --- /dev/null +++ b/java/src/main/java/com/google/protobuf/UnknownFieldSet.java @@ -0,0 +1,746 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.protobuf; + +import java.io.InputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.TreeMap; +import java.util.List; +import java.util.Map; + +/** + * {@code UnknownFieldSet} is used to keep track of fields which were seen when + * parsing a protocol message but whose field numbers or types are unrecognized. + * This most frequently occurs when new fields are added to a message type + * and then messages containing those feilds are read by old software that was + * compiled before the new types were added. + * + * <p>Every {@link Message} contains an {@code UnknownFieldSet} (and every + * {@link Message.Builder} contains an {@link UnknownFieldSet.Builder}). + * + * <p>Most users will never need to use this class. + * + * @author kenton@google.com Kenton Varda + */ +public final class UnknownFieldSet { + private UnknownFieldSet() {} + + /** Create a new {@link UnknownFieldSet.Builder}. */ + public static Builder newBuilder() { + return new Builder(); + } + + /** + * Create a new {@link UnknownFieldSet.Builder} and initialize it to be a copy + * of {@code copyFrom}. + */ + public static Builder newBuilder(UnknownFieldSet copyFrom) { + return new Builder().mergeFrom(copyFrom); + } + + /** Get an empty {@code UnknownFieldSet}. */ + public static UnknownFieldSet getDefaultInstance() { + return defaultInstance; + } + private static UnknownFieldSet defaultInstance = + new UnknownFieldSet(Collections.<Integer, Field>emptyMap()); + + /** + * Construct an {@code UnknownFieldSet} around the given map. The map is + * expected to be immutable. + */ + private UnknownFieldSet(Map<Integer, Field> fields) { + this.fields = fields; + } + private Map<Integer, Field> fields; + + /** Get a map of fields in the set by number. */ + public Map<Integer, Field> asMap() { + return fields; + } + + /** Check if the given field number is present in the set. */ + public boolean hasField(int number) { + return fields.containsKey(number); + } + + /** + * Get a field by number. Returns an empty field if not present. Never + * returns {@code null}. + */ + public Field getField(int number) { + Field result = fields.get(number); + return (result == null) ? Field.getDefaultInstance() : result; + } + + /** Serializes the set and writes it to {@code output}. */ + public void writeTo(CodedOutputStream output) throws IOException { + for (Map.Entry<Integer, Field> entry : fields.entrySet()) { + entry.getValue().writeTo(entry.getKey(), output); + } + } + + /** + * Converts the set to a string in protocol buffer text format. This is + * just a trivial wrapper around + * {@link TextFormat#printToString(UnknownFieldSet)}. + */ + public final String toString() { + return TextFormat.printToString(this); + } + + /** + * Serializes the message to a {@code ByteString} and returns it. This is + * just a trivial wrapper around {@link #writeTo(CodedOutputStream)}. + */ + public final ByteString toByteString() { + try { + ByteString.CodedBuilder out = + ByteString.newCodedBuilder(getSerializedSize()); + writeTo(out.getCodedOutput()); + return out.build(); + } catch (IOException e) { + throw new RuntimeException( + "Serializing to a ByteString threw an IOException (should " + + "never happen).", e); + } + } + + /** + * Serializes the message to a {@code byte} array and returns it. This is + * just a trivial wrapper around {@link #writeTo(CodedOutputStream)}. + */ + public final byte[] toByteArray() { + try { + byte[] result = new byte[getSerializedSize()]; + CodedOutputStream output = CodedOutputStream.newInstance(result); + writeTo(output); + output.checkNoSpaceLeft(); + return result; + } catch (IOException e) { + throw new RuntimeException( + "Serializing to a byte array threw an IOException " + + "(should never happen).", e); + } + } + + /** + * Serializes the message and writes it to {@code output}. This is just a + * trivial wrapper around {@link #writeTo(CodedOutputStream)}. + */ + public final void writeTo(OutputStream output) throws IOException { + CodedOutputStream codedOutput = CodedOutputStream.newInstance(output); + writeTo(codedOutput); + codedOutput.flush(); + } + + /** Get the number of bytes required to encode this set. */ + public int getSerializedSize() { + int result = 0; + for (Map.Entry<Integer, Field> entry : fields.entrySet()) { + result += entry.getValue().getSerializedSize(entry.getKey()); + } + return result; + } + + /** + * Serializes the set and writes it to {@code output} using + * {@code MessageSet} wire format. + */ + public void writeAsMessageSetTo(CodedOutputStream output) + throws IOException { + for (Map.Entry<Integer, Field> entry : fields.entrySet()) { + entry.getValue().writeAsMessageSetExtensionTo( + entry.getKey(), output); + } + } + + /** + * Get the number of bytes required to encode this set using + * {@code MessageSet} wire format. + */ + public int getSerializedSizeAsMessageSet() { + int result = 0; + for (Map.Entry<Integer, Field> entry : fields.entrySet()) { + result += entry.getValue().getSerializedSizeAsMessageSetExtension( + entry.getKey()); + } + return result; + } + + /** Parse an {@code UnknownFieldSet} from the given input stream. */ + static public UnknownFieldSet parseFrom(CodedInputStream input) + throws IOException { + return newBuilder().mergeFrom(input).build(); + } + + /** Parse {@code data} as an {@code UnknownFieldSet} and return it. */ + public static UnknownFieldSet parseFrom(ByteString data) + throws InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).build(); + } + + /** Parse {@code data} as an {@code UnknownFieldSet} and return it. */ + public static UnknownFieldSet parseFrom(byte[] data) + throws InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).build(); + } + + /** Parse an {@code UnknownFieldSet} from {@code input} and return it. */ + public static UnknownFieldSet parseFrom(InputStream input) + throws IOException { + return newBuilder().mergeFrom(input).build(); + } + + /** + * Builder for {@link UnknownFieldSet}s. + * + * <p>Note that this class maintains {@link Field.Builder}s for all fields + * in the set. Thus, adding one element to an existing {@link Field} does not + * require making a copy. This is important for efficient parsing of + * unknown repeated fields. However, it implies that {@link Field}s cannot + * be constructed independently, nor can two {@link UnknownFieldSet}s share + * the same {@code Field} object. + * + * <p>Use {@link UnknownFieldSet#newBuilder()} to construct a {@code Builder}. + */ + public static final class Builder { + private Builder() {} + private Map<Integer, Field> fields = new TreeMap<Integer, Field>(); + + // Optimization: We keep around a builder for the last field that was + // modified so that we can efficiently add to it multiple times in a + // row (important when parsing an unknown repeated field). + int lastFieldNumber = 0; + Field.Builder lastField = null; + + /** + * Get a field builder for the given field number which includes any + * values that already exist. + */ + private Field.Builder getFieldBuilder(int number) { + if (lastField != null) { + if (number == lastFieldNumber) { + return lastField; + } + // Note: addField() will reset lastField and lastFieldNumber. + addField(lastFieldNumber, lastField.build()); + } + if (number == 0) { + return null; + } else { + Field existing = fields.get(number); + lastFieldNumber = number; + lastField = Field.newBuilder(); + if (existing != null) { + lastField.mergeFrom(existing); + } + return lastField; + } + } + + /** + * Build the {@link UnknownFieldSet} and return it. + * + * <p>Once {@code build()} has been called, the {@code Builder} will no + * longer be usable. Calling any method after {@code build()} will throw + * {@code NullPointerException}. + */ + public UnknownFieldSet build() { + getFieldBuilder(0); // Force lastField to be built. + UnknownFieldSet result; + if (fields.isEmpty()) { + result = getDefaultInstance(); + } else { + result = new UnknownFieldSet(Collections.unmodifiableMap(fields)); + } + fields = null; + return result; + } + + /** Reset the builder to an empty set. */ + public Builder clear() { + fields = new TreeMap<Integer, Field>(); + lastFieldNumber = 0; + lastField = null; + return this; + } + + /** + * Merge the fields from {@code other} into this set. If a field number + * exists in both sets, {@code other}'s values for that field will be + * appended to the values in this set. + */ + public Builder mergeFrom(UnknownFieldSet other) { + if (other != getDefaultInstance()) { + for (Map.Entry<Integer, Field> entry : other.fields.entrySet()) { + mergeField(entry.getKey(), entry.getValue()); + } + } + return this; + } + + /** + * Add a field to the {@code UnknownFieldSet}. If a field with the same + * number already exists, the two are merged. + */ + public Builder mergeField(int number, Field field) { + if (number == 0) { + throw new IllegalArgumentException("Zero is not a valid field number."); + } + if (hasField(number)) { + getFieldBuilder(number).mergeFrom(field); + } else { + // Optimization: We could call getFieldBuilder(number).mergeFrom(field) + // in this case, but that would create a copy of the Field object. + // We'd rather reuse the one passed to us, so call addField() instead. + addField(number, field); + } + return this; + } + + /** + * Convenience method for merging a new field containing a single varint + * value. This is used in particular when an unknown enum value is + * encountered. + */ + public Builder mergeVarintField(int number, int value) { + if (number == 0) { + throw new IllegalArgumentException("Zero is not a valid field number."); + } + getFieldBuilder(number).addVarint(value); + return this; + } + + /** Check if the given field number is present in the set. */ + public boolean hasField(int number) { + if (number == 0) { + throw new IllegalArgumentException("Zero is not a valid field number."); + } + return number == lastFieldNumber || fields.containsKey(number); + } + + /** + * Add a field to the {@code UnknownFieldSet}. If a field with the same + * number already exists, it is removed. + */ + public Builder addField(int number, Field field) { + if (number == 0) { + throw new IllegalArgumentException("Zero is not a valid field number."); + } + if (lastField != null && lastFieldNumber == number) { + // Discard this. + lastField = null; + lastFieldNumber = 0; + } + fields.put(number, field); + return this; + } + + /** + * Get all present {@code Field}s as an immutable {@code Map}. If more + * fields are added, the changes may or may not be reflected in this map. + */ + public Map<Integer, Field> asMap() { + getFieldBuilder(0); // Force lastField to be built. + return Collections.unmodifiableMap(fields); + } + + /** + * Parse an entire message from {@code input} and merge its fields into + * this set. + */ + public Builder mergeFrom(CodedInputStream input) throws IOException { + while (true) { + int tag = input.readTag(); + if (tag == 0 || !mergeFieldFrom(tag, input)) { + break; + } + } + return this; + } + + /** + * Parse a single field from {@code input} and merge it into this set. + * @param tag The field's tag number, which was already parsed. + * @return {@code false} if the tag is an engroup tag. + */ + public boolean mergeFieldFrom(int tag, CodedInputStream input) + throws IOException { + int number = WireFormat.getTagFieldNumber(tag); + switch (WireFormat.getTagWireType(tag)) { + case WireFormat.WIRETYPE_VARINT: + getFieldBuilder(number).addVarint(input.readInt64()); + return true; + case WireFormat.WIRETYPE_FIXED64: + getFieldBuilder(number).addFixed64(input.readFixed64()); + return true; + case WireFormat.WIRETYPE_LENGTH_DELIMITED: + getFieldBuilder(number).addLengthDelimited(input.readBytes()); + return true; + case WireFormat.WIRETYPE_START_GROUP: { + UnknownFieldSet.Builder subBuilder = UnknownFieldSet.newBuilder(); + input.readUnknownGroup(number, subBuilder); + getFieldBuilder(number).addGroup(subBuilder.build()); + return true; + } + case WireFormat.WIRETYPE_END_GROUP: + return false; + case WireFormat.WIRETYPE_FIXED32: + getFieldBuilder(number).addFixed32(input.readFixed32()); + return true; + default: + throw InvalidProtocolBufferException.invalidWireType(); + } + } + + /** + * Parse {@code data} as an {@code UnknownFieldSet} and merge it with the + * set being built. This is just a small wrapper around + * {@link #mergeFrom(CodedInputStream)}. + */ + public Builder mergeFrom(ByteString data) + throws InvalidProtocolBufferException { + try { + CodedInputStream input = data.newCodedInput(); + mergeFrom(input); + input.checkLastTagWas(0); + return this; + } catch (InvalidProtocolBufferException e) { + throw e; + } catch (IOException e) { + throw new RuntimeException( + "Reading from a ByteString threw an IOException (should " + + "never happen).", e); + } + } + + /** + * Parse {@code data} as an {@code UnknownFieldSet} and merge it with the + * set being built. This is just a small wrapper around + * {@link #mergeFrom(CodedInputStream)}. + */ + public Builder mergeFrom(byte[] data) + throws InvalidProtocolBufferException { + try { + CodedInputStream input = CodedInputStream.newInstance(data); + mergeFrom(input); + input.checkLastTagWas(0); + return this; + } catch (InvalidProtocolBufferException e) { + throw e; + } catch (IOException e) { + throw new RuntimeException( + "Reading from a byte array threw an IOException (should " + + "never happen).", e); + } + } + + /** + * Parse an {@code UnknownFieldSet} from {@code input} and merge it with the + * set being built. This is just a small wrapper around + * {@link #mergeFrom(CodedInputStream)}. + */ + public Builder mergeFrom(InputStream input) throws IOException { + CodedInputStream codedInput = CodedInputStream.newInstance(input); + mergeFrom(codedInput); + codedInput.checkLastTagWas(0); + return this; + } + } + + /** + * Represents a single field in an {@code UnknownFieldSet}. + * + * <p>A {@code Field} consists of five lists of values. The lists correspond + * to the five "wire types" used in the protocol buffer binary format. + * The wire type of each field can be determined from the encoded form alone, + * without knowing the field's declared type. So, we are able to parse + * unknown values at least this far and separate them. Normally, only one + * of the five lists will contain any values, since it is impossible to + * define a valid message type that declares two different types for the + * same field number. However, the code is designed to allow for the case + * where the same unknown field number is encountered using multiple different + * wire types. + * + * <p>{@code Field} is an immutable class. To construct one, you must use a + * {@link Field.Builder}. + * + * @see UnknownFieldSet + */ + public static final class Field { + private Field() {} + + /** Construct a new {@link Builder}. */ + public static Builder newBuilder() { + return new Builder(); + } + + /** + * Construct a new {@link Builder} and initialize it to a copy of + * {@code copyFrom}. + */ + public static Builder newBuilder(Field copyFrom) { + return new Builder().mergeFrom(copyFrom); + } + + /** Get an empty {@code Field}. */ + public static Field getDefaultInstance() { + return defaultInstance; + } + private static Field defaultInstance = newBuilder().build(); + + /** Get the list of varint values for this field. */ + public List<Long> getVarintList() { return varint; } + + /** Get the list of fixed32 values for this field. */ + public List<Integer> getFixed32List() { return fixed32; } + + /** Get the list of fixed64 values for this field. */ + public List<Long> getFixed64List() { return fixed64; } + + /** Get the list of length-delimited values for this field. */ + public List<ByteString> getLengthDelimitedList() { return lengthDelimited; } + + /** + * Get the list of embedded group values for this field. These are + * represented using {@link UnknownFieldSet}s rather than {@link Message}s + * since the group's type is presumably unknown. + */ + public List<UnknownFieldSet> getGroupList() { return group; } + + /** + * Serializes the field, including field number, and writes it to + * {@code output}. + */ + public void writeTo(int fieldNumber, CodedOutputStream output) + throws IOException { + for (long value : varint) { + output.writeUInt64(fieldNumber, value); + } + for (int value : fixed32) { + output.writeFixed32(fieldNumber, value); + } + for (long value : fixed64) { + output.writeFixed64(fieldNumber, value); + } + for (ByteString value : lengthDelimited) { + output.writeBytes(fieldNumber, value); + } + for (UnknownFieldSet value : group) { + output.writeUnknownGroup(fieldNumber, value); + } + } + + /** + * Get the number of bytes required to encode this field, including field + * number. + */ + public int getSerializedSize(int fieldNumber) { + int result = 0; + for (long value : varint) { + result += CodedOutputStream.computeUInt64Size(fieldNumber, value); + } + for (int value : fixed32) { + result += CodedOutputStream.computeFixed32Size(fieldNumber, value); + } + for (long value : fixed64) { + result += CodedOutputStream.computeFixed64Size(fieldNumber, value); + } + for (ByteString value : lengthDelimited) { + result += CodedOutputStream.computeBytesSize(fieldNumber, value); + } + for (UnknownFieldSet value : group) { + result += CodedOutputStream.computeUnknownGroupSize(fieldNumber, value); + } + return result; + } + + /** + * Serializes the field, including field number, and writes it to + * {@code output}, using {@code MessageSet} wire format. + */ + public void writeAsMessageSetExtensionTo( + int fieldNumber, + CodedOutputStream output) + throws IOException { + for (ByteString value : lengthDelimited) { + output.writeRawMessageSetExtension(fieldNumber, value); + } + } + + /** + * Get the number of bytes required to encode this field, including field + * number, using {@code MessageSet} wire format. + */ + public int getSerializedSizeAsMessageSetExtension(int fieldNumber) { + int result = 0; + for (ByteString value : lengthDelimited) { + result += CodedOutputStream.computeRawMessageSetExtensionSize( + fieldNumber, value); + } + return result; + } + + private List<Long> varint; + private List<Integer> fixed32; + private List<Long> fixed64; + private List<ByteString> lengthDelimited; + private List<UnknownFieldSet> group; + + /** + * Used to build a {@link Field} within an {@link UnknownFieldSet}. + * + * <p>Use {@link Field#newBuilder()} to construct a {@code Builder}. + */ + public static final class Builder { + private Builder() {} + private Field result = new Field(); + + /** + * Build the field. After {@code build()} has been called, the + * {@code Builder} is no longer usable. Calling any other method will + * throw a {@code NullPointerException}. + */ + public Field build() { + if (result.varint == null) { + result.varint = Collections.emptyList(); + } else { + result.varint = Collections.unmodifiableList(result.varint); + } + if (result.fixed32 == null) { + result.fixed32 = Collections.emptyList(); + } else { + result.fixed32 = Collections.unmodifiableList(result.fixed32); + } + if (result.fixed64 == null) { + result.fixed64 = Collections.emptyList(); + } else { + result.fixed64 = Collections.unmodifiableList(result.fixed64); + } + if (result.lengthDelimited == null) { + result.lengthDelimited = Collections.emptyList(); + } else { + result.lengthDelimited = + Collections.unmodifiableList(result.lengthDelimited); + } + if (result.group == null) { + result.group = Collections.emptyList(); + } else { + result.group = Collections.unmodifiableList(result.group); + } + + Field returnMe = result; + result = null; + return returnMe; + } + + /** Discard the field's contents. */ + public Builder clear() { + result = new Field(); + return this; + } + + /** + * Merge the values in {@code other} into this field. For each list + * of values, {@code other}'s values are append to the ones in this + * field. + */ + public Builder mergeFrom(Field other) { + if (!other.varint.isEmpty()) { + if (result.varint == null) { + result.varint = new ArrayList<Long>(); + } + result.varint.addAll(other.varint); + } + if (!other.fixed32.isEmpty()) { + if (result.fixed32 == null) { + result.fixed32 = new ArrayList<Integer>(); + } + result.fixed32.addAll(other.fixed32); + } + if (!other.fixed64.isEmpty()) { + if (result.fixed64 == null) { + result.fixed64 = new ArrayList<Long>(); + } + result.fixed64.addAll(other.fixed64); + } + if (!other.lengthDelimited.isEmpty()) { + if (result.lengthDelimited == null) { + result.lengthDelimited = new ArrayList<ByteString>(); + } + result.lengthDelimited.addAll(other.lengthDelimited); + } + if (!other.group.isEmpty()) { + if (result.group == null) { + result.group = new ArrayList<UnknownFieldSet>(); + } + result.group.addAll(other.group); + } + return this; + } + + /** Add a varint value. */ + public Builder addVarint(long value) { + if (result.varint == null) { + result.varint = new ArrayList<Long>(); + } + result.varint.add(value); + return this; + } + + /** Add a fixed32 value. */ + public Builder addFixed32(int value) { + if (result.fixed32 == null) { + result.fixed32 = new ArrayList<Integer>(); + } + result.fixed32.add(value); + return this; + } + + /** Add a fixed64 value. */ + public Builder addFixed64(long value) { + if (result.fixed64 == null) { + result.fixed64 = new ArrayList<Long>(); + } + result.fixed64.add(value); + return this; + } + + /** Add a length-delimited value. */ + public Builder addLengthDelimited(ByteString value) { + if (result.lengthDelimited == null) { + result.lengthDelimited = new ArrayList<ByteString>(); + } + result.lengthDelimited.add(value); + return this; + } + + /** Add an embedded group. */ + public Builder addGroup(UnknownFieldSet value) { + if (result.group == null) { + result.group = new ArrayList<UnknownFieldSet>(); + } + result.group.add(value); + return this; + } + } + } +} diff --git a/java/src/main/java/com/google/protobuf/WireFormat.java b/java/src/main/java/com/google/protobuf/WireFormat.java new file mode 100644 index 00000000..31bfd402 --- /dev/null +++ b/java/src/main/java/com/google/protobuf/WireFormat.java @@ -0,0 +1,99 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.protobuf; + +/** + * This class is used internally by the Protocol Buffer library and generated + * message implementations. It is public only because those generated messages + * do not reside in the {@code protocol2} package. Others should not use this + * class directly. + * + * This class contains constants and helper functions useful for dealing with + * the Protocol Buffer wire format. + * + * @author kenton@google.com Kenton Varda + */ +public final class WireFormat { + // Do not allow instantiation. + private WireFormat() {} + + static final int WIRETYPE_VARINT = 0; + static final int WIRETYPE_FIXED64 = 1; + static final int WIRETYPE_LENGTH_DELIMITED = 2; + static final int WIRETYPE_START_GROUP = 3; + static final int WIRETYPE_END_GROUP = 4; + static final int WIRETYPE_FIXED32 = 5; + + static final int TAG_TYPE_BITS = 3; + static final int TAG_TYPE_MASK = (1 << TAG_TYPE_BITS) - 1; + + /** Given a tag value, determines the wire type (the lower 3 bits). */ + static int getTagWireType(int tag) { + return tag & TAG_TYPE_MASK; + } + + /** Given a tag value, determines the field number (the upper 29 bits). */ + public static int getTagFieldNumber(int tag) { + return tag >>> TAG_TYPE_BITS; + } + + /** Makes a tag value given a field number and wire type. */ + static int makeTag(int fieldNumber, int wireType) { + return (fieldNumber << TAG_TYPE_BITS) | wireType; + } + + static int getWireFormatForFieldType(Descriptors.FieldDescriptor.Type type) { + switch (type) { + case DOUBLE : return WIRETYPE_FIXED64; + case FLOAT : return WIRETYPE_FIXED32; + case INT64 : return WIRETYPE_VARINT; + case UINT64 : return WIRETYPE_VARINT; + case INT32 : return WIRETYPE_VARINT; + case FIXED64 : return WIRETYPE_FIXED64; + case FIXED32 : return WIRETYPE_FIXED32; + case BOOL : return WIRETYPE_VARINT; + case STRING : return WIRETYPE_LENGTH_DELIMITED; + case GROUP : return WIRETYPE_START_GROUP; + case MESSAGE : return WIRETYPE_LENGTH_DELIMITED; + case BYTES : return WIRETYPE_LENGTH_DELIMITED; + case UINT32 : return WIRETYPE_VARINT; + case ENUM : return WIRETYPE_VARINT; + case SFIXED32: return WIRETYPE_FIXED32; + case SFIXED64: return WIRETYPE_FIXED64; + case SINT32 : return WIRETYPE_VARINT; + case SINT64 : return WIRETYPE_VARINT; + } + + throw new RuntimeException( + "There is no way to get here, but the compiler thinks otherwise."); + } + + // Field numbers for feilds in MessageSet wire format. + static final int MESSAGE_SET_ITEM = 1; + static final int MESSAGE_SET_TYPE_ID = 2; + static final int MESSAGE_SET_MESSAGE = 3; + + // Tag numbers. + static final int MESSAGE_SET_ITEM_TAG = + makeTag(MESSAGE_SET_ITEM, WIRETYPE_START_GROUP); + static final int MESSAGE_SET_ITEM_END_TAG = + makeTag(MESSAGE_SET_ITEM, WIRETYPE_END_GROUP); + static final int MESSAGE_SET_TYPE_ID_TAG = + makeTag(MESSAGE_SET_TYPE_ID, WIRETYPE_VARINT); + static final int MESSAGE_SET_MESSAGE_TAG = + makeTag(MESSAGE_SET_MESSAGE, WIRETYPE_LENGTH_DELIMITED); +} diff --git a/java/src/test/java/com/google/protobuf/AbstractMessageTest.java b/java/src/test/java/com/google/protobuf/AbstractMessageTest.java new file mode 100644 index 00000000..bbb88b86 --- /dev/null +++ b/java/src/test/java/com/google/protobuf/AbstractMessageTest.java @@ -0,0 +1,362 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.protobuf; + +import protobuf_unittest.UnittestProto; +import protobuf_unittest.UnittestProto.ForeignMessage; +import protobuf_unittest.UnittestProto.TestAllTypes; +import protobuf_unittest.UnittestProto.TestAllExtensions; +import protobuf_unittest.UnittestProto.TestRequired; +import protobuf_unittest.UnittestProto.TestRequiredForeign; +import protobuf_unittest.UnittestOptimizeFor.TestOptimizedForSize; + +import junit.framework.TestCase; + +import java.util.Map; + +/** + * Unit test for {@link AbstractMessage}. + * + * @author kenton@google.com Kenton Varda + */ +public class AbstractMessageTest extends TestCase { + /** + * Extends AbstractMessage and wraps some other message object. The methods + * of the Message interface which aren't explicitly implemented by + * AbstractMessage are forwarded to the wrapped object. This allows us to + * test that AbstractMessage's implementations work even if the wrapped + * object does not use them. + */ + private static class AbstractMessageWrapper extends AbstractMessage { + private final Message wrappedMessage; + + public AbstractMessageWrapper(Message wrappedMessage) { + this.wrappedMessage = wrappedMessage; + } + + public Descriptors.Descriptor getDescriptorForType() { + return wrappedMessage.getDescriptorForType(); + } + public AbstractMessageWrapper getDefaultInstanceForType() { + return new AbstractMessageWrapper( + wrappedMessage.getDefaultInstanceForType()); + } + public Map<Descriptors.FieldDescriptor, Object> getAllFields() { + return wrappedMessage.getAllFields(); + } + public boolean hasField(Descriptors.FieldDescriptor field) { + return wrappedMessage.hasField(field); + } + public Object getField(Descriptors.FieldDescriptor field) { + return wrappedMessage.getField(field); + } + public int getRepeatedFieldCount(Descriptors.FieldDescriptor field) { + return wrappedMessage.getRepeatedFieldCount(field); + } + public Object getRepeatedField( + Descriptors.FieldDescriptor field, int index) { + return wrappedMessage.getRepeatedField(field, index); + } + public UnknownFieldSet getUnknownFields() { + return wrappedMessage.getUnknownFields(); + } + public Builder newBuilderForType() { + return new Builder(wrappedMessage.newBuilderForType()); + } + + static class Builder extends AbstractMessage.Builder<Builder> { + private final Message.Builder wrappedBuilder; + + public Builder(Message.Builder wrappedBuilder) { + this.wrappedBuilder = wrappedBuilder; + } + + public AbstractMessageWrapper build() { + return new AbstractMessageWrapper(wrappedBuilder.build()); + } + public AbstractMessageWrapper buildPartial() { + return new AbstractMessageWrapper(wrappedBuilder.buildPartial()); + } + public Builder clone() { + return new Builder(wrappedBuilder.clone()); + } + public boolean isInitialized() { + return clone().buildPartial().isInitialized(); + } + public Descriptors.Descriptor getDescriptorForType() { + return wrappedBuilder.getDescriptorForType(); + } + public AbstractMessageWrapper getDefaultInstanceForType() { + return new AbstractMessageWrapper( + wrappedBuilder.getDefaultInstanceForType()); + } + public Map<Descriptors.FieldDescriptor, Object> getAllFields() { + return wrappedBuilder.getAllFields(); + } + public Builder newBuilderForField(Descriptors.FieldDescriptor field) { + return new Builder(wrappedBuilder.newBuilderForField(field)); + } + public boolean hasField(Descriptors.FieldDescriptor field) { + return wrappedBuilder.hasField(field); + } + public Object getField(Descriptors.FieldDescriptor field) { + return wrappedBuilder.getField(field); + } + public Builder setField(Descriptors.FieldDescriptor field, Object value) { + wrappedBuilder.setField(field, value); + return this; + } + public Builder clearField(Descriptors.FieldDescriptor field) { + wrappedBuilder.clearField(field); + return this; + } + public int getRepeatedFieldCount(Descriptors.FieldDescriptor field) { + return wrappedBuilder.getRepeatedFieldCount(field); + } + public Object getRepeatedField( + Descriptors.FieldDescriptor field, int index) { + return wrappedBuilder.getRepeatedField(field, index); + } + public Builder setRepeatedField(Descriptors.FieldDescriptor field, + int index, Object value) { + wrappedBuilder.setRepeatedField(field, index, value); + return this; + } + public Builder addRepeatedField( + Descriptors.FieldDescriptor field, Object value) { + wrappedBuilder.addRepeatedField(field, value); + return this; + } + public UnknownFieldSet getUnknownFields() { + return wrappedBuilder.getUnknownFields(); + } + public Builder setUnknownFields(UnknownFieldSet unknownFields) { + wrappedBuilder.setUnknownFields(unknownFields); + return this; + } + } + } + + // ================================================================= + + TestUtil.ReflectionTester reflectionTester = + new TestUtil.ReflectionTester(TestAllTypes.getDescriptor(), null); + + TestUtil.ReflectionTester extensionsReflectionTester = + new TestUtil.ReflectionTester(TestAllExtensions.getDescriptor(), + TestUtil.getExtensionRegistry()); + + public void testClear() throws Exception { + AbstractMessageWrapper message = + new AbstractMessageWrapper.Builder( + TestAllTypes.newBuilder(TestUtil.getAllSet())) + .clear().build(); + TestUtil.assertClear((TestAllTypes) message.wrappedMessage); + } + + public void testCopy() throws Exception { + AbstractMessageWrapper message = + new AbstractMessageWrapper.Builder(TestAllTypes.newBuilder()) + .mergeFrom(TestUtil.getAllSet()).build(); + TestUtil.assertAllFieldsSet((TestAllTypes) message.wrappedMessage); + } + + public void testSerializedSize() throws Exception { + TestAllTypes message = TestUtil.getAllSet(); + Message abstractMessage = new AbstractMessageWrapper(TestUtil.getAllSet()); + + assertEquals(message.getSerializedSize(), + abstractMessage.getSerializedSize()); + } + + public void testSerialization() throws Exception { + Message abstractMessage = new AbstractMessageWrapper(TestUtil.getAllSet()); + + TestUtil.assertAllFieldsSet( + TestAllTypes.parseFrom(abstractMessage.toByteString())); + + assertEquals(TestUtil.getAllSet().toByteString(), + abstractMessage.toByteString()); + } + + public void testParsing() throws Exception { + AbstractMessageWrapper.Builder builder = + new AbstractMessageWrapper.Builder(TestAllTypes.newBuilder()); + AbstractMessageWrapper message = + builder.mergeFrom(TestUtil.getAllSet().toByteString()).build(); + TestUtil.assertAllFieldsSet((TestAllTypes) message.wrappedMessage); + } + + public void testOptimizedForSize() throws Exception { + // We're mostly only checking that this class was compiled successfully. + TestOptimizedForSize message = + TestOptimizedForSize.newBuilder().setI(1).build(); + message = TestOptimizedForSize.parseFrom(message.toByteString()); + assertEquals(2, message.getSerializedSize()); + } + + // ----------------------------------------------------------------- + // Tests for isInitialized(). + + private static final TestRequired TEST_REQUIRED_UNINITIALIZED = + TestRequired.getDefaultInstance(); + private static final TestRequired TEST_REQUIRED_INITIALIZED = + TestRequired.newBuilder().setA(1).setB(2).setC(3).build(); + + public void testIsInitialized() throws Exception { + TestRequired.Builder builder = TestRequired.newBuilder(); + AbstractMessageWrapper.Builder abstractBuilder = + new AbstractMessageWrapper.Builder(builder); + + assertFalse(abstractBuilder.isInitialized()); + builder.setA(1); + assertFalse(abstractBuilder.isInitialized()); + builder.setB(1); + assertFalse(abstractBuilder.isInitialized()); + builder.setC(1); + assertTrue(abstractBuilder.isInitialized()); + } + + public void testForeignIsInitialized() throws Exception { + TestRequiredForeign.Builder builder = TestRequiredForeign.newBuilder(); + AbstractMessageWrapper.Builder abstractBuilder = + new AbstractMessageWrapper.Builder(builder); + + assertTrue(abstractBuilder.isInitialized()); + + builder.setOptionalMessage(TEST_REQUIRED_UNINITIALIZED); + assertFalse(abstractBuilder.isInitialized()); + + builder.setOptionalMessage(TEST_REQUIRED_INITIALIZED); + assertTrue(abstractBuilder.isInitialized()); + + builder.addRepeatedMessage(TEST_REQUIRED_UNINITIALIZED); + assertFalse(abstractBuilder.isInitialized()); + + builder.setRepeatedMessage(0, TEST_REQUIRED_INITIALIZED); + assertTrue(abstractBuilder.isInitialized()); + } + + // ----------------------------------------------------------------- + // Tests for mergeFrom + + static final TestAllTypes MERGE_SOURCE = + TestAllTypes.newBuilder() + .setOptionalInt32(1) + .setOptionalString("foo") + .setOptionalForeignMessage(ForeignMessage.getDefaultInstance()) + .addRepeatedString("bar") + .build(); + + static final TestAllTypes MERGE_DEST = + TestAllTypes.newBuilder() + .setOptionalInt64(2) + .setOptionalString("baz") + .setOptionalForeignMessage(ForeignMessage.newBuilder().setC(3).build()) + .addRepeatedString("qux") + .build(); + + static final String MERGE_RESULT_TEXT = + "optional_int32: 1\n" + + "optional_int64: 2\n" + + "optional_string: \"foo\"\n" + + "optional_foreign_message {\n" + + " c: 3\n" + + "}\n" + + "repeated_string: \"qux\"\n" + + "repeated_string: \"bar\"\n"; + + public void testMergeFrom() throws Exception { + AbstractMessageWrapper result = + new AbstractMessageWrapper.Builder( + TestAllTypes.newBuilder(MERGE_DEST)) + .mergeFrom(MERGE_SOURCE).build(); + + assertEquals(MERGE_RESULT_TEXT, result.toString()); + } + + // ----------------------------------------------------------------- + // Tests for equals and hashCode + + public void testEqualsAndHashCode() { + TestAllTypes a = TestUtil.getAllSet(); + TestAllTypes b = TestAllTypes.newBuilder().build(); + TestAllTypes c = TestAllTypes.newBuilder(b).addRepeatedString("x").build(); + TestAllTypes d = TestAllTypes.newBuilder(c).addRepeatedString("y").build(); + TestAllExtensions e = TestUtil.getAllExtensionsSet(); + TestAllExtensions f = TestAllExtensions.newBuilder(e) + .addExtension(UnittestProto.repeatedInt32Extension, 999).build(); + + checkEqualsIsConsistent(a); + checkEqualsIsConsistent(b); + checkEqualsIsConsistent(c); + checkEqualsIsConsistent(d); + checkEqualsIsConsistent(e); + checkEqualsIsConsistent(f); + + checkNotEqual(a, b); + checkNotEqual(a, c); + checkNotEqual(a, d); + checkNotEqual(a, e); + checkNotEqual(a, f); + + checkNotEqual(b, c); + checkNotEqual(b, d); + checkNotEqual(b, e); + checkNotEqual(b, f); + + checkNotEqual(c, d); + checkNotEqual(c, e); + checkNotEqual(c, f); + + checkNotEqual(d, e); + checkNotEqual(d, f); + + checkNotEqual(e, f); + } + + /** + * Asserts that the given protos are equal and have the same hash code. + */ + private void checkEqualsIsConsistent(Message message) { + // Object should be equal to itself. + assertEquals(message, message); + + // Object should be equal to a dynamic copy of itself. + DynamicMessage dynamic = DynamicMessage.newBuilder(message).build(); + assertEquals(message, dynamic); + assertEquals(dynamic, message); + assertEquals(dynamic.hashCode(), message.hashCode()); + } + + /** + * Asserts that the given protos are not equal and have different hash codes. + * + * @warning It's valid for non-equal objects to have the same hash code, so + * this test is stricter than it needs to be. However, this should happen + * relatively rarely. + */ + private void checkNotEqual(Message m1, Message m2) { + String equalsError = String.format("%s should not be equal to %s", m1, m2); + assertFalse(equalsError, m1.equals(m2)); + assertFalse(equalsError, m2.equals(m1)); + + assertFalse( + String.format("%s should have a different hash code from %s", m1, m2), + m1.hashCode() == m2.hashCode()); + } +} diff --git a/java/src/test/java/com/google/protobuf/CodedInputStreamTest.java b/java/src/test/java/com/google/protobuf/CodedInputStreamTest.java new file mode 100644 index 00000000..b34e56f0 --- /dev/null +++ b/java/src/test/java/com/google/protobuf/CodedInputStreamTest.java @@ -0,0 +1,401 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.protobuf; + +import protobuf_unittest.UnittestProto.TestAllTypes; +import protobuf_unittest.UnittestProto.TestRecursiveMessage; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.FilterInputStream; +import java.io.InputStream; +import java.io.IOException; + +/** + * Unit test for {@link CodedInputStream}. + * + * @author kenton@google.com Kenton Varda + */ +public class CodedInputStreamTest extends TestCase { + /** + * Helper to construct a byte array from a bunch of bytes. The inputs are + * actually ints so that I can use hex notation and not get stupid errors + * about precision. + */ + private byte[] bytes(int... bytesAsInts) { + byte[] bytes = new byte[bytesAsInts.length]; + for (int i = 0; i < bytesAsInts.length; i++) { + bytes[i] = (byte) bytesAsInts[i]; + } + return bytes; + } + + /** + * An InputStream which limits the number of bytes it reads at a time. + * We use this to make sure that CodedInputStream doesn't screw up when + * reading in small blocks. + */ + private static final class SmallBlockInputStream extends FilterInputStream { + private final int blockSize; + + public SmallBlockInputStream(byte[] data, int blockSize) { + this(new ByteArrayInputStream(data), blockSize); + } + + public SmallBlockInputStream(InputStream in, int blockSize) { + super(in); + this.blockSize = blockSize; + } + + public int read(byte[] b) throws IOException { + return super.read(b, 0, Math.min(b.length, blockSize)); + } + + public int read(byte[] b, int off, int len) throws IOException { + return super.read(b, off, Math.min(len, blockSize)); + } + } + + /** + * Parses the given bytes using readRawVarint32() and readRawVarint64() and + * checks that the result matches the given value. + */ + private void assertReadVarint(byte[] data, long value) throws Exception { + CodedInputStream input = CodedInputStream.newInstance(data); + assertEquals((int)value, input.readRawVarint32()); + + input = CodedInputStream.newInstance(data); + assertEquals(value, input.readRawVarint64()); + + // Try different block sizes. + for (int blockSize = 1; blockSize <= 16; blockSize *= 2) { + input = CodedInputStream.newInstance( + new SmallBlockInputStream(data, blockSize)); + assertEquals((int)value, input.readRawVarint32()); + + input = CodedInputStream.newInstance( + new SmallBlockInputStream(data, blockSize)); + assertEquals(value, input.readRawVarint64()); + } + } + + /** + * Parses the given bytes using readRawVarint32() and readRawVarint64() and + * expects them to fail with an InvalidProtocolBufferException whose + * description matches the given one. + */ + private void assertReadVarintFailure( + InvalidProtocolBufferException expected, byte[] data) + throws Exception { + CodedInputStream input = CodedInputStream.newInstance(data); + try { + input.readRawVarint32(); + fail("Should have thrown an exception."); + } catch (InvalidProtocolBufferException e) { + assertEquals(expected.getMessage(), e.getMessage()); + } + + input = CodedInputStream.newInstance(data); + try { + input.readRawVarint64(); + fail("Should have thrown an exception."); + } catch (InvalidProtocolBufferException e) { + assertEquals(expected.getMessage(), e.getMessage()); + } + } + + /** Tests readRawVarint32() and readRawVarint64(). */ + public void testReadVarint() throws Exception { + assertReadVarint(bytes(0x00), 0); + assertReadVarint(bytes(0x01), 1); + assertReadVarint(bytes(0x7f), 127); + // 14882 + assertReadVarint(bytes(0xa2, 0x74), (0x22 << 0) | (0x74 << 7)); + // 2961488830 + assertReadVarint(bytes(0xbe, 0xf7, 0x92, 0x84, 0x0b), + (0x3e << 0) | (0x77 << 7) | (0x12 << 14) | (0x04 << 21) | + (0x0bL << 28)); + + // 64-bit + // 7256456126 + assertReadVarint(bytes(0xbe, 0xf7, 0x92, 0x84, 0x1b), + (0x3e << 0) | (0x77 << 7) | (0x12 << 14) | (0x04 << 21) | + (0x1bL << 28)); + // 41256202580718336 + assertReadVarint( + bytes(0x80, 0xe6, 0xeb, 0x9c, 0xc3, 0xc9, 0xa4, 0x49), + (0x00 << 0) | (0x66 << 7) | (0x6b << 14) | (0x1c << 21) | + (0x43L << 28) | (0x49L << 35) | (0x24L << 42) | (0x49L << 49)); + // 11964378330978735131 + assertReadVarint( + bytes(0x9b, 0xa8, 0xf9, 0xc2, 0xbb, 0xd6, 0x80, 0x85, 0xa6, 0x01), + (0x1b << 0) | (0x28 << 7) | (0x79 << 14) | (0x42 << 21) | + (0x3bL << 28) | (0x56L << 35) | (0x00L << 42) | + (0x05L << 49) | (0x26L << 56) | (0x01L << 63)); + + // Failures + assertReadVarintFailure( + InvalidProtocolBufferException.malformedVarint(), + bytes(0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x00)); + assertReadVarintFailure( + InvalidProtocolBufferException.truncatedMessage(), + bytes(0x80)); + } + + /** + * Parses the given bytes using readRawLittleEndian32() and checks + * that the result matches the given value. + */ + private void assertReadLittleEndian32(byte[] data, int value) + throws Exception { + CodedInputStream input = CodedInputStream.newInstance(data); + assertEquals(value, input.readRawLittleEndian32()); + + // Try different block sizes. + for (int blockSize = 1; blockSize <= 16; blockSize *= 2) { + input = CodedInputStream.newInstance( + new SmallBlockInputStream(data, blockSize)); + assertEquals(value, input.readRawLittleEndian32()); + } + } + + /** + * Parses the given bytes using readRawLittleEndian64() and checks + * that the result matches the given value. + */ + private void assertReadLittleEndian64(byte[] data, long value) + throws Exception { + CodedInputStream input = CodedInputStream.newInstance(data); + assertEquals(value, input.readRawLittleEndian64()); + + // Try different block sizes. + for (int blockSize = 1; blockSize <= 16; blockSize *= 2) { + input = CodedInputStream.newInstance( + new SmallBlockInputStream(data, blockSize)); + assertEquals(value, input.readRawLittleEndian64()); + } + } + + /** Tests readRawLittleEndian32() and readRawLittleEndian64(). */ + public void testReadLittleEndian() throws Exception { + assertReadLittleEndian32(bytes(0x78, 0x56, 0x34, 0x12), 0x12345678); + assertReadLittleEndian32(bytes(0xf0, 0xde, 0xbc, 0x9a), 0x9abcdef0); + + assertReadLittleEndian64( + bytes(0xf0, 0xde, 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12), + 0x123456789abcdef0L); + assertReadLittleEndian64( + bytes(0x78, 0x56, 0x34, 0x12, 0xf0, 0xde, 0xbc, 0x9a), + 0x9abcdef012345678L); + } + + /** Test decodeZigZag32() and decodeZigZag64(). */ + public void testDecodeZigZag() throws Exception { + assertEquals( 0, CodedInputStream.decodeZigZag32(0)); + assertEquals(-1, CodedInputStream.decodeZigZag32(1)); + assertEquals( 1, CodedInputStream.decodeZigZag32(2)); + assertEquals(-2, CodedInputStream.decodeZigZag32(3)); + assertEquals(0x3FFFFFFF, CodedInputStream.decodeZigZag32(0x7FFFFFFE)); + assertEquals(0xC0000000, CodedInputStream.decodeZigZag32(0x7FFFFFFF)); + assertEquals(0x7FFFFFFF, CodedInputStream.decodeZigZag32(0xFFFFFFFE)); + assertEquals(0x80000000, CodedInputStream.decodeZigZag32(0xFFFFFFFF)); + + assertEquals( 0, CodedInputStream.decodeZigZag64(0)); + assertEquals(-1, CodedInputStream.decodeZigZag64(1)); + assertEquals( 1, CodedInputStream.decodeZigZag64(2)); + assertEquals(-2, CodedInputStream.decodeZigZag64(3)); + assertEquals(0x000000003FFFFFFFL, + CodedInputStream.decodeZigZag64(0x000000007FFFFFFEL)); + assertEquals(0xFFFFFFFFC0000000L, + CodedInputStream.decodeZigZag64(0x000000007FFFFFFFL)); + assertEquals(0x000000007FFFFFFFL, + CodedInputStream.decodeZigZag64(0x00000000FFFFFFFEL)); + assertEquals(0xFFFFFFFF80000000L, + CodedInputStream.decodeZigZag64(0x00000000FFFFFFFFL)); + assertEquals(0x7FFFFFFFFFFFFFFFL, + CodedInputStream.decodeZigZag64(0xFFFFFFFFFFFFFFFEL)); + assertEquals(0x8000000000000000L, + CodedInputStream.decodeZigZag64(0xFFFFFFFFFFFFFFFFL)); + } + + /** Tests reading and parsing a whole message with every field type. */ + public void testReadWholeMessage() throws Exception { + TestAllTypes message = TestUtil.getAllSet(); + + byte[] rawBytes = message.toByteArray(); + assertEquals(rawBytes.length, message.getSerializedSize()); + + TestAllTypes message2 = TestAllTypes.parseFrom(rawBytes); + TestUtil.assertAllFieldsSet(message2); + + // Try different block sizes. + for (int blockSize = 1; blockSize < 256; blockSize *= 2) { + message2 = TestAllTypes.parseFrom( + new SmallBlockInputStream(rawBytes, blockSize)); + TestUtil.assertAllFieldsSet(message2); + } + } + + /** Tests skipField(). */ + public void testSkipWholeMessage() throws Exception { + TestAllTypes message = TestUtil.getAllSet(); + byte[] rawBytes = message.toByteArray(); + + // Create two parallel inputs. Parse one as unknown fields while using + // skipField() to skip each field on the other. Expect the same tags. + CodedInputStream input1 = CodedInputStream.newInstance(rawBytes); + CodedInputStream input2 = CodedInputStream.newInstance(rawBytes); + UnknownFieldSet.Builder unknownFields = UnknownFieldSet.newBuilder(); + + while (true) { + int tag = input1.readTag(); + assertEquals(tag, input2.readTag()); + if (tag == 0) { + break; + } + unknownFields.mergeFieldFrom(tag, input1); + input2.skipField(tag); + } + } + + public void testReadHugeBlob() throws Exception { + // Allocate and initialize a 1MB blob. + byte[] blob = new byte[1 << 20]; + for (int i = 0; i < blob.length; i++) { + blob[i] = (byte)i; + } + + // Make a message containing it. + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TestUtil.setAllFields(builder); + builder.setOptionalBytes(ByteString.copyFrom(blob)); + TestAllTypes message = builder.build(); + + // Serialize and parse it. Make sure to parse from an InputStream, not + // directly from a ByteString, so that CodedInputStream uses buffered + // reading. + TestAllTypes message2 = + TestAllTypes.parseFrom(message.toByteString().newInput()); + + assertEquals(message.getOptionalBytes(), message2.getOptionalBytes()); + + // Make sure all the other fields were parsed correctly. + TestAllTypes message3 = TestAllTypes.newBuilder(message2) + .setOptionalBytes(TestUtil.getAllSet().getOptionalBytes()) + .build(); + TestUtil.assertAllFieldsSet(message3); + } + + public void testReadMaliciouslyLargeBlob() throws Exception { + ByteString.Output rawOutput = ByteString.newOutput(); + CodedOutputStream output = CodedOutputStream.newInstance(rawOutput); + + int tag = WireFormat.makeTag(1, WireFormat.WIRETYPE_LENGTH_DELIMITED); + output.writeRawVarint32(tag); + output.writeRawVarint32(0x7FFFFFFF); + output.writeRawBytes(new byte[32]); // Pad with a few random bytes. + output.flush(); + + CodedInputStream input = rawOutput.toByteString().newCodedInput(); + assertEquals(tag, input.readTag()); + + try { + input.readBytes(); + fail("Should have thrown an exception!"); + } catch (InvalidProtocolBufferException e) { + // success. + } + } + + private TestRecursiveMessage makeRecursiveMessage(int depth) { + if (depth == 0) { + return TestRecursiveMessage.newBuilder().setI(5).build(); + } else { + return TestRecursiveMessage.newBuilder() + .setA(makeRecursiveMessage(depth - 1)).build(); + } + } + + private void assertMessageDepth(TestRecursiveMessage message, int depth) { + if (depth == 0) { + assertFalse(message.hasA()); + assertEquals(5, message.getI()); + } else { + assertTrue(message.hasA()); + assertMessageDepth(message.getA(), depth - 1); + } + } + + public void testMaliciousRecursion() throws Exception { + ByteString data64 = makeRecursiveMessage(64).toByteString(); + ByteString data65 = makeRecursiveMessage(65).toByteString(); + + assertMessageDepth(TestRecursiveMessage.parseFrom(data64), 64); + + try { + TestRecursiveMessage.parseFrom(data65); + fail("Should have thrown an exception!"); + } catch (InvalidProtocolBufferException e) { + // success. + } + + CodedInputStream input = data64.newCodedInput(); + input.setRecursionLimit(8); + try { + TestRecursiveMessage.parseFrom(input); + fail("Should have thrown an exception!"); + } catch (InvalidProtocolBufferException e) { + // success. + } + } + + public void testSizeLimit() throws Exception { + CodedInputStream input = CodedInputStream.newInstance( + TestUtil.getAllSet().toByteString().newInput()); + input.setSizeLimit(16); + + try { + TestAllTypes.parseFrom(input); + 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 + * "replacement character" U+FFFD. + */ + public void testReadInvalidUtf8() throws Exception { + ByteString.Output rawOutput = ByteString.newOutput(); + CodedOutputStream output = CodedOutputStream.newInstance(rawOutput); + + int tag = WireFormat.makeTag(1, WireFormat.WIRETYPE_LENGTH_DELIMITED); + output.writeRawVarint32(tag); + output.writeRawVarint32(1); + output.writeRawBytes(new byte[] { (byte)0x80 }); + output.flush(); + + CodedInputStream input = rawOutput.toByteString().newCodedInput(); + assertEquals(tag, input.readTag()); + String text = input.readString(); + assertEquals(0xfffd, text.charAt(0)); + } +} diff --git a/java/src/test/java/com/google/protobuf/CodedOutputStreamTest.java b/java/src/test/java/com/google/protobuf/CodedOutputStreamTest.java new file mode 100644 index 00000000..7ee75534 --- /dev/null +++ b/java/src/test/java/com/google/protobuf/CodedOutputStreamTest.java @@ -0,0 +1,280 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.protobuf; + +import protobuf_unittest.UnittestProto.TestAllTypes; + +import junit.framework.TestCase; + +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Unit test for {@link CodedOutputStream}. + * + * @author kenton@google.com Kenton Varda + */ +public class CodedOutputStreamTest extends TestCase { + /** + * Helper to construct a byte array from a bunch of bytes. The inputs are + * actually ints so that I can use hex notation and not get stupid errors + * about precision. + */ + private byte[] bytes(int... bytesAsInts) { + byte[] bytes = new byte[bytesAsInts.length]; + for (int i = 0; i < bytesAsInts.length; i++) { + bytes[i] = (byte) bytesAsInts[i]; + } + return bytes; + } + + /** Arrays.asList() does not work with arrays of primitives. :( */ + private List<Byte> toList(byte[] bytes) { + List<Byte> result = new ArrayList<Byte>(); + for (byte b : bytes) { + result.add(b); + } + return result; + } + + private void assertEqualBytes(byte[] a, byte[] b) { + assertEquals(toList(a), toList(b)); + } + + /** + * Writes the given value using writeRawVarint32() and writeRawVarint64() and + * checks that the result matches the given bytes. + */ + private void assertWriteVarint(byte[] data, long value) throws Exception { + // Only do 32-bit write if the value fits in 32 bits. + if ((value >>> 32) == 0) { + ByteArrayOutputStream rawOutput = new ByteArrayOutputStream(); + CodedOutputStream output = CodedOutputStream.newInstance(rawOutput); + output.writeRawVarint32((int) value); + output.flush(); + assertEqualBytes(data, rawOutput.toByteArray()); + + // Also try computing size. + assertEquals(data.length, + CodedOutputStream.computeRawVarint32Size((int) value)); + } + + { + ByteArrayOutputStream rawOutput = new ByteArrayOutputStream(); + CodedOutputStream output = CodedOutputStream.newInstance(rawOutput); + output.writeRawVarint64(value); + output.flush(); + assertEqualBytes(data, rawOutput.toByteArray()); + + // Also try computing size. + assertEquals(data.length, + CodedOutputStream.computeRawVarint64Size(value)); + } + + // Try different block sizes. + for (int blockSize = 1; blockSize <= 16; blockSize *= 2) { + // Only do 32-bit write if the value fits in 32 bits. + if ((value >>> 32) == 0) { + ByteArrayOutputStream rawOutput = new ByteArrayOutputStream(); + CodedOutputStream output = + CodedOutputStream.newInstance(rawOutput, blockSize); + output.writeRawVarint32((int) value); + output.flush(); + assertEqualBytes(data, rawOutput.toByteArray()); + } + + { + ByteArrayOutputStream rawOutput = new ByteArrayOutputStream(); + CodedOutputStream output = + CodedOutputStream.newInstance(rawOutput, blockSize); + output.writeRawVarint64(value); + output.flush(); + assertEqualBytes(data, rawOutput.toByteArray()); + } + } + } + + /** Tests writeRawVarint32() and writeRawVarint64(). */ + public void testWriteVarint() throws Exception { + assertWriteVarint(bytes(0x00), 0); + assertWriteVarint(bytes(0x01), 1); + assertWriteVarint(bytes(0x7f), 127); + // 14882 + assertWriteVarint(bytes(0xa2, 0x74), (0x22 << 0) | (0x74 << 7)); + // 2961488830 + assertWriteVarint(bytes(0xbe, 0xf7, 0x92, 0x84, 0x0b), + (0x3e << 0) | (0x77 << 7) | (0x12 << 14) | (0x04 << 21) | + (0x0bL << 28)); + + // 64-bit + // 7256456126 + assertWriteVarint(bytes(0xbe, 0xf7, 0x92, 0x84, 0x1b), + (0x3e << 0) | (0x77 << 7) | (0x12 << 14) | (0x04 << 21) | + (0x1bL << 28)); + // 41256202580718336 + assertWriteVarint( + bytes(0x80, 0xe6, 0xeb, 0x9c, 0xc3, 0xc9, 0xa4, 0x49), + (0x00 << 0) | (0x66 << 7) | (0x6b << 14) | (0x1c << 21) | + (0x43L << 28) | (0x49L << 35) | (0x24L << 42) | (0x49L << 49)); + // 11964378330978735131 + assertWriteVarint( + bytes(0x9b, 0xa8, 0xf9, 0xc2, 0xbb, 0xd6, 0x80, 0x85, 0xa6, 0x01), + (0x1b << 0) | (0x28 << 7) | (0x79 << 14) | (0x42 << 21) | + (0x3bL << 28) | (0x56L << 35) | (0x00L << 42) | + (0x05L << 49) | (0x26L << 56) | (0x01L << 63)); + } + + /** + * Parses the given bytes using writeRawLittleEndian32() and checks + * that the result matches the given value. + */ + private void assertWriteLittleEndian32(byte[] data, int value) + throws Exception { + ByteArrayOutputStream rawOutput = new ByteArrayOutputStream(); + CodedOutputStream output = CodedOutputStream.newInstance(rawOutput); + output.writeRawLittleEndian32(value); + output.flush(); + assertEqualBytes(data, rawOutput.toByteArray()); + + // Try different block sizes. + for (int blockSize = 1; blockSize <= 16; blockSize *= 2) { + rawOutput = new ByteArrayOutputStream(); + output = CodedOutputStream.newInstance(rawOutput, blockSize); + output.writeRawLittleEndian32(value); + output.flush(); + assertEqualBytes(data, rawOutput.toByteArray()); + } + } + + /** + * Parses the given bytes using writeRawLittleEndian64() and checks + * that the result matches the given value. + */ + private void assertWriteLittleEndian64(byte[] data, long value) + throws Exception { + ByteArrayOutputStream rawOutput = new ByteArrayOutputStream(); + CodedOutputStream output = CodedOutputStream.newInstance(rawOutput); + output.writeRawLittleEndian64(value); + output.flush(); + assertEqualBytes(data, rawOutput.toByteArray()); + + // Try different block sizes. + for (int blockSize = 1; blockSize <= 16; blockSize *= 2) { + rawOutput = new ByteArrayOutputStream(); + output = CodedOutputStream.newInstance(rawOutput, blockSize); + output.writeRawLittleEndian64(value); + output.flush(); + assertEqualBytes(data, rawOutput.toByteArray()); + } + } + + /** Tests writeRawLittleEndian32() and writeRawLittleEndian64(). */ + public void testWriteLittleEndian() throws Exception { + assertWriteLittleEndian32(bytes(0x78, 0x56, 0x34, 0x12), 0x12345678); + assertWriteLittleEndian32(bytes(0xf0, 0xde, 0xbc, 0x9a), 0x9abcdef0); + + assertWriteLittleEndian64( + bytes(0xf0, 0xde, 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12), + 0x123456789abcdef0L); + assertWriteLittleEndian64( + bytes(0x78, 0x56, 0x34, 0x12, 0xf0, 0xde, 0xbc, 0x9a), + 0x9abcdef012345678L); + } + + /** Test encodeZigZag32() and encodeZigZag64(). */ + public void testEncodeZigZag() throws Exception { + assertEquals(0, CodedOutputStream.encodeZigZag32( 0)); + assertEquals(1, CodedOutputStream.encodeZigZag32(-1)); + assertEquals(2, CodedOutputStream.encodeZigZag32( 1)); + assertEquals(3, CodedOutputStream.encodeZigZag32(-2)); + assertEquals(0x7FFFFFFE, CodedOutputStream.encodeZigZag32(0x3FFFFFFF)); + assertEquals(0x7FFFFFFF, CodedOutputStream.encodeZigZag32(0xC0000000)); + assertEquals(0xFFFFFFFE, CodedOutputStream.encodeZigZag32(0x7FFFFFFF)); + assertEquals(0xFFFFFFFF, CodedOutputStream.encodeZigZag32(0x80000000)); + + assertEquals(0, CodedOutputStream.encodeZigZag64( 0)); + assertEquals(1, CodedOutputStream.encodeZigZag64(-1)); + assertEquals(2, CodedOutputStream.encodeZigZag64( 1)); + assertEquals(3, CodedOutputStream.encodeZigZag64(-2)); + assertEquals(0x000000007FFFFFFEL, + CodedOutputStream.encodeZigZag64(0x000000003FFFFFFFL)); + assertEquals(0x000000007FFFFFFFL, + CodedOutputStream.encodeZigZag64(0xFFFFFFFFC0000000L)); + assertEquals(0x00000000FFFFFFFEL, + CodedOutputStream.encodeZigZag64(0x000000007FFFFFFFL)); + assertEquals(0x00000000FFFFFFFFL, + CodedOutputStream.encodeZigZag64(0xFFFFFFFF80000000L)); + assertEquals(0xFFFFFFFFFFFFFFFEL, + CodedOutputStream.encodeZigZag64(0x7FFFFFFFFFFFFFFFL)); + assertEquals(0xFFFFFFFFFFFFFFFFL, + CodedOutputStream.encodeZigZag64(0x8000000000000000L)); + + // Some easier-to-verify round-trip tests. The inputs (other than 0, 1, -1) + // were chosen semi-randomly via keyboard bashing. + assertEquals(0, + CodedOutputStream.encodeZigZag32(CodedInputStream.decodeZigZag32(0))); + assertEquals(1, + CodedOutputStream.encodeZigZag32(CodedInputStream.decodeZigZag32(1))); + assertEquals(-1, + CodedOutputStream.encodeZigZag32(CodedInputStream.decodeZigZag32(-1))); + assertEquals(14927, + CodedOutputStream.encodeZigZag32(CodedInputStream.decodeZigZag32(14927))); + assertEquals(-3612, + CodedOutputStream.encodeZigZag32(CodedInputStream.decodeZigZag32(-3612))); + + assertEquals(0, + CodedOutputStream.encodeZigZag64(CodedInputStream.decodeZigZag64(0))); + assertEquals(1, + CodedOutputStream.encodeZigZag64(CodedInputStream.decodeZigZag64(1))); + assertEquals(-1, + CodedOutputStream.encodeZigZag64(CodedInputStream.decodeZigZag64(-1))); + assertEquals(14927, + CodedOutputStream.encodeZigZag64(CodedInputStream.decodeZigZag64(14927))); + assertEquals(-3612, + CodedOutputStream.encodeZigZag64(CodedInputStream.decodeZigZag64(-3612))); + + assertEquals(856912304801416L, + CodedOutputStream.encodeZigZag64( + CodedInputStream.decodeZigZag64( + 856912304801416L))); + assertEquals(-75123905439571256L, + CodedOutputStream.encodeZigZag64( + CodedInputStream.decodeZigZag64( + -75123905439571256L))); + } + + /** Tests writing a whole message with every field type. */ + public void testWriteWholeMessage() throws Exception { + TestAllTypes message = TestUtil.getAllSet(); + + byte[] rawBytes = message.toByteArray(); + assertEqualBytes(TestUtil.getGoldenMessage().toByteArray(), rawBytes); + + // Try different block sizes. + for (int blockSize = 1; blockSize < 256; blockSize *= 2) { + ByteArrayOutputStream rawOutput = new ByteArrayOutputStream(); + CodedOutputStream output = + CodedOutputStream.newInstance(rawOutput, blockSize); + message.writeTo(output); + output.flush(); + assertEqualBytes(rawBytes, rawOutput.toByteArray()); + } + } +} diff --git a/java/src/test/java/com/google/protobuf/DescriptorsTest.java b/java/src/test/java/com/google/protobuf/DescriptorsTest.java new file mode 100644 index 00000000..98d6d01a --- /dev/null +++ b/java/src/test/java/com/google/protobuf/DescriptorsTest.java @@ -0,0 +1,313 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.protobuf; + +import com.google.protobuf.Descriptors.FileDescriptor; +import com.google.protobuf.Descriptors.Descriptor; +import com.google.protobuf.Descriptors.FieldDescriptor; +import com.google.protobuf.Descriptors.EnumDescriptor; +import com.google.protobuf.Descriptors.EnumValueDescriptor; +import com.google.protobuf.Descriptors.ServiceDescriptor; +import com.google.protobuf.Descriptors.MethodDescriptor; + +import com.google.protobuf.test.UnittestImport; +import com.google.protobuf.test.UnittestImport.ImportEnum; +import com.google.protobuf.test.UnittestImport.ImportMessage; +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.TestExtremeDefaultValues; +import protobuf_unittest.UnittestProto.TestRequired; +import protobuf_unittest.UnittestProto.TestService; + + +import junit.framework.TestCase; + +import java.util.Arrays; +import java.util.Collections; + +/** + * Unit test for {@link Descriptors}. + * + * @author kenton@google.com Kenton Varda + */ +public class DescriptorsTest extends TestCase { + public void testFileDescriptor() throws Exception { + FileDescriptor file = UnittestProto.getDescriptor(); + + assertEquals("google/protobuf/unittest.proto", file.getName()); + assertEquals("protobuf_unittest", file.getPackage()); + + assertEquals("UnittestProto", file.getOptions().getJavaOuterClassname()); + assertEquals("google/protobuf/unittest.proto", + file.toProto().getName()); + + assertEquals(Arrays.asList(UnittestImport.getDescriptor()), + file.getDependencies()); + + Descriptor messageType = TestAllTypes.getDescriptor(); + assertEquals(messageType, file.getMessageTypes().get(0)); + assertEquals(messageType, file.findMessageTypeByName("TestAllTypes")); + assertNull(file.findMessageTypeByName("NoSuchType")); + assertNull(file.findMessageTypeByName("protobuf_unittest.TestAllTypes")); + for (int i = 0; i < file.getMessageTypes().size(); i++) { + assertEquals(i, file.getMessageTypes().get(i).getIndex()); + } + + EnumDescriptor enumType = ForeignEnum.getDescriptor(); + assertEquals(enumType, file.getEnumTypes().get(0)); + assertEquals(enumType, file.findEnumTypeByName("ForeignEnum")); + assertNull(file.findEnumTypeByName("NoSuchType")); + assertNull(file.findEnumTypeByName("protobuf_unittest.ForeignEnum")); + assertEquals(Arrays.asList(ImportEnum.getDescriptor()), + UnittestImport.getDescriptor().getEnumTypes()); + for (int i = 0; i < file.getEnumTypes().size(); i++) { + assertEquals(i, file.getEnumTypes().get(i).getIndex()); + } + + ServiceDescriptor service = TestService.getDescriptor(); + assertEquals(service, file.getServices().get(0)); + assertEquals(service, file.findServiceByName("TestService")); + assertNull(file.findServiceByName("NoSuchType")); + assertNull(file.findServiceByName("protobuf_unittest.TestService")); + assertEquals(Collections.emptyList(), + UnittestImport.getDescriptor().getServices()); + for (int i = 0; i < file.getServices().size(); i++) { + assertEquals(i, file.getServices().get(i).getIndex()); + } + + FieldDescriptor extension = + UnittestProto.optionalInt32Extension.getDescriptor(); + assertEquals(extension, file.getExtensions().get(0)); + assertEquals(extension, + file.findExtensionByName("optional_int32_extension")); + assertNull(file.findExtensionByName("no_such_ext")); + assertNull(file.findExtensionByName( + "protobuf_unittest.optional_int32_extension")); + assertEquals(Collections.emptyList(), + UnittestImport.getDescriptor().getExtensions()); + for (int i = 0; i < file.getExtensions().size(); i++) { + assertEquals(i, file.getExtensions().get(i).getIndex()); + } + } + + public void testDescriptor() throws Exception { + Descriptor messageType = TestAllTypes.getDescriptor(); + Descriptor nestedType = TestAllTypes.NestedMessage.getDescriptor(); + + assertEquals("TestAllTypes", messageType.getName()); + assertEquals("protobuf_unittest.TestAllTypes", messageType.getFullName()); + assertEquals(UnittestProto.getDescriptor(), messageType.getFile()); + assertNull(messageType.getContainingType()); + assertEquals(DescriptorProtos.MessageOptions.getDefaultInstance(), + messageType.getOptions()); + assertEquals("TestAllTypes", messageType.toProto().getName()); + + assertEquals("NestedMessage", nestedType.getName()); + assertEquals("protobuf_unittest.TestAllTypes.NestedMessage", + nestedType.getFullName()); + assertEquals(UnittestProto.getDescriptor(), nestedType.getFile()); + assertEquals(messageType, nestedType.getContainingType()); + + FieldDescriptor field = messageType.getFields().get(0); + assertEquals("optional_int32", field.getName()); + assertEquals(field, messageType.findFieldByName("optional_int32")); + assertNull(messageType.findFieldByName("no_such_field")); + assertEquals(field, messageType.findFieldByNumber(1)); + assertNull(messageType.findFieldByNumber(571283)); + for (int i = 0; i < messageType.getFields().size(); i++) { + assertEquals(i, messageType.getFields().get(i).getIndex()); + } + + assertEquals(nestedType, messageType.getNestedTypes().get(0)); + assertEquals(nestedType, messageType.findNestedTypeByName("NestedMessage")); + assertNull(messageType.findNestedTypeByName("NoSuchType")); + for (int i = 0; i < messageType.getNestedTypes().size(); i++) { + assertEquals(i, messageType.getNestedTypes().get(i).getIndex()); + } + + EnumDescriptor enumType = TestAllTypes.NestedEnum.getDescriptor(); + assertEquals(enumType, messageType.getEnumTypes().get(0)); + assertEquals(enumType, messageType.findEnumTypeByName("NestedEnum")); + assertNull(messageType.findEnumTypeByName("NoSuchType")); + for (int i = 0; i < messageType.getEnumTypes().size(); i++) { + assertEquals(i, messageType.getEnumTypes().get(i).getIndex()); + } + } + + public void testFieldDescriptor() throws Exception { + Descriptor messageType = TestAllTypes.getDescriptor(); + FieldDescriptor primitiveField = + messageType.findFieldByName("optional_int32"); + FieldDescriptor enumField = + messageType.findFieldByName("optional_nested_enum"); + FieldDescriptor messageField = + messageType.findFieldByName("optional_foreign_message"); + FieldDescriptor cordField = + messageType.findFieldByName("optional_cord"); + FieldDescriptor extension = + UnittestProto.optionalInt32Extension.getDescriptor(); + FieldDescriptor nestedExtension = TestRequired.single.getDescriptor(); + + assertEquals("optional_int32", primitiveField.getName()); + assertEquals("protobuf_unittest.TestAllTypes.optional_int32", + primitiveField.getFullName()); + assertEquals(1, primitiveField.getNumber()); + assertEquals(messageType, primitiveField.getContainingType()); + assertEquals(UnittestProto.getDescriptor(), primitiveField.getFile()); + assertEquals(FieldDescriptor.Type.INT32, primitiveField.getType()); + assertEquals(FieldDescriptor.JavaType.INT, primitiveField.getJavaType()); + assertEquals(DescriptorProtos.FieldOptions.getDefaultInstance(), + primitiveField.getOptions()); + assertFalse(primitiveField.isExtension()); + assertEquals("optional_int32", primitiveField.toProto().getName()); + + assertEquals("optional_nested_enum", enumField.getName()); + assertEquals(FieldDescriptor.Type.ENUM, enumField.getType()); + assertEquals(FieldDescriptor.JavaType.ENUM, enumField.getJavaType()); + assertEquals(TestAllTypes.NestedEnum.getDescriptor(), + enumField.getEnumType()); + + assertEquals("optional_foreign_message", messageField.getName()); + assertEquals(FieldDescriptor.Type.MESSAGE, messageField.getType()); + assertEquals(FieldDescriptor.JavaType.MESSAGE, messageField.getJavaType()); + assertEquals(ForeignMessage.getDescriptor(), messageField.getMessageType()); + + assertEquals("optional_cord", cordField.getName()); + assertEquals(FieldDescriptor.Type.STRING, cordField.getType()); + assertEquals(FieldDescriptor.JavaType.STRING, cordField.getJavaType()); + assertEquals(DescriptorProtos.FieldOptions.CType.CORD, + cordField.getOptions().getCtype()); + + assertEquals("optional_int32_extension", extension.getName()); + assertEquals("protobuf_unittest.optional_int32_extension", + extension.getFullName()); + assertEquals(1, extension.getNumber()); + assertEquals(TestAllExtensions.getDescriptor(), + extension.getContainingType()); + assertEquals(UnittestProto.getDescriptor(), extension.getFile()); + assertEquals(FieldDescriptor.Type.INT32, extension.getType()); + assertEquals(FieldDescriptor.JavaType.INT, extension.getJavaType()); + assertEquals(DescriptorProtos.FieldOptions.getDefaultInstance(), + extension.getOptions()); + assertTrue(extension.isExtension()); + assertEquals(null, extension.getExtensionScope()); + assertEquals("optional_int32_extension", extension.toProto().getName()); + + assertEquals("single", nestedExtension.getName()); + assertEquals("protobuf_unittest.TestRequired.single", + nestedExtension.getFullName()); + assertEquals(TestRequired.getDescriptor(), + nestedExtension.getExtensionScope()); + } + + public void testFieldDescriptorLabel() throws Exception { + FieldDescriptor requiredField = + TestRequired.getDescriptor().findFieldByName("a"); + FieldDescriptor optionalField = + TestAllTypes.getDescriptor().findFieldByName("optional_int32"); + FieldDescriptor repeatedField = + TestAllTypes.getDescriptor().findFieldByName("repeated_int32"); + + assertTrue(requiredField.isRequired()); + assertFalse(requiredField.isRepeated()); + assertFalse(optionalField.isRequired()); + assertFalse(optionalField.isRepeated()); + assertFalse(repeatedField.isRequired()); + assertTrue(repeatedField.isRepeated()); + } + + public void testFieldDescriptorDefault() throws Exception { + Descriptor d = TestAllTypes.getDescriptor(); + assertFalse(d.findFieldByName("optional_int32").hasDefaultValue()); + assertEquals(0, d.findFieldByName("optional_int32").getDefaultValue()); + assertTrue(d.findFieldByName("default_int32").hasDefaultValue()); + assertEquals(41, d.findFieldByName("default_int32").getDefaultValue()); + + d = TestExtremeDefaultValues.getDescriptor(); + assertEquals( + ByteString.copyFrom( + "\0\001\007\b\f\n\r\t\013\\\'\"\u00fe".getBytes("ISO-8859-1")), + d.findFieldByName("escaped_bytes").getDefaultValue()); + assertEquals(-1, d.findFieldByName("large_uint32").getDefaultValue()); + assertEquals(-1L, d.findFieldByName("large_uint64").getDefaultValue()); + } + + public void testEnumDescriptor() throws Exception { + EnumDescriptor enumType = ForeignEnum.getDescriptor(); + EnumDescriptor nestedType = TestAllTypes.NestedEnum.getDescriptor(); + + assertEquals("ForeignEnum", enumType.getName()); + assertEquals("protobuf_unittest.ForeignEnum", enumType.getFullName()); + assertEquals(UnittestProto.getDescriptor(), enumType.getFile()); + assertNull(enumType.getContainingType()); + assertEquals(DescriptorProtos.EnumOptions.getDefaultInstance(), + enumType.getOptions()); + + assertEquals("NestedEnum", nestedType.getName()); + assertEquals("protobuf_unittest.TestAllTypes.NestedEnum", + nestedType.getFullName()); + assertEquals(UnittestProto.getDescriptor(), nestedType.getFile()); + assertEquals(TestAllTypes.getDescriptor(), nestedType.getContainingType()); + + EnumValueDescriptor value = ForeignEnum.FOREIGN_FOO.getValueDescriptor(); + assertEquals(value, enumType.getValues().get(0)); + assertEquals("FOREIGN_FOO", value.getName()); + assertEquals(4, value.getNumber()); + assertEquals(value, enumType.findValueByName("FOREIGN_FOO")); + assertEquals(value, enumType.findValueByNumber(4)); + assertNull(enumType.findValueByName("NO_SUCH_VALUE")); + for (int i = 0; i < enumType.getValues().size(); i++) { + assertEquals(i, enumType.getValues().get(i).getIndex()); + } + } + + public void testServiceDescriptor() throws Exception { + ServiceDescriptor service = TestService.getDescriptor(); + + assertEquals("TestService", service.getName()); + assertEquals("protobuf_unittest.TestService", service.getFullName()); + assertEquals(UnittestProto.getDescriptor(), service.getFile()); + + assertEquals(2, service.getMethods().size()); + + MethodDescriptor fooMethod = service.getMethods().get(0); + assertEquals("Foo", fooMethod.getName()); + assertEquals(UnittestProto.FooRequest.getDescriptor(), + fooMethod.getInputType()); + assertEquals(UnittestProto.FooResponse.getDescriptor(), + fooMethod.getOutputType()); + assertEquals(fooMethod, service.findMethodByName("Foo")); + + MethodDescriptor barMethod = service.getMethods().get(1); + assertEquals("Bar", barMethod.getName()); + assertEquals(UnittestProto.BarRequest.getDescriptor(), + barMethod.getInputType()); + assertEquals(UnittestProto.BarResponse.getDescriptor(), + barMethod.getOutputType()); + assertEquals(barMethod, service.findMethodByName("Bar")); + + assertNull(service.findMethodByName("NoSuchMethod")); + + for (int i = 0; i < service.getMethods().size(); i++) { + assertEquals(i, service.getMethods().get(i).getIndex()); + } + } + +} diff --git a/java/src/test/java/com/google/protobuf/DynamicMessageTest.java b/java/src/test/java/com/google/protobuf/DynamicMessageTest.java new file mode 100644 index 00000000..7a458981 --- /dev/null +++ b/java/src/test/java/com/google/protobuf/DynamicMessageTest.java @@ -0,0 +1,120 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.protobuf; + +import protobuf_unittest.UnittestProto.TestAllTypes; +import protobuf_unittest.UnittestProto.TestAllExtensions; + +import junit.framework.TestCase; + +/** + * Unit test for {@link DynamicMessage}. See also {@link MessageTest}, which + * tests some {@link DynamicMessage} functionality. + * + * @author kenton@google.com Kenton Varda + */ +public class DynamicMessageTest extends TestCase { + TestUtil.ReflectionTester reflectionTester = + new TestUtil.ReflectionTester(TestAllTypes.getDescriptor(), null); + + TestUtil.ReflectionTester extensionsReflectionTester = + new TestUtil.ReflectionTester(TestAllExtensions.getDescriptor(), + TestUtil.getExtensionRegistry()); + + public void testDynamicMessageAccessors() throws Exception { + Message.Builder builder = + DynamicMessage.newBuilder(TestAllTypes.getDescriptor()); + reflectionTester.setAllFieldsViaReflection(builder); + Message message = builder.build(); + reflectionTester.assertAllFieldsSetViaReflection(message); + } + + public void testDynamicMessageExtensionAccessors() throws Exception { + // We don't need to extensively test DynamicMessage's handling of + // extensions because, frankly, it doesn't do anything special with them. + // It treats them just like any other fields. + Message.Builder builder = + DynamicMessage.newBuilder(TestAllExtensions.getDescriptor()); + extensionsReflectionTester.setAllFieldsViaReflection(builder); + Message message = builder.build(); + extensionsReflectionTester.assertAllFieldsSetViaReflection(message); + } + + public void testDynamicMessageRepeatedSetters() throws Exception { + Message.Builder builder = + DynamicMessage.newBuilder(TestAllTypes.getDescriptor()); + reflectionTester.setAllFieldsViaReflection(builder); + reflectionTester.modifyRepeatedFieldsViaReflection(builder); + Message message = builder.build(); + reflectionTester.assertRepeatedFieldsModifiedViaReflection(message); + } + + public void testDynamicMessageDefaults() throws Exception { + reflectionTester.assertClearViaReflection( + DynamicMessage.getDefaultInstance(TestAllTypes.getDescriptor())); + reflectionTester.assertClearViaReflection( + DynamicMessage.newBuilder(TestAllTypes.getDescriptor()).build()); + } + + public void testDynamicMessageSerializedSize() throws Exception { + TestAllTypes message = TestUtil.getAllSet(); + + Message.Builder dynamicBuilder = + DynamicMessage.newBuilder(TestAllTypes.getDescriptor()); + reflectionTester.setAllFieldsViaReflection(dynamicBuilder); + Message dynamicMessage = dynamicBuilder.build(); + + assertEquals(message.getSerializedSize(), + dynamicMessage.getSerializedSize()); + } + + public void testDynamicMessageSerialization() throws Exception { + Message.Builder builder = + DynamicMessage.newBuilder(TestAllTypes.getDescriptor()); + reflectionTester.setAllFieldsViaReflection(builder); + Message message = builder.build(); + + ByteString rawBytes = message.toByteString(); + TestAllTypes message2 = TestAllTypes.parseFrom(rawBytes); + + TestUtil.assertAllFieldsSet(message2); + + // In fact, the serialized forms should be exactly the same, byte-for-byte. + assertEquals(TestUtil.getAllSet().toByteString(), rawBytes); + } + + public void testDynamicMessageParsing() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TestUtil.setAllFields(builder); + TestAllTypes message = builder.build(); + + ByteString rawBytes = message.toByteString(); + + Message message2 = + DynamicMessage.parseFrom(TestAllTypes.getDescriptor(), rawBytes); + reflectionTester.assertAllFieldsSetViaReflection(message2); + } + + public void testDynamicMessageCopy() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TestUtil.setAllFields(builder); + TestAllTypes message = builder.build(); + + DynamicMessage copy = DynamicMessage.newBuilder(message).build(); + reflectionTester.assertAllFieldsSetViaReflection(copy); + } +} diff --git a/java/src/test/java/com/google/protobuf/GeneratedMessageTest.java b/java/src/test/java/com/google/protobuf/GeneratedMessageTest.java new file mode 100644 index 00000000..30d73d28 --- /dev/null +++ b/java/src/test/java/com/google/protobuf/GeneratedMessageTest.java @@ -0,0 +1,246 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.protobuf; + +import protobuf_unittest.UnittestProto; +import protobuf_unittest.UnittestProto.ForeignMessage; +import protobuf_unittest.UnittestProto.ForeignEnum; +import protobuf_unittest.UnittestProto.TestAllTypes; +import protobuf_unittest.UnittestProto.TestAllExtensions; +import protobuf_unittest.UnittestProto.TestExtremeDefaultValues; +import protobuf_unittest.MultipleFilesTestProto; +import protobuf_unittest.MessageWithNoOuter; +import protobuf_unittest.EnumWithNoOuter; +import protobuf_unittest.ServiceWithNoOuter; + +import junit.framework.TestCase; +import java.util.Arrays; + +/** + * Unit test for generated messages and generated code. See also + * {@link MessageTest}, which tests some generated message functionality. + * + * @author kenton@google.com Kenton Varda + */ +public class GeneratedMessageTest extends TestCase { + TestUtil.ReflectionTester reflectionTester = + new TestUtil.ReflectionTester(TestAllTypes.getDescriptor(), null); + + public void testDefaultInstance() throws Exception { + assertSame(TestAllTypes.getDefaultInstance(), + TestAllTypes.getDefaultInstance().getDefaultInstanceForType()); + assertSame(TestAllTypes.getDefaultInstance(), + TestAllTypes.newBuilder().getDefaultInstanceForType()); + } + + public void testAccessors() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TestUtil.setAllFields(builder); + TestAllTypes message = builder.build(); + TestUtil.assertAllFieldsSet(message); + } + + public void testRepeatedSetters() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TestUtil.setAllFields(builder); + TestUtil.modifyRepeatedFields(builder); + TestAllTypes message = builder.build(); + TestUtil.assertRepeatedFieldsModified(message); + } + + public void testRepeatedAppend() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + + builder.addAllRepeatedInt32(Arrays.asList(1, 2, 3, 4)); + builder.addAllRepeatedForeignEnum(Arrays.asList(ForeignEnum.FOREIGN_BAZ)); + + ForeignMessage foreignMessage = + ForeignMessage.newBuilder().setC(12).build(); + builder.addAllRepeatedForeignMessage(Arrays.asList(foreignMessage)); + + TestAllTypes message = builder.build(); + assertEquals(message.getRepeatedInt32List(), Arrays.asList(1, 2, 3, 4)); + assertEquals(message.getRepeatedForeignEnumList(), + Arrays.asList(ForeignEnum.FOREIGN_BAZ)); + assertEquals(1, message.getRepeatedForeignMessageCount()); + assertEquals(12, message.getRepeatedForeignMessage(0).getC()); + } + + public void testSettingForeignMessageUsingBuilder() throws Exception { + TestAllTypes message = TestAllTypes.newBuilder() + // Pass builder for foreign message instance. + .setOptionalForeignMessage(ForeignMessage.newBuilder().setC(123)) + .build(); + TestAllTypes expectedMessage = TestAllTypes.newBuilder() + // Create expected version passing foreign message instance explicitly. + .setOptionalForeignMessage( + ForeignMessage.newBuilder().setC(123).build()) + .build(); + // TODO(ngd): Upgrade to using real #equals method once implemented + assertEquals(expectedMessage.toString(), message.toString()); + } + + public void testSettingRepeatedForeignMessageUsingBuilder() throws Exception { + TestAllTypes message = TestAllTypes.newBuilder() + // Pass builder for foreign message instance. + .addRepeatedForeignMessage(ForeignMessage.newBuilder().setC(456)) + .build(); + TestAllTypes expectedMessage = TestAllTypes.newBuilder() + // Create expected version passing foreign message instance explicitly. + .addRepeatedForeignMessage( + ForeignMessage.newBuilder().setC(456).build()) + .build(); + assertEquals(expectedMessage.toString(), message.toString()); + } + + public void testDefaults() throws Exception { + TestUtil.assertClear(TestAllTypes.getDefaultInstance()); + TestUtil.assertClear(TestAllTypes.newBuilder().build()); + + assertEquals("\u1234", + TestExtremeDefaultValues.getDefaultInstance().getUtf8String()); + } + + public void testReflectionGetters() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TestUtil.setAllFields(builder); + TestAllTypes message = builder.build(); + reflectionTester.assertAllFieldsSetViaReflection(message); + } + + public void testReflectionSetters() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + reflectionTester.setAllFieldsViaReflection(builder); + TestAllTypes message = builder.build(); + TestUtil.assertAllFieldsSet(message); + } + + public void testReflectionRepeatedSetters() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + reflectionTester.setAllFieldsViaReflection(builder); + reflectionTester.modifyRepeatedFieldsViaReflection(builder); + TestAllTypes message = builder.build(); + TestUtil.assertRepeatedFieldsModified(message); + } + + public void testReflectionDefaults() throws Exception { + reflectionTester.assertClearViaReflection( + TestAllTypes.getDefaultInstance()); + reflectionTester.assertClearViaReflection( + TestAllTypes.newBuilder().build()); + } + + // ================================================================= + // Extensions. + + TestUtil.ReflectionTester extensionsReflectionTester = + new TestUtil.ReflectionTester(TestAllExtensions.getDescriptor(), + TestUtil.getExtensionRegistry()); + + public void testExtensionAccessors() throws Exception { + TestAllExtensions.Builder builder = TestAllExtensions.newBuilder(); + TestUtil.setAllExtensions(builder); + TestAllExtensions message = builder.build(); + TestUtil.assertAllExtensionsSet(message); + } + + public void testExtensionRepeatedSetters() throws Exception { + TestAllExtensions.Builder builder = TestAllExtensions.newBuilder(); + TestUtil.setAllExtensions(builder); + TestUtil.modifyRepeatedExtensions(builder); + TestAllExtensions message = builder.build(); + TestUtil.assertRepeatedExtensionsModified(message); + } + + public void testExtensionDefaults() throws Exception { + TestUtil.assertExtensionsClear(TestAllExtensions.getDefaultInstance()); + TestUtil.assertExtensionsClear(TestAllExtensions.newBuilder().build()); + } + + public void testExtensionReflectionGetters() throws Exception { + TestAllExtensions.Builder builder = TestAllExtensions.newBuilder(); + TestUtil.setAllExtensions(builder); + TestAllExtensions message = builder.build(); + extensionsReflectionTester.assertAllFieldsSetViaReflection(message); + } + + public void testExtensionReflectionSetters() throws Exception { + TestAllExtensions.Builder builder = TestAllExtensions.newBuilder(); + extensionsReflectionTester.setAllFieldsViaReflection(builder); + TestAllExtensions message = builder.build(); + TestUtil.assertAllExtensionsSet(message); + } + + public void testExtensionReflectionRepeatedSetters() throws Exception { + TestAllExtensions.Builder builder = TestAllExtensions.newBuilder(); + extensionsReflectionTester.setAllFieldsViaReflection(builder); + extensionsReflectionTester.modifyRepeatedFieldsViaReflection(builder); + TestAllExtensions message = builder.build(); + TestUtil.assertRepeatedExtensionsModified(message); + } + + public void testExtensionReflectionDefaults() throws Exception { + extensionsReflectionTester.assertClearViaReflection( + TestAllExtensions.getDefaultInstance()); + extensionsReflectionTester.assertClearViaReflection( + TestAllExtensions.newBuilder().build()); + } + + public void testClearExtension() throws Exception { + // clearExtension() is not actually used in TestUtil, so try it manually. + assertFalse( + TestAllExtensions.newBuilder() + .setExtension(UnittestProto.optionalInt32Extension, 1) + .clearExtension(UnittestProto.optionalInt32Extension) + .hasExtension(UnittestProto.optionalInt32Extension)); + assertEquals(0, + TestAllExtensions.newBuilder() + .addExtension(UnittestProto.repeatedInt32Extension, 1) + .clearExtension(UnittestProto.repeatedInt32Extension) + .getExtensionCount(UnittestProto.repeatedInt32Extension)); + } + + // ================================================================= + // multiple_files_test + + public void testMultipleFilesOption() throws Exception { + // We mostly just want to check that things compile. + MessageWithNoOuter message = + MessageWithNoOuter.newBuilder() + .setNested(MessageWithNoOuter.NestedMessage.newBuilder().setI(1)) + .addForeign(TestAllTypes.newBuilder().setOptionalInt32(1)) + .setNestedEnum(MessageWithNoOuter.NestedEnum.BAZ) + .setForeignEnum(EnumWithNoOuter.BAR) + .build(); + assertEquals(message, MessageWithNoOuter.parseFrom(message.toByteString())); + + assertEquals(MultipleFilesTestProto.getDescriptor(), + MessageWithNoOuter.getDescriptor().getFile()); + + Descriptors.FieldDescriptor field = + MessageWithNoOuter.getDescriptor().findFieldByName("foreign_enum"); + assertEquals(EnumWithNoOuter.BAR.getValueDescriptor(), + message.getField(field)); + + assertEquals(MultipleFilesTestProto.getDescriptor(), + ServiceWithNoOuter.getDescriptor().getFile()); + + assertFalse( + TestAllExtensions.getDefaultInstance().hasExtension( + MultipleFilesTestProto.extensionWithOuter)); + } +} diff --git a/java/src/test/java/com/google/protobuf/MessageTest.java b/java/src/test/java/com/google/protobuf/MessageTest.java new file mode 100644 index 00000000..3dece1ff --- /dev/null +++ b/java/src/test/java/com/google/protobuf/MessageTest.java @@ -0,0 +1,299 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.protobuf; + +import protobuf_unittest.UnittestProto.TestAllTypes; +import protobuf_unittest.UnittestProto.TestAllExtensions; +import protobuf_unittest.UnittestProto.TestRequired; +import protobuf_unittest.UnittestProto.TestRequiredForeign; +import protobuf_unittest.UnittestProto.ForeignMessage; + +import junit.framework.TestCase; + +/** + * Misc. unit tests for message operations that apply to both generated + * and dynamic messages. + * + * @author kenton@google.com Kenton Varda + */ +public class MessageTest extends TestCase { + // ================================================================= + // Message-merging tests. + + static final TestAllTypes MERGE_SOURCE = + TestAllTypes.newBuilder() + .setOptionalInt32(1) + .setOptionalString("foo") + .setOptionalForeignMessage(ForeignMessage.getDefaultInstance()) + .addRepeatedString("bar") + .build(); + + static final TestAllTypes MERGE_DEST = + TestAllTypes.newBuilder() + .setOptionalInt64(2) + .setOptionalString("baz") + .setOptionalForeignMessage(ForeignMessage.newBuilder().setC(3).build()) + .addRepeatedString("qux") + .build(); + + static final String MERGE_RESULT_TEXT = + "optional_int32: 1\n" + + "optional_int64: 2\n" + + "optional_string: \"foo\"\n" + + "optional_foreign_message {\n" + + " c: 3\n" + + "}\n" + + "repeated_string: \"qux\"\n" + + "repeated_string: \"bar\"\n"; + + public void testMergeFrom() throws Exception { + TestAllTypes result = + TestAllTypes.newBuilder(MERGE_DEST) + .mergeFrom(MERGE_SOURCE).build(); + + assertEquals(MERGE_RESULT_TEXT, result.toString()); + } + + /** + * Test merging a DynamicMessage into a GeneratedMessage. As long as they + * have the same descriptor, this should work, but it is an entirely different + * code path. + */ + public void testMergeFromDynamic() throws Exception { + TestAllTypes result = + TestAllTypes.newBuilder(MERGE_DEST) + .mergeFrom(DynamicMessage.newBuilder(MERGE_SOURCE).build()) + .build(); + + assertEquals(MERGE_RESULT_TEXT, result.toString()); + } + + /** Test merging two DynamicMessages. */ + public void testDynamicMergeFrom() throws Exception { + DynamicMessage result = + DynamicMessage.newBuilder(MERGE_DEST) + .mergeFrom(DynamicMessage.newBuilder(MERGE_SOURCE).build()) + .build(); + + assertEquals(MERGE_RESULT_TEXT, result.toString()); + } + + // ================================================================= + // Required-field-related tests. + + private static final TestRequired TEST_REQUIRED_UNINITIALIZED = + TestRequired.getDefaultInstance(); + private static final TestRequired TEST_REQUIRED_INITIALIZED = + TestRequired.newBuilder().setA(1).setB(2).setC(3).build(); + + public void testRequired() throws Exception { + TestRequired.Builder builder = TestRequired.newBuilder(); + + assertFalse(builder.isInitialized()); + builder.setA(1); + assertFalse(builder.isInitialized()); + builder.setB(1); + assertFalse(builder.isInitialized()); + builder.setC(1); + assertTrue(builder.isInitialized()); + } + + public void testRequiredForeign() throws Exception { + TestRequiredForeign.Builder builder = TestRequiredForeign.newBuilder(); + + assertTrue(builder.isInitialized()); + + builder.setOptionalMessage(TEST_REQUIRED_UNINITIALIZED); + assertFalse(builder.isInitialized()); + + builder.setOptionalMessage(TEST_REQUIRED_INITIALIZED); + assertTrue(builder.isInitialized()); + + builder.addRepeatedMessage(TEST_REQUIRED_UNINITIALIZED); + assertFalse(builder.isInitialized()); + + builder.setRepeatedMessage(0, TEST_REQUIRED_INITIALIZED); + assertTrue(builder.isInitialized()); + } + + public void testRequiredExtension() throws Exception { + TestAllExtensions.Builder builder = TestAllExtensions.newBuilder(); + + assertTrue(builder.isInitialized()); + + builder.setExtension(TestRequired.single, TEST_REQUIRED_UNINITIALIZED); + assertFalse(builder.isInitialized()); + + builder.setExtension(TestRequired.single, TEST_REQUIRED_INITIALIZED); + assertTrue(builder.isInitialized()); + + builder.addExtension(TestRequired.multi, TEST_REQUIRED_UNINITIALIZED); + assertFalse(builder.isInitialized()); + + builder.setExtension(TestRequired.multi, 0, TEST_REQUIRED_INITIALIZED); + assertTrue(builder.isInitialized()); + } + + public void testRequiredDynamic() throws Exception { + Descriptors.Descriptor descriptor = TestRequired.getDescriptor(); + DynamicMessage.Builder builder = DynamicMessage.newBuilder(descriptor); + + assertFalse(builder.isInitialized()); + builder.setField(descriptor.findFieldByName("a"), 1); + assertFalse(builder.isInitialized()); + builder.setField(descriptor.findFieldByName("b"), 1); + assertFalse(builder.isInitialized()); + builder.setField(descriptor.findFieldByName("c"), 1); + assertTrue(builder.isInitialized()); + } + + public void testRequiredDynamicForeign() throws Exception { + Descriptors.Descriptor descriptor = TestRequiredForeign.getDescriptor(); + DynamicMessage.Builder builder = DynamicMessage.newBuilder(descriptor); + + assertTrue(builder.isInitialized()); + + builder.setField(descriptor.findFieldByName("optional_message"), + TEST_REQUIRED_UNINITIALIZED); + assertFalse(builder.isInitialized()); + + builder.setField(descriptor.findFieldByName("optional_message"), + TEST_REQUIRED_INITIALIZED); + assertTrue(builder.isInitialized()); + + builder.addRepeatedField(descriptor.findFieldByName("repeated_message"), + TEST_REQUIRED_UNINITIALIZED); + assertFalse(builder.isInitialized()); + + builder.setRepeatedField(descriptor.findFieldByName("repeated_message"), 0, + TEST_REQUIRED_INITIALIZED); + assertTrue(builder.isInitialized()); + } + + public void testUninitializedException() throws Exception { + try { + TestRequired.newBuilder().build(); + fail("Should have thrown an exception."); + } catch (UninitializedMessageException e) { + assertEquals("Message missing required fields: a, b, c", e.getMessage()); + } + } + + public void testBuildPartial() throws Exception { + // We're mostly testing that no exception is thrown. + TestRequired message = TestRequired.newBuilder().buildPartial(); + assertFalse(message.isInitialized()); + } + + public void testNestedUninitializedException() throws Exception { + try { + TestRequiredForeign.newBuilder() + .setOptionalMessage(TEST_REQUIRED_UNINITIALIZED) + .addRepeatedMessage(TEST_REQUIRED_UNINITIALIZED) + .addRepeatedMessage(TEST_REQUIRED_UNINITIALIZED) + .build(); + fail("Should have thrown an exception."); + } catch (UninitializedMessageException e) { + assertEquals( + "Message missing required fields: " + + "optional_message.a, " + + "optional_message.b, " + + "optional_message.c, " + + "repeated_message[0].a, " + + "repeated_message[0].b, " + + "repeated_message[0].c, " + + "repeated_message[1].a, " + + "repeated_message[1].b, " + + "repeated_message[1].c", + e.getMessage()); + } + } + + public void testBuildNestedPartial() throws Exception { + // We're mostly testing that no exception is thrown. + TestRequiredForeign message = + TestRequiredForeign.newBuilder() + .setOptionalMessage(TEST_REQUIRED_UNINITIALIZED) + .addRepeatedMessage(TEST_REQUIRED_UNINITIALIZED) + .addRepeatedMessage(TEST_REQUIRED_UNINITIALIZED) + .buildPartial(); + assertFalse(message.isInitialized()); + } + + public void testParseUnititialized() throws Exception { + try { + TestRequired.parseFrom(ByteString.EMPTY); + fail("Should have thrown an exception."); + } catch (InvalidProtocolBufferException e) { + assertEquals("Message missing required fields: a, b, c", e.getMessage()); + } + } + + public void testParseNestedUnititialized() throws Exception { + ByteString data = + TestRequiredForeign.newBuilder() + .setOptionalMessage(TEST_REQUIRED_UNINITIALIZED) + .addRepeatedMessage(TEST_REQUIRED_UNINITIALIZED) + .addRepeatedMessage(TEST_REQUIRED_UNINITIALIZED) + .buildPartial().toByteString(); + + try { + TestRequiredForeign.parseFrom(data); + fail("Should have thrown an exception."); + } catch (InvalidProtocolBufferException e) { + assertEquals( + "Message missing required fields: " + + "optional_message.a, " + + "optional_message.b, " + + "optional_message.c, " + + "repeated_message[0].a, " + + "repeated_message[0].b, " + + "repeated_message[0].c, " + + "repeated_message[1].a, " + + "repeated_message[1].b, " + + "repeated_message[1].c", + e.getMessage()); + } + } + + public void testDynamicUninitializedException() throws Exception { + try { + DynamicMessage.newBuilder(TestRequired.getDescriptor()).build(); + fail("Should have thrown an exception."); + } catch (UninitializedMessageException e) { + assertEquals("Message missing required fields: a, b, c", e.getMessage()); + } + } + + public void testDynamicBuildPartial() throws Exception { + // We're mostly testing that no exception is thrown. + DynamicMessage message = + DynamicMessage.newBuilder(TestRequired.getDescriptor()) + .buildPartial(); + assertFalse(message.isInitialized()); + } + + public void testDynamicParseUnititialized() throws Exception { + try { + Descriptors.Descriptor descriptor = TestRequired.getDescriptor(); + DynamicMessage.parseFrom(descriptor, ByteString.EMPTY); + fail("Should have thrown an exception."); + } catch (InvalidProtocolBufferException e) { + assertEquals("Message missing required fields: a, b, c", e.getMessage()); + } + } +} diff --git a/java/src/test/java/com/google/protobuf/ServiceTest.java b/java/src/test/java/com/google/protobuf/ServiceTest.java new file mode 100644 index 00000000..2f83837b --- /dev/null +++ b/java/src/test/java/com/google/protobuf/ServiceTest.java @@ -0,0 +1,164 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.protobuf; + +import protobuf_unittest.UnittestProto.TestService; +import protobuf_unittest.UnittestProto.FooRequest; +import protobuf_unittest.UnittestProto.FooResponse; +import protobuf_unittest.UnittestProto.BarRequest; +import protobuf_unittest.UnittestProto.BarResponse; + +import org.easymock.classextension.EasyMock; +import org.easymock.classextension.IMocksControl; +import org.easymock.IArgumentMatcher; + +import junit.framework.TestCase; + +/** + * Tests services and stubs. + * + * @author kenton@google.com Kenton Varda + */ +public class ServiceTest extends TestCase { + private IMocksControl control; + private RpcController mockController; + + private final Descriptors.MethodDescriptor fooDescriptor = + TestService.getDescriptor().getMethods().get(0); + private final Descriptors.MethodDescriptor barDescriptor = + TestService.getDescriptor().getMethods().get(1); + + protected void setUp() throws Exception { + super.setUp(); + control = EasyMock.createStrictControl(); + mockController = control.createMock(RpcController.class); + } + + // ================================================================= + + /** Tests Service.callMethod(). */ + public void testCallMethod() throws Exception { + FooRequest fooRequest = FooRequest.newBuilder().build(); + BarRequest barRequest = BarRequest.newBuilder().build(); + MockCallback<Message> fooCallback = new MockCallback<Message>(); + MockCallback<Message> barCallback = new MockCallback<Message>(); + TestService mockService = control.createMock(TestService.class); + + mockService.foo(EasyMock.same(mockController), EasyMock.same(fooRequest), + this.<FooResponse>wrapsCallback(fooCallback)); + mockService.bar(EasyMock.same(mockController), EasyMock.same(barRequest), + this.<BarResponse>wrapsCallback(barCallback)); + control.replay(); + + mockService.callMethod(fooDescriptor, mockController, + fooRequest, fooCallback); + mockService.callMethod(barDescriptor, mockController, + barRequest, barCallback); + control.verify(); + } + + /** Tests Service.get{Request,Response}Prototype(). */ + public void testGetPrototype() throws Exception { + TestService mockService = control.createMock(TestService.class); + + assertSame(mockService.getRequestPrototype(fooDescriptor), + FooRequest.getDefaultInstance()); + assertSame(mockService.getResponsePrototype(fooDescriptor), + FooResponse.getDefaultInstance()); + assertSame(mockService.getRequestPrototype(barDescriptor), + BarRequest.getDefaultInstance()); + assertSame(mockService.getResponsePrototype(barDescriptor), + BarResponse.getDefaultInstance()); + } + + /** Tests generated stubs. */ + public void testStub() throws Exception { + FooRequest fooRequest = FooRequest.newBuilder().build(); + BarRequest barRequest = BarRequest.newBuilder().build(); + MockCallback<FooResponse> fooCallback = new MockCallback<FooResponse>(); + MockCallback<BarResponse> barCallback = new MockCallback<BarResponse>(); + RpcChannel mockChannel = control.createMock(RpcChannel.class); + TestService stub = TestService.newStub(mockChannel); + + mockChannel.callMethod( + EasyMock.same(fooDescriptor), + EasyMock.same(mockController), + EasyMock.same(fooRequest), + EasyMock.same(FooResponse.getDefaultInstance()), + this.<Message>wrapsCallback(fooCallback)); + mockChannel.callMethod( + EasyMock.same(barDescriptor), + EasyMock.same(mockController), + EasyMock.same(barRequest), + EasyMock.same(BarResponse.getDefaultInstance()), + this.<Message>wrapsCallback(barCallback)); + control.replay(); + + stub.foo(mockController, fooRequest, fooCallback); + stub.bar(mockController, barRequest, barCallback); + control.verify(); + } + + // ================================================================= + + /** + * wrapsCallback() is an EasyMock argument predicate. wrapsCallback(c) + * matches a callback if calling that callback causes c to be called. + * In other words, c wraps the given callback. + */ + private <Type extends Message> RpcCallback<Type> wrapsCallback( + MockCallback callback) { + EasyMock.reportMatcher(new WrapsCallback(callback)); + return null; + } + + /** The parameter to wrapsCallback() must be a MockCallback. */ + private static class MockCallback<Type extends Message> + implements RpcCallback<Type> { + private boolean called = false; + + public boolean isCalled() { return called; } + + public void reset() { called = false; } + public void run(Type message) { called = true; } + } + + /** Implementation of the wrapsCallback() argument matcher. */ + private static class WrapsCallback implements IArgumentMatcher { + private MockCallback callback; + + public WrapsCallback(MockCallback callback) { + this.callback = callback; + } + + @SuppressWarnings("unchecked") + public boolean matches(Object actual) { + if (!(actual instanceof RpcCallback)) { + return false; + } + RpcCallback actualCallback = (RpcCallback)actual; + + callback.reset(); + actualCallback.run(null); + return callback.isCalled(); + } + + public void appendTo(StringBuffer buffer) { + buffer.append("wrapsCallback(mockCallback)"); + } + } +} diff --git a/java/src/test/java/com/google/protobuf/TestUtil.java b/java/src/test/java/com/google/protobuf/TestUtil.java new file mode 100644 index 00000000..af493ad1 --- /dev/null +++ b/java/src/test/java/com/google/protobuf/TestUtil.java @@ -0,0 +1,2402 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Note: This file contains many lines over 80 characters. It even contains +// many lines over 100 characters, which fails a presubmit test. However, +// given the extremely repetitive nature of the file, I (kenton) feel that +// having similar components of each statement line up is more important than +// avoiding horizontal scrolling. So, I am bypassing the presubmit check. + +package com.google.protobuf; + +import protobuf_unittest.UnittestProto; +import protobuf_unittest.UnittestProto.TestAllTypes; +import protobuf_unittest.UnittestProto.TestAllExtensions; +import protobuf_unittest.UnittestProto.ForeignMessage; +import protobuf_unittest.UnittestProto.ForeignEnum; +import com.google.protobuf.test.UnittestImport.ImportMessage; +import com.google.protobuf.test.UnittestImport.ImportEnum; + +import junit.framework.Assert; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; + +/** + * Contains methods for setting all fields of {@code TestAllTypes} to + * some vaules as well as checking that all the fields are set to those values. + * These are useful for testing various protocol message features, e.g. + * set all fields of a message, serialize it, parse it, and check that all + * fields are set. + * + * @author kenton@google.com Kenton Varda + */ +class TestUtil { + private TestUtil() {} + + /** Helper to convert a String to ByteString. */ + private 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); + } + } + + /** + * Get a {@code TestAllTypes} with all fields set as they would be by + * {@link #setAllFields(TestAllTypes.Builder)}. + */ + public static TestAllTypes getAllSet() { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + setAllFields(builder); + return builder.build(); + } + + /** + * Get a {@code TestAllExtensions} with all fields set as they would be by + * {@link #setAllExtensions(TestAllExtensions.Builder)}. + */ + public static TestAllExtensions getAllExtensionsSet() { + TestAllExtensions.Builder builder = TestAllExtensions.newBuilder(); + setAllExtensions(builder); + return builder.build(); + } + + /** + * Set every field of {@code message} to the values expected by + * {@code assertAllFieldsSet()}. + */ + public static void setAllFields(TestAllTypes.Builder message) { + message.setOptionalInt32 (101); + message.setOptionalInt64 (102); + message.setOptionalUint32 (103); + message.setOptionalUint64 (104); + message.setOptionalSint32 (105); + message.setOptionalSint64 (106); + message.setOptionalFixed32 (107); + message.setOptionalFixed64 (108); + message.setOptionalSfixed32(109); + message.setOptionalSfixed64(110); + message.setOptionalFloat (111); + message.setOptionalDouble (112); + message.setOptionalBool (true); + message.setOptionalString ("115"); + message.setOptionalBytes (toBytes("116")); + + message.setOptionalGroup( + TestAllTypes.OptionalGroup.newBuilder().setA(117).build()); + message.setOptionalNestedMessage( + TestAllTypes.NestedMessage.newBuilder().setBb(118).build()); + message.setOptionalForeignMessage( + ForeignMessage.newBuilder().setC(119).build()); + message.setOptionalImportMessage( + ImportMessage.newBuilder().setD(120).build()); + + message.setOptionalNestedEnum (TestAllTypes.NestedEnum.BAZ); + message.setOptionalForeignEnum(ForeignEnum.FOREIGN_BAZ); + message.setOptionalImportEnum (ImportEnum.IMPORT_BAZ); + + message.setOptionalStringPiece("124"); + message.setOptionalCord("125"); + + // ----------------------------------------------------------------- + + message.addRepeatedInt32 (201); + message.addRepeatedInt64 (202); + message.addRepeatedUint32 (203); + message.addRepeatedUint64 (204); + message.addRepeatedSint32 (205); + message.addRepeatedSint64 (206); + message.addRepeatedFixed32 (207); + message.addRepeatedFixed64 (208); + message.addRepeatedSfixed32(209); + message.addRepeatedSfixed64(210); + message.addRepeatedFloat (211); + message.addRepeatedDouble (212); + message.addRepeatedBool (true); + message.addRepeatedString ("215"); + message.addRepeatedBytes (toBytes("216")); + + message.addRepeatedGroup( + TestAllTypes.RepeatedGroup.newBuilder().setA(217).build()); + message.addRepeatedNestedMessage( + TestAllTypes.NestedMessage.newBuilder().setBb(218).build()); + message.addRepeatedForeignMessage( + ForeignMessage.newBuilder().setC(219).build()); + message.addRepeatedImportMessage( + ImportMessage.newBuilder().setD(220).build()); + + message.addRepeatedNestedEnum (TestAllTypes.NestedEnum.BAR); + message.addRepeatedForeignEnum(ForeignEnum.FOREIGN_BAR); + message.addRepeatedImportEnum (ImportEnum.IMPORT_BAR); + + message.addRepeatedStringPiece("224"); + message.addRepeatedCord("225"); + + // Add a second one of each field. + message.addRepeatedInt32 (301); + message.addRepeatedInt64 (302); + message.addRepeatedUint32 (303); + message.addRepeatedUint64 (304); + message.addRepeatedSint32 (305); + message.addRepeatedSint64 (306); + message.addRepeatedFixed32 (307); + message.addRepeatedFixed64 (308); + message.addRepeatedSfixed32(309); + message.addRepeatedSfixed64(310); + message.addRepeatedFloat (311); + message.addRepeatedDouble (312); + message.addRepeatedBool (false); + message.addRepeatedString ("315"); + message.addRepeatedBytes (toBytes("316")); + + message.addRepeatedGroup( + TestAllTypes.RepeatedGroup.newBuilder().setA(317).build()); + message.addRepeatedNestedMessage( + TestAllTypes.NestedMessage.newBuilder().setBb(318).build()); + message.addRepeatedForeignMessage( + ForeignMessage.newBuilder().setC(319).build()); + message.addRepeatedImportMessage( + ImportMessage.newBuilder().setD(320).build()); + + message.addRepeatedNestedEnum (TestAllTypes.NestedEnum.BAZ); + message.addRepeatedForeignEnum(ForeignEnum.FOREIGN_BAZ); + message.addRepeatedImportEnum (ImportEnum.IMPORT_BAZ); + + message.addRepeatedStringPiece("324"); + message.addRepeatedCord("325"); + + // ----------------------------------------------------------------- + + message.setDefaultInt32 (401); + message.setDefaultInt64 (402); + message.setDefaultUint32 (403); + message.setDefaultUint64 (404); + message.setDefaultSint32 (405); + message.setDefaultSint64 (406); + message.setDefaultFixed32 (407); + message.setDefaultFixed64 (408); + message.setDefaultSfixed32(409); + message.setDefaultSfixed64(410); + message.setDefaultFloat (411); + message.setDefaultDouble (412); + message.setDefaultBool (false); + message.setDefaultString ("415"); + message.setDefaultBytes (toBytes("416")); + + message.setDefaultNestedEnum (TestAllTypes.NestedEnum.FOO); + message.setDefaultForeignEnum(ForeignEnum.FOREIGN_FOO); + message.setDefaultImportEnum (ImportEnum.IMPORT_FOO); + + message.setDefaultStringPiece("424"); + message.setDefaultCord("425"); + } + + // ------------------------------------------------------------------- + + /** + * Modify the repeated fields of {@code message} to contain the values + * expected by {@code assertRepeatedFieldsModified()}. + */ + public static void modifyRepeatedFields(TestAllTypes.Builder message) { + message.setRepeatedInt32 (1, 501); + message.setRepeatedInt64 (1, 502); + message.setRepeatedUint32 (1, 503); + message.setRepeatedUint64 (1, 504); + message.setRepeatedSint32 (1, 505); + message.setRepeatedSint64 (1, 506); + message.setRepeatedFixed32 (1, 507); + message.setRepeatedFixed64 (1, 508); + message.setRepeatedSfixed32(1, 509); + message.setRepeatedSfixed64(1, 510); + message.setRepeatedFloat (1, 511); + message.setRepeatedDouble (1, 512); + message.setRepeatedBool (1, true); + message.setRepeatedString (1, "515"); + message.setRepeatedBytes (1, toBytes("516")); + + message.setRepeatedGroup(1, + TestAllTypes.RepeatedGroup.newBuilder().setA(517).build()); + message.setRepeatedNestedMessage(1, + TestAllTypes.NestedMessage.newBuilder().setBb(518).build()); + message.setRepeatedForeignMessage(1, + ForeignMessage.newBuilder().setC(519).build()); + message.setRepeatedImportMessage(1, + ImportMessage.newBuilder().setD(520).build()); + + message.setRepeatedNestedEnum (1, TestAllTypes.NestedEnum.FOO); + message.setRepeatedForeignEnum(1, ForeignEnum.FOREIGN_FOO); + message.setRepeatedImportEnum (1, ImportEnum.IMPORT_FOO); + + message.setRepeatedStringPiece(1, "524"); + message.setRepeatedCord(1, "525"); + } + + // ------------------------------------------------------------------- + + /** + * Assert (using {@code junit.framework.Assert}} that all fields of + * {@code message} are set to the values assigned by {@code setAllFields}. + */ + public static void assertAllFieldsSet(TestAllTypes message) { + Assert.assertTrue(message.hasOptionalInt32 ()); + Assert.assertTrue(message.hasOptionalInt64 ()); + Assert.assertTrue(message.hasOptionalUint32 ()); + Assert.assertTrue(message.hasOptionalUint64 ()); + Assert.assertTrue(message.hasOptionalSint32 ()); + Assert.assertTrue(message.hasOptionalSint64 ()); + Assert.assertTrue(message.hasOptionalFixed32 ()); + Assert.assertTrue(message.hasOptionalFixed64 ()); + Assert.assertTrue(message.hasOptionalSfixed32()); + Assert.assertTrue(message.hasOptionalSfixed64()); + Assert.assertTrue(message.hasOptionalFloat ()); + Assert.assertTrue(message.hasOptionalDouble ()); + Assert.assertTrue(message.hasOptionalBool ()); + Assert.assertTrue(message.hasOptionalString ()); + Assert.assertTrue(message.hasOptionalBytes ()); + + Assert.assertTrue(message.hasOptionalGroup ()); + Assert.assertTrue(message.hasOptionalNestedMessage ()); + Assert.assertTrue(message.hasOptionalForeignMessage()); + Assert.assertTrue(message.hasOptionalImportMessage ()); + + Assert.assertTrue(message.getOptionalGroup ().hasA()); + Assert.assertTrue(message.getOptionalNestedMessage ().hasBb()); + Assert.assertTrue(message.getOptionalForeignMessage().hasC()); + Assert.assertTrue(message.getOptionalImportMessage ().hasD()); + + Assert.assertTrue(message.hasOptionalNestedEnum ()); + Assert.assertTrue(message.hasOptionalForeignEnum()); + Assert.assertTrue(message.hasOptionalImportEnum ()); + + Assert.assertTrue(message.hasOptionalStringPiece()); + Assert.assertTrue(message.hasOptionalCord()); + + Assert.assertEquals(101 , message.getOptionalInt32 ()); + Assert.assertEquals(102 , message.getOptionalInt64 ()); + Assert.assertEquals(103 , message.getOptionalUint32 ()); + Assert.assertEquals(104 , message.getOptionalUint64 ()); + Assert.assertEquals(105 , message.getOptionalSint32 ()); + Assert.assertEquals(106 , message.getOptionalSint64 ()); + Assert.assertEquals(107 , message.getOptionalFixed32 ()); + Assert.assertEquals(108 , message.getOptionalFixed64 ()); + Assert.assertEquals(109 , message.getOptionalSfixed32()); + Assert.assertEquals(110 , message.getOptionalSfixed64()); + Assert.assertEquals(111 , message.getOptionalFloat (), 0.0); + Assert.assertEquals(112 , message.getOptionalDouble (), 0.0); + Assert.assertEquals(true , message.getOptionalBool ()); + Assert.assertEquals("115", message.getOptionalString ()); + Assert.assertEquals(toBytes("116"), message.getOptionalBytes()); + + Assert.assertEquals(117, message.getOptionalGroup ().getA()); + Assert.assertEquals(118, message.getOptionalNestedMessage ().getBb()); + Assert.assertEquals(119, message.getOptionalForeignMessage().getC()); + Assert.assertEquals(120, message.getOptionalImportMessage ().getD()); + + Assert.assertEquals(TestAllTypes.NestedEnum.BAZ, message.getOptionalNestedEnum()); + Assert.assertEquals(ForeignEnum.FOREIGN_BAZ, message.getOptionalForeignEnum()); + Assert.assertEquals(ImportEnum.IMPORT_BAZ, message.getOptionalImportEnum()); + + Assert.assertEquals("124", message.getOptionalStringPiece()); + Assert.assertEquals("125", message.getOptionalCord()); + + // ----------------------------------------------------------------- + + Assert.assertEquals(2, message.getRepeatedInt32Count ()); + Assert.assertEquals(2, message.getRepeatedInt64Count ()); + Assert.assertEquals(2, message.getRepeatedUint32Count ()); + Assert.assertEquals(2, message.getRepeatedUint64Count ()); + Assert.assertEquals(2, message.getRepeatedSint32Count ()); + Assert.assertEquals(2, message.getRepeatedSint64Count ()); + Assert.assertEquals(2, message.getRepeatedFixed32Count ()); + Assert.assertEquals(2, message.getRepeatedFixed64Count ()); + Assert.assertEquals(2, message.getRepeatedSfixed32Count()); + Assert.assertEquals(2, message.getRepeatedSfixed64Count()); + Assert.assertEquals(2, message.getRepeatedFloatCount ()); + Assert.assertEquals(2, message.getRepeatedDoubleCount ()); + Assert.assertEquals(2, message.getRepeatedBoolCount ()); + Assert.assertEquals(2, message.getRepeatedStringCount ()); + Assert.assertEquals(2, message.getRepeatedBytesCount ()); + + Assert.assertEquals(2, message.getRepeatedGroupCount ()); + Assert.assertEquals(2, message.getRepeatedNestedMessageCount ()); + Assert.assertEquals(2, message.getRepeatedForeignMessageCount()); + Assert.assertEquals(2, message.getRepeatedImportMessageCount ()); + Assert.assertEquals(2, message.getRepeatedNestedEnumCount ()); + Assert.assertEquals(2, message.getRepeatedForeignEnumCount ()); + Assert.assertEquals(2, message.getRepeatedImportEnumCount ()); + + Assert.assertEquals(2, message.getRepeatedStringPieceCount()); + Assert.assertEquals(2, message.getRepeatedCordCount()); + + Assert.assertEquals(201 , message.getRepeatedInt32 (0)); + Assert.assertEquals(202 , message.getRepeatedInt64 (0)); + Assert.assertEquals(203 , message.getRepeatedUint32 (0)); + Assert.assertEquals(204 , message.getRepeatedUint64 (0)); + Assert.assertEquals(205 , message.getRepeatedSint32 (0)); + Assert.assertEquals(206 , message.getRepeatedSint64 (0)); + Assert.assertEquals(207 , message.getRepeatedFixed32 (0)); + Assert.assertEquals(208 , message.getRepeatedFixed64 (0)); + Assert.assertEquals(209 , message.getRepeatedSfixed32(0)); + Assert.assertEquals(210 , message.getRepeatedSfixed64(0)); + Assert.assertEquals(211 , message.getRepeatedFloat (0), 0.0); + Assert.assertEquals(212 , message.getRepeatedDouble (0), 0.0); + Assert.assertEquals(true , message.getRepeatedBool (0)); + Assert.assertEquals("215", message.getRepeatedString (0)); + Assert.assertEquals(toBytes("216"), message.getRepeatedBytes(0)); + + Assert.assertEquals(217, message.getRepeatedGroup (0).getA()); + Assert.assertEquals(218, message.getRepeatedNestedMessage (0).getBb()); + Assert.assertEquals(219, message.getRepeatedForeignMessage(0).getC()); + Assert.assertEquals(220, message.getRepeatedImportMessage (0).getD()); + + Assert.assertEquals(TestAllTypes.NestedEnum.BAR, message.getRepeatedNestedEnum (0)); + Assert.assertEquals(ForeignEnum.FOREIGN_BAR, message.getRepeatedForeignEnum(0)); + Assert.assertEquals(ImportEnum.IMPORT_BAR, message.getRepeatedImportEnum(0)); + + Assert.assertEquals("224", message.getRepeatedStringPiece(0)); + Assert.assertEquals("225", message.getRepeatedCord(0)); + + Assert.assertEquals(301 , message.getRepeatedInt32 (1)); + Assert.assertEquals(302 , message.getRepeatedInt64 (1)); + Assert.assertEquals(303 , message.getRepeatedUint32 (1)); + Assert.assertEquals(304 , message.getRepeatedUint64 (1)); + Assert.assertEquals(305 , message.getRepeatedSint32 (1)); + Assert.assertEquals(306 , message.getRepeatedSint64 (1)); + Assert.assertEquals(307 , message.getRepeatedFixed32 (1)); + Assert.assertEquals(308 , message.getRepeatedFixed64 (1)); + Assert.assertEquals(309 , message.getRepeatedSfixed32(1)); + Assert.assertEquals(310 , message.getRepeatedSfixed64(1)); + Assert.assertEquals(311 , message.getRepeatedFloat (1), 0.0); + Assert.assertEquals(312 , message.getRepeatedDouble (1), 0.0); + Assert.assertEquals(false, message.getRepeatedBool (1)); + Assert.assertEquals("315", message.getRepeatedString (1)); + Assert.assertEquals(toBytes("316"), message.getRepeatedBytes(1)); + + Assert.assertEquals(317, message.getRepeatedGroup (1).getA()); + Assert.assertEquals(318, message.getRepeatedNestedMessage (1).getBb()); + Assert.assertEquals(319, message.getRepeatedForeignMessage(1).getC()); + Assert.assertEquals(320, message.getRepeatedImportMessage (1).getD()); + + Assert.assertEquals(TestAllTypes.NestedEnum.BAZ, message.getRepeatedNestedEnum (1)); + Assert.assertEquals(ForeignEnum.FOREIGN_BAZ, message.getRepeatedForeignEnum(1)); + Assert.assertEquals(ImportEnum.IMPORT_BAZ, message.getRepeatedImportEnum(1)); + + Assert.assertEquals("324", message.getRepeatedStringPiece(1)); + Assert.assertEquals("325", message.getRepeatedCord(1)); + + // ----------------------------------------------------------------- + + Assert.assertTrue(message.hasDefaultInt32 ()); + Assert.assertTrue(message.hasDefaultInt64 ()); + Assert.assertTrue(message.hasDefaultUint32 ()); + Assert.assertTrue(message.hasDefaultUint64 ()); + Assert.assertTrue(message.hasDefaultSint32 ()); + Assert.assertTrue(message.hasDefaultSint64 ()); + Assert.assertTrue(message.hasDefaultFixed32 ()); + Assert.assertTrue(message.hasDefaultFixed64 ()); + Assert.assertTrue(message.hasDefaultSfixed32()); + Assert.assertTrue(message.hasDefaultSfixed64()); + Assert.assertTrue(message.hasDefaultFloat ()); + Assert.assertTrue(message.hasDefaultDouble ()); + Assert.assertTrue(message.hasDefaultBool ()); + Assert.assertTrue(message.hasDefaultString ()); + Assert.assertTrue(message.hasDefaultBytes ()); + + Assert.assertTrue(message.hasDefaultNestedEnum ()); + Assert.assertTrue(message.hasDefaultForeignEnum()); + Assert.assertTrue(message.hasDefaultImportEnum ()); + + Assert.assertTrue(message.hasDefaultStringPiece()); + Assert.assertTrue(message.hasDefaultCord()); + + Assert.assertEquals(401 , message.getDefaultInt32 ()); + Assert.assertEquals(402 , message.getDefaultInt64 ()); + Assert.assertEquals(403 , message.getDefaultUint32 ()); + Assert.assertEquals(404 , message.getDefaultUint64 ()); + Assert.assertEquals(405 , message.getDefaultSint32 ()); + Assert.assertEquals(406 , message.getDefaultSint64 ()); + Assert.assertEquals(407 , message.getDefaultFixed32 ()); + Assert.assertEquals(408 , message.getDefaultFixed64 ()); + Assert.assertEquals(409 , message.getDefaultSfixed32()); + Assert.assertEquals(410 , message.getDefaultSfixed64()); + Assert.assertEquals(411 , message.getDefaultFloat (), 0.0); + Assert.assertEquals(412 , message.getDefaultDouble (), 0.0); + Assert.assertEquals(false, message.getDefaultBool ()); + Assert.assertEquals("415", message.getDefaultString ()); + Assert.assertEquals(toBytes("416"), message.getDefaultBytes()); + + Assert.assertEquals(TestAllTypes.NestedEnum.FOO, message.getDefaultNestedEnum ()); + Assert.assertEquals(ForeignEnum.FOREIGN_FOO, message.getDefaultForeignEnum()); + Assert.assertEquals(ImportEnum.IMPORT_FOO, message.getDefaultImportEnum()); + + Assert.assertEquals("424", message.getDefaultStringPiece()); + Assert.assertEquals("425", message.getDefaultCord()); + } + + // ------------------------------------------------------------------- + + /** + * Assert (using {@code junit.framework.Assert}} that all fields of + * {@code message} are cleared, and that getting the fields returns their + * default values. + */ + public static void assertClear(TestAllTypes message) { + // hasBlah() should initially be false for all optional fields. + Assert.assertFalse(message.hasOptionalInt32 ()); + Assert.assertFalse(message.hasOptionalInt64 ()); + Assert.assertFalse(message.hasOptionalUint32 ()); + Assert.assertFalse(message.hasOptionalUint64 ()); + Assert.assertFalse(message.hasOptionalSint32 ()); + Assert.assertFalse(message.hasOptionalSint64 ()); + Assert.assertFalse(message.hasOptionalFixed32 ()); + Assert.assertFalse(message.hasOptionalFixed64 ()); + Assert.assertFalse(message.hasOptionalSfixed32()); + Assert.assertFalse(message.hasOptionalSfixed64()); + Assert.assertFalse(message.hasOptionalFloat ()); + Assert.assertFalse(message.hasOptionalDouble ()); + Assert.assertFalse(message.hasOptionalBool ()); + Assert.assertFalse(message.hasOptionalString ()); + Assert.assertFalse(message.hasOptionalBytes ()); + + Assert.assertFalse(message.hasOptionalGroup ()); + Assert.assertFalse(message.hasOptionalNestedMessage ()); + Assert.assertFalse(message.hasOptionalForeignMessage()); + Assert.assertFalse(message.hasOptionalImportMessage ()); + + Assert.assertFalse(message.hasOptionalNestedEnum ()); + Assert.assertFalse(message.hasOptionalForeignEnum()); + Assert.assertFalse(message.hasOptionalImportEnum ()); + + Assert.assertFalse(message.hasOptionalStringPiece()); + Assert.assertFalse(message.hasOptionalCord()); + + // Optional fields without defaults are set to zero or something like it. + Assert.assertEquals(0 , message.getOptionalInt32 ()); + Assert.assertEquals(0 , message.getOptionalInt64 ()); + Assert.assertEquals(0 , message.getOptionalUint32 ()); + Assert.assertEquals(0 , message.getOptionalUint64 ()); + Assert.assertEquals(0 , message.getOptionalSint32 ()); + Assert.assertEquals(0 , message.getOptionalSint64 ()); + Assert.assertEquals(0 , message.getOptionalFixed32 ()); + Assert.assertEquals(0 , message.getOptionalFixed64 ()); + Assert.assertEquals(0 , message.getOptionalSfixed32()); + Assert.assertEquals(0 , message.getOptionalSfixed64()); + Assert.assertEquals(0 , message.getOptionalFloat (), 0.0); + Assert.assertEquals(0 , message.getOptionalDouble (), 0.0); + Assert.assertEquals(false, message.getOptionalBool ()); + Assert.assertEquals("" , message.getOptionalString ()); + Assert.assertEquals(ByteString.EMPTY, message.getOptionalBytes()); + + // Embedded messages should also be clear. + Assert.assertFalse(message.getOptionalGroup ().hasA()); + Assert.assertFalse(message.getOptionalNestedMessage ().hasBb()); + Assert.assertFalse(message.getOptionalForeignMessage().hasC()); + Assert.assertFalse(message.getOptionalImportMessage ().hasD()); + + Assert.assertEquals(0, message.getOptionalGroup ().getA()); + Assert.assertEquals(0, message.getOptionalNestedMessage ().getBb()); + Assert.assertEquals(0, message.getOptionalForeignMessage().getC()); + Assert.assertEquals(0, message.getOptionalImportMessage ().getD()); + + // Enums without defaults are set to the first value in the enum. + Assert.assertEquals(TestAllTypes.NestedEnum.FOO, message.getOptionalNestedEnum ()); + Assert.assertEquals(ForeignEnum.FOREIGN_FOO, message.getOptionalForeignEnum()); + Assert.assertEquals(ImportEnum.IMPORT_FOO, message.getOptionalImportEnum()); + + Assert.assertEquals("", message.getOptionalStringPiece()); + Assert.assertEquals("", message.getOptionalCord()); + + // Repeated fields are empty. + Assert.assertEquals(0, message.getRepeatedInt32Count ()); + Assert.assertEquals(0, message.getRepeatedInt64Count ()); + Assert.assertEquals(0, message.getRepeatedUint32Count ()); + Assert.assertEquals(0, message.getRepeatedUint64Count ()); + Assert.assertEquals(0, message.getRepeatedSint32Count ()); + Assert.assertEquals(0, message.getRepeatedSint64Count ()); + Assert.assertEquals(0, message.getRepeatedFixed32Count ()); + Assert.assertEquals(0, message.getRepeatedFixed64Count ()); + Assert.assertEquals(0, message.getRepeatedSfixed32Count()); + Assert.assertEquals(0, message.getRepeatedSfixed64Count()); + Assert.assertEquals(0, message.getRepeatedFloatCount ()); + Assert.assertEquals(0, message.getRepeatedDoubleCount ()); + Assert.assertEquals(0, message.getRepeatedBoolCount ()); + Assert.assertEquals(0, message.getRepeatedStringCount ()); + Assert.assertEquals(0, message.getRepeatedBytesCount ()); + + Assert.assertEquals(0, message.getRepeatedGroupCount ()); + Assert.assertEquals(0, message.getRepeatedNestedMessageCount ()); + Assert.assertEquals(0, message.getRepeatedForeignMessageCount()); + Assert.assertEquals(0, message.getRepeatedImportMessageCount ()); + Assert.assertEquals(0, message.getRepeatedNestedEnumCount ()); + Assert.assertEquals(0, message.getRepeatedForeignEnumCount ()); + Assert.assertEquals(0, message.getRepeatedImportEnumCount ()); + + Assert.assertEquals(0, message.getRepeatedStringPieceCount()); + Assert.assertEquals(0, message.getRepeatedCordCount()); + + // hasBlah() should also be false for all default fields. + Assert.assertFalse(message.hasDefaultInt32 ()); + Assert.assertFalse(message.hasDefaultInt64 ()); + Assert.assertFalse(message.hasDefaultUint32 ()); + Assert.assertFalse(message.hasDefaultUint64 ()); + Assert.assertFalse(message.hasDefaultSint32 ()); + Assert.assertFalse(message.hasDefaultSint64 ()); + Assert.assertFalse(message.hasDefaultFixed32 ()); + Assert.assertFalse(message.hasDefaultFixed64 ()); + Assert.assertFalse(message.hasDefaultSfixed32()); + Assert.assertFalse(message.hasDefaultSfixed64()); + Assert.assertFalse(message.hasDefaultFloat ()); + Assert.assertFalse(message.hasDefaultDouble ()); + Assert.assertFalse(message.hasDefaultBool ()); + Assert.assertFalse(message.hasDefaultString ()); + Assert.assertFalse(message.hasDefaultBytes ()); + + Assert.assertFalse(message.hasDefaultNestedEnum ()); + Assert.assertFalse(message.hasDefaultForeignEnum()); + Assert.assertFalse(message.hasDefaultImportEnum ()); + + Assert.assertFalse(message.hasDefaultStringPiece()); + Assert.assertFalse(message.hasDefaultCord()); + + // Fields with defaults have their default values (duh). + Assert.assertEquals( 41 , message.getDefaultInt32 ()); + Assert.assertEquals( 42 , message.getDefaultInt64 ()); + Assert.assertEquals( 43 , message.getDefaultUint32 ()); + Assert.assertEquals( 44 , message.getDefaultUint64 ()); + Assert.assertEquals(-45 , message.getDefaultSint32 ()); + Assert.assertEquals( 46 , message.getDefaultSint64 ()); + Assert.assertEquals( 47 , message.getDefaultFixed32 ()); + Assert.assertEquals( 48 , message.getDefaultFixed64 ()); + Assert.assertEquals( 49 , message.getDefaultSfixed32()); + Assert.assertEquals(-50 , message.getDefaultSfixed64()); + Assert.assertEquals( 51.5 , message.getDefaultFloat (), 0.0); + Assert.assertEquals( 52e3 , message.getDefaultDouble (), 0.0); + Assert.assertEquals(true , message.getDefaultBool ()); + Assert.assertEquals("hello", message.getDefaultString ()); + Assert.assertEquals(toBytes("world"), message.getDefaultBytes()); + + Assert.assertEquals(TestAllTypes.NestedEnum.BAR, message.getDefaultNestedEnum ()); + Assert.assertEquals(ForeignEnum.FOREIGN_BAR, message.getDefaultForeignEnum()); + Assert.assertEquals(ImportEnum.IMPORT_BAR, message.getDefaultImportEnum()); + + Assert.assertEquals("abc", message.getDefaultStringPiece()); + Assert.assertEquals("123", message.getDefaultCord()); + } + + // ------------------------------------------------------------------- + + /** + * Assert (using {@code junit.framework.Assert}} that all fields of + * {@code message} are set to the values assigned by {@code setAllFields} + * followed by {@code modifyRepeatedFields}. + */ + public static void assertRepeatedFieldsModified(TestAllTypes message) { + // ModifyRepeatedFields only sets the second repeated element of each + // field. In addition to verifying this, we also verify that the first + // element and size were *not* modified. + Assert.assertEquals(2, message.getRepeatedInt32Count ()); + Assert.assertEquals(2, message.getRepeatedInt64Count ()); + Assert.assertEquals(2, message.getRepeatedUint32Count ()); + Assert.assertEquals(2, message.getRepeatedUint64Count ()); + Assert.assertEquals(2, message.getRepeatedSint32Count ()); + Assert.assertEquals(2, message.getRepeatedSint64Count ()); + Assert.assertEquals(2, message.getRepeatedFixed32Count ()); + Assert.assertEquals(2, message.getRepeatedFixed64Count ()); + Assert.assertEquals(2, message.getRepeatedSfixed32Count()); + Assert.assertEquals(2, message.getRepeatedSfixed64Count()); + Assert.assertEquals(2, message.getRepeatedFloatCount ()); + Assert.assertEquals(2, message.getRepeatedDoubleCount ()); + Assert.assertEquals(2, message.getRepeatedBoolCount ()); + Assert.assertEquals(2, message.getRepeatedStringCount ()); + Assert.assertEquals(2, message.getRepeatedBytesCount ()); + + Assert.assertEquals(2, message.getRepeatedGroupCount ()); + Assert.assertEquals(2, message.getRepeatedNestedMessageCount ()); + Assert.assertEquals(2, message.getRepeatedForeignMessageCount()); + Assert.assertEquals(2, message.getRepeatedImportMessageCount ()); + Assert.assertEquals(2, message.getRepeatedNestedEnumCount ()); + Assert.assertEquals(2, message.getRepeatedForeignEnumCount ()); + Assert.assertEquals(2, message.getRepeatedImportEnumCount ()); + + Assert.assertEquals(2, message.getRepeatedStringPieceCount()); + Assert.assertEquals(2, message.getRepeatedCordCount()); + + Assert.assertEquals(201 , message.getRepeatedInt32 (0)); + Assert.assertEquals(202L , message.getRepeatedInt64 (0)); + Assert.assertEquals(203 , message.getRepeatedUint32 (0)); + Assert.assertEquals(204L , message.getRepeatedUint64 (0)); + Assert.assertEquals(205 , message.getRepeatedSint32 (0)); + Assert.assertEquals(206L , message.getRepeatedSint64 (0)); + Assert.assertEquals(207 , message.getRepeatedFixed32 (0)); + Assert.assertEquals(208L , message.getRepeatedFixed64 (0)); + Assert.assertEquals(209 , message.getRepeatedSfixed32(0)); + Assert.assertEquals(210L , message.getRepeatedSfixed64(0)); + Assert.assertEquals(211F , message.getRepeatedFloat (0)); + Assert.assertEquals(212D , message.getRepeatedDouble (0)); + Assert.assertEquals(true , message.getRepeatedBool (0)); + Assert.assertEquals("215", message.getRepeatedString (0)); + Assert.assertEquals(toBytes("216"), message.getRepeatedBytes(0)); + + Assert.assertEquals(217, message.getRepeatedGroup (0).getA()); + Assert.assertEquals(218, message.getRepeatedNestedMessage (0).getBb()); + Assert.assertEquals(219, message.getRepeatedForeignMessage(0).getC()); + Assert.assertEquals(220, message.getRepeatedImportMessage (0).getD()); + + Assert.assertEquals(TestAllTypes.NestedEnum.BAR, message.getRepeatedNestedEnum (0)); + Assert.assertEquals(ForeignEnum.FOREIGN_BAR, message.getRepeatedForeignEnum(0)); + Assert.assertEquals(ImportEnum.IMPORT_BAR, message.getRepeatedImportEnum(0)); + + Assert.assertEquals("224", message.getRepeatedStringPiece(0)); + Assert.assertEquals("225", message.getRepeatedCord(0)); + + // Actually verify the second (modified) elements now. + Assert.assertEquals(501 , message.getRepeatedInt32 (1)); + Assert.assertEquals(502L , message.getRepeatedInt64 (1)); + Assert.assertEquals(503 , message.getRepeatedUint32 (1)); + Assert.assertEquals(504L , message.getRepeatedUint64 (1)); + Assert.assertEquals(505 , message.getRepeatedSint32 (1)); + Assert.assertEquals(506L , message.getRepeatedSint64 (1)); + Assert.assertEquals(507 , message.getRepeatedFixed32 (1)); + Assert.assertEquals(508L , message.getRepeatedFixed64 (1)); + Assert.assertEquals(509 , message.getRepeatedSfixed32(1)); + Assert.assertEquals(510L , message.getRepeatedSfixed64(1)); + Assert.assertEquals(511F , message.getRepeatedFloat (1)); + Assert.assertEquals(512D , message.getRepeatedDouble (1)); + Assert.assertEquals(true , message.getRepeatedBool (1)); + Assert.assertEquals("515", message.getRepeatedString (1)); + Assert.assertEquals(toBytes("516"), message.getRepeatedBytes(1)); + + Assert.assertEquals(517, message.getRepeatedGroup (1).getA()); + Assert.assertEquals(518, message.getRepeatedNestedMessage (1).getBb()); + Assert.assertEquals(519, message.getRepeatedForeignMessage(1).getC()); + Assert.assertEquals(520, message.getRepeatedImportMessage (1).getD()); + + Assert.assertEquals(TestAllTypes.NestedEnum.FOO, message.getRepeatedNestedEnum (1)); + Assert.assertEquals(ForeignEnum.FOREIGN_FOO, message.getRepeatedForeignEnum(1)); + Assert.assertEquals(ImportEnum.IMPORT_FOO, message.getRepeatedImportEnum(1)); + + Assert.assertEquals("524", message.getRepeatedStringPiece(1)); + Assert.assertEquals("525", message.getRepeatedCord(1)); + } + + // =================================================================== + // Like above, but for extensions + + // Java gets confused with things like assertEquals(int, Integer): it can't + // decide whether to call assertEquals(int, int) or assertEquals(Object, + // Object). So we define these methods to help it. + private static void assertEqualsExactType(int a, int b) { + Assert.assertEquals(a, b); + } + private static void assertEqualsExactType(long a, long b) { + Assert.assertEquals(a, b); + } + private static void assertEqualsExactType(float a, float b) { + Assert.assertEquals(a, b, 0.0); + } + private static void assertEqualsExactType(double a, double b) { + Assert.assertEquals(a, b, 0.0); + } + private static void assertEqualsExactType(boolean a, boolean b) { + Assert.assertEquals(a, b); + } + private static void assertEqualsExactType(String a, String b) { + Assert.assertEquals(a, b); + } + private static void assertEqualsExactType(ByteString a, ByteString b) { + Assert.assertEquals(a, b); + } + private static void assertEqualsExactType(TestAllTypes.NestedEnum a, + TestAllTypes.NestedEnum b) { + Assert.assertEquals(a, b); + } + private static void assertEqualsExactType(ForeignEnum a, ForeignEnum b) { + Assert.assertEquals(a, b); + } + private static void assertEqualsExactType(ImportEnum a, ImportEnum b) { + Assert.assertEquals(a, b); + } + + /** + * Get an unmodifiable {@link ExtensionRegistry} containing all the + * extensions of {@code TestAllExtensions}. + */ + public static ExtensionRegistry getExtensionRegistry() { + ExtensionRegistry registry = ExtensionRegistry.newInstance(); + registerAllExtensions(registry); + return registry.getUnmodifiable(); + } + + /** + * Register all of {@code TestAllExtensions}' extensions with the + * given {@link ExtensionRegistry}. + */ + public static void registerAllExtensions(ExtensionRegistry registry) { + registry.add(UnittestProto.optionalInt32Extension ); + registry.add(UnittestProto.optionalInt64Extension ); + registry.add(UnittestProto.optionalUint32Extension ); + registry.add(UnittestProto.optionalUint64Extension ); + registry.add(UnittestProto.optionalSint32Extension ); + registry.add(UnittestProto.optionalSint64Extension ); + registry.add(UnittestProto.optionalFixed32Extension ); + registry.add(UnittestProto.optionalFixed64Extension ); + registry.add(UnittestProto.optionalSfixed32Extension ); + registry.add(UnittestProto.optionalSfixed64Extension ); + registry.add(UnittestProto.optionalFloatExtension ); + registry.add(UnittestProto.optionalDoubleExtension ); + registry.add(UnittestProto.optionalBoolExtension ); + registry.add(UnittestProto.optionalStringExtension ); + registry.add(UnittestProto.optionalBytesExtension ); + registry.add(UnittestProto.optionalGroupExtension ); + registry.add(UnittestProto.optionalNestedMessageExtension ); + registry.add(UnittestProto.optionalForeignMessageExtension); + registry.add(UnittestProto.optionalImportMessageExtension ); + registry.add(UnittestProto.optionalNestedEnumExtension ); + registry.add(UnittestProto.optionalForeignEnumExtension ); + registry.add(UnittestProto.optionalImportEnumExtension ); + registry.add(UnittestProto.optionalStringPieceExtension ); + registry.add(UnittestProto.optionalCordExtension ); + + registry.add(UnittestProto.repeatedInt32Extension ); + registry.add(UnittestProto.repeatedInt64Extension ); + registry.add(UnittestProto.repeatedUint32Extension ); + registry.add(UnittestProto.repeatedUint64Extension ); + registry.add(UnittestProto.repeatedSint32Extension ); + registry.add(UnittestProto.repeatedSint64Extension ); + registry.add(UnittestProto.repeatedFixed32Extension ); + registry.add(UnittestProto.repeatedFixed64Extension ); + registry.add(UnittestProto.repeatedSfixed32Extension ); + registry.add(UnittestProto.repeatedSfixed64Extension ); + registry.add(UnittestProto.repeatedFloatExtension ); + registry.add(UnittestProto.repeatedDoubleExtension ); + registry.add(UnittestProto.repeatedBoolExtension ); + registry.add(UnittestProto.repeatedStringExtension ); + registry.add(UnittestProto.repeatedBytesExtension ); + registry.add(UnittestProto.repeatedGroupExtension ); + registry.add(UnittestProto.repeatedNestedMessageExtension ); + registry.add(UnittestProto.repeatedForeignMessageExtension); + registry.add(UnittestProto.repeatedImportMessageExtension ); + registry.add(UnittestProto.repeatedNestedEnumExtension ); + registry.add(UnittestProto.repeatedForeignEnumExtension ); + registry.add(UnittestProto.repeatedImportEnumExtension ); + registry.add(UnittestProto.repeatedStringPieceExtension ); + registry.add(UnittestProto.repeatedCordExtension ); + + registry.add(UnittestProto.defaultInt32Extension ); + registry.add(UnittestProto.defaultInt64Extension ); + registry.add(UnittestProto.defaultUint32Extension ); + registry.add(UnittestProto.defaultUint64Extension ); + registry.add(UnittestProto.defaultSint32Extension ); + registry.add(UnittestProto.defaultSint64Extension ); + registry.add(UnittestProto.defaultFixed32Extension ); + registry.add(UnittestProto.defaultFixed64Extension ); + registry.add(UnittestProto.defaultSfixed32Extension ); + registry.add(UnittestProto.defaultSfixed64Extension ); + registry.add(UnittestProto.defaultFloatExtension ); + registry.add(UnittestProto.defaultDoubleExtension ); + registry.add(UnittestProto.defaultBoolExtension ); + registry.add(UnittestProto.defaultStringExtension ); + registry.add(UnittestProto.defaultBytesExtension ); + registry.add(UnittestProto.defaultNestedEnumExtension ); + registry.add(UnittestProto.defaultForeignEnumExtension); + registry.add(UnittestProto.defaultImportEnumExtension ); + registry.add(UnittestProto.defaultStringPieceExtension); + registry.add(UnittestProto.defaultCordExtension ); + } + + /** + * Set every field of {@code message} to the values expected by + * {@code assertAllExtensionsSet()}. + */ + public static void setAllExtensions(TestAllExtensions.Builder message) { + message.setExtension(UnittestProto.optionalInt32Extension , 101); + message.setExtension(UnittestProto.optionalInt64Extension , 102L); + message.setExtension(UnittestProto.optionalUint32Extension , 103); + message.setExtension(UnittestProto.optionalUint64Extension , 104L); + message.setExtension(UnittestProto.optionalSint32Extension , 105); + message.setExtension(UnittestProto.optionalSint64Extension , 106L); + message.setExtension(UnittestProto.optionalFixed32Extension , 107); + message.setExtension(UnittestProto.optionalFixed64Extension , 108L); + message.setExtension(UnittestProto.optionalSfixed32Extension, 109); + message.setExtension(UnittestProto.optionalSfixed64Extension, 110L); + message.setExtension(UnittestProto.optionalFloatExtension , 111F); + message.setExtension(UnittestProto.optionalDoubleExtension , 112D); + message.setExtension(UnittestProto.optionalBoolExtension , true); + message.setExtension(UnittestProto.optionalStringExtension , "115"); + message.setExtension(UnittestProto.optionalBytesExtension , toBytes("116")); + + message.setExtension(UnittestProto.optionalGroupExtension, + UnittestProto.OptionalGroup_extension.newBuilder().setA(117).build()); + message.setExtension(UnittestProto.optionalNestedMessageExtension, + TestAllTypes.NestedMessage.newBuilder().setBb(118).build()); + message.setExtension(UnittestProto.optionalForeignMessageExtension, + ForeignMessage.newBuilder().setC(119).build()); + message.setExtension(UnittestProto.optionalImportMessageExtension, + ImportMessage.newBuilder().setD(120).build()); + + message.setExtension(UnittestProto.optionalNestedEnumExtension, + TestAllTypes.NestedEnum.BAZ); + message.setExtension(UnittestProto.optionalForeignEnumExtension, + ForeignEnum.FOREIGN_BAZ); + message.setExtension(UnittestProto.optionalImportEnumExtension, + ImportEnum.IMPORT_BAZ); + + message.setExtension(UnittestProto.optionalStringPieceExtension, "124"); + message.setExtension(UnittestProto.optionalCordExtension, "125"); + + // ----------------------------------------------------------------- + + message.addExtension(UnittestProto.repeatedInt32Extension , 201); + message.addExtension(UnittestProto.repeatedInt64Extension , 202L); + message.addExtension(UnittestProto.repeatedUint32Extension , 203); + message.addExtension(UnittestProto.repeatedUint64Extension , 204L); + message.addExtension(UnittestProto.repeatedSint32Extension , 205); + message.addExtension(UnittestProto.repeatedSint64Extension , 206L); + message.addExtension(UnittestProto.repeatedFixed32Extension , 207); + message.addExtension(UnittestProto.repeatedFixed64Extension , 208L); + message.addExtension(UnittestProto.repeatedSfixed32Extension, 209); + message.addExtension(UnittestProto.repeatedSfixed64Extension, 210L); + message.addExtension(UnittestProto.repeatedFloatExtension , 211F); + message.addExtension(UnittestProto.repeatedDoubleExtension , 212D); + message.addExtension(UnittestProto.repeatedBoolExtension , true); + message.addExtension(UnittestProto.repeatedStringExtension , "215"); + message.addExtension(UnittestProto.repeatedBytesExtension , toBytes("216")); + + message.addExtension(UnittestProto.repeatedGroupExtension, + UnittestProto.RepeatedGroup_extension.newBuilder().setA(217).build()); + message.addExtension(UnittestProto.repeatedNestedMessageExtension, + TestAllTypes.NestedMessage.newBuilder().setBb(218).build()); + message.addExtension(UnittestProto.repeatedForeignMessageExtension, + ForeignMessage.newBuilder().setC(219).build()); + message.addExtension(UnittestProto.repeatedImportMessageExtension, + ImportMessage.newBuilder().setD(220).build()); + + message.addExtension(UnittestProto.repeatedNestedEnumExtension, + TestAllTypes.NestedEnum.BAR); + message.addExtension(UnittestProto.repeatedForeignEnumExtension, + ForeignEnum.FOREIGN_BAR); + message.addExtension(UnittestProto.repeatedImportEnumExtension, + ImportEnum.IMPORT_BAR); + + message.addExtension(UnittestProto.repeatedStringPieceExtension, "224"); + message.addExtension(UnittestProto.repeatedCordExtension, "225"); + + // Add a second one of each field. + message.addExtension(UnittestProto.repeatedInt32Extension , 301); + message.addExtension(UnittestProto.repeatedInt64Extension , 302L); + message.addExtension(UnittestProto.repeatedUint32Extension , 303); + message.addExtension(UnittestProto.repeatedUint64Extension , 304L); + message.addExtension(UnittestProto.repeatedSint32Extension , 305); + message.addExtension(UnittestProto.repeatedSint64Extension , 306L); + message.addExtension(UnittestProto.repeatedFixed32Extension , 307); + message.addExtension(UnittestProto.repeatedFixed64Extension , 308L); + message.addExtension(UnittestProto.repeatedSfixed32Extension, 309); + message.addExtension(UnittestProto.repeatedSfixed64Extension, 310L); + message.addExtension(UnittestProto.repeatedFloatExtension , 311F); + message.addExtension(UnittestProto.repeatedDoubleExtension , 312D); + message.addExtension(UnittestProto.repeatedBoolExtension , false); + message.addExtension(UnittestProto.repeatedStringExtension , "315"); + message.addExtension(UnittestProto.repeatedBytesExtension , toBytes("316")); + + message.addExtension(UnittestProto.repeatedGroupExtension, + UnittestProto.RepeatedGroup_extension.newBuilder().setA(317).build()); + message.addExtension(UnittestProto.repeatedNestedMessageExtension, + TestAllTypes.NestedMessage.newBuilder().setBb(318).build()); + message.addExtension(UnittestProto.repeatedForeignMessageExtension, + ForeignMessage.newBuilder().setC(319).build()); + message.addExtension(UnittestProto.repeatedImportMessageExtension, + ImportMessage.newBuilder().setD(320).build()); + + message.addExtension(UnittestProto.repeatedNestedEnumExtension, + TestAllTypes.NestedEnum.BAZ); + message.addExtension(UnittestProto.repeatedForeignEnumExtension, + ForeignEnum.FOREIGN_BAZ); + message.addExtension(UnittestProto.repeatedImportEnumExtension, + ImportEnum.IMPORT_BAZ); + + message.addExtension(UnittestProto.repeatedStringPieceExtension, "324"); + message.addExtension(UnittestProto.repeatedCordExtension, "325"); + + // ----------------------------------------------------------------- + + message.setExtension(UnittestProto.defaultInt32Extension , 401); + message.setExtension(UnittestProto.defaultInt64Extension , 402L); + message.setExtension(UnittestProto.defaultUint32Extension , 403); + message.setExtension(UnittestProto.defaultUint64Extension , 404L); + message.setExtension(UnittestProto.defaultSint32Extension , 405); + message.setExtension(UnittestProto.defaultSint64Extension , 406L); + message.setExtension(UnittestProto.defaultFixed32Extension , 407); + message.setExtension(UnittestProto.defaultFixed64Extension , 408L); + message.setExtension(UnittestProto.defaultSfixed32Extension, 409); + message.setExtension(UnittestProto.defaultSfixed64Extension, 410L); + message.setExtension(UnittestProto.defaultFloatExtension , 411F); + message.setExtension(UnittestProto.defaultDoubleExtension , 412D); + message.setExtension(UnittestProto.defaultBoolExtension , false); + message.setExtension(UnittestProto.defaultStringExtension , "415"); + message.setExtension(UnittestProto.defaultBytesExtension , toBytes("416")); + + message.setExtension(UnittestProto.defaultNestedEnumExtension, + TestAllTypes.NestedEnum.FOO); + message.setExtension(UnittestProto.defaultForeignEnumExtension, + ForeignEnum.FOREIGN_FOO); + message.setExtension(UnittestProto.defaultImportEnumExtension, + ImportEnum.IMPORT_FOO); + + message.setExtension(UnittestProto.defaultStringPieceExtension, "424"); + message.setExtension(UnittestProto.defaultCordExtension, "425"); + } + + // ------------------------------------------------------------------- + + /** + * Modify the repeated extensions of {@code message} to contain the values + * expected by {@code assertRepeatedExtensionsModified()}. + */ + public static void modifyRepeatedExtensions( + TestAllExtensions.Builder message) { + message.setExtension(UnittestProto.repeatedInt32Extension , 1, 501); + message.setExtension(UnittestProto.repeatedInt64Extension , 1, 502L); + message.setExtension(UnittestProto.repeatedUint32Extension , 1, 503); + message.setExtension(UnittestProto.repeatedUint64Extension , 1, 504L); + message.setExtension(UnittestProto.repeatedSint32Extension , 1, 505); + message.setExtension(UnittestProto.repeatedSint64Extension , 1, 506L); + message.setExtension(UnittestProto.repeatedFixed32Extension , 1, 507); + message.setExtension(UnittestProto.repeatedFixed64Extension , 1, 508L); + message.setExtension(UnittestProto.repeatedSfixed32Extension, 1, 509); + message.setExtension(UnittestProto.repeatedSfixed64Extension, 1, 510L); + message.setExtension(UnittestProto.repeatedFloatExtension , 1, 511F); + message.setExtension(UnittestProto.repeatedDoubleExtension , 1, 512D); + message.setExtension(UnittestProto.repeatedBoolExtension , 1, true); + message.setExtension(UnittestProto.repeatedStringExtension , 1, "515"); + message.setExtension(UnittestProto.repeatedBytesExtension , 1, toBytes("516")); + + message.setExtension(UnittestProto.repeatedGroupExtension, 1, + UnittestProto.RepeatedGroup_extension.newBuilder().setA(517).build()); + message.setExtension(UnittestProto.repeatedNestedMessageExtension, 1, + TestAllTypes.NestedMessage.newBuilder().setBb(518).build()); + message.setExtension(UnittestProto.repeatedForeignMessageExtension, 1, + ForeignMessage.newBuilder().setC(519).build()); + message.setExtension(UnittestProto.repeatedImportMessageExtension, 1, + ImportMessage.newBuilder().setD(520).build()); + + message.setExtension(UnittestProto.repeatedNestedEnumExtension , 1, + TestAllTypes.NestedEnum.FOO); + message.setExtension(UnittestProto.repeatedForeignEnumExtension, 1, + ForeignEnum.FOREIGN_FOO); + message.setExtension(UnittestProto.repeatedImportEnumExtension , 1, + ImportEnum.IMPORT_FOO); + + message.setExtension(UnittestProto.repeatedStringPieceExtension, 1, "524"); + message.setExtension(UnittestProto.repeatedCordExtension, 1, "525"); + } + + // ------------------------------------------------------------------- + + /** + * Assert (using {@code junit.framework.Assert}} that all extensions of + * {@code message} are set to the values assigned by {@code setAllExtensions}. + */ + public static void assertAllExtensionsSet(TestAllExtensions message) { + Assert.assertTrue(message.hasExtension(UnittestProto.optionalInt32Extension )); + Assert.assertTrue(message.hasExtension(UnittestProto.optionalInt64Extension )); + Assert.assertTrue(message.hasExtension(UnittestProto.optionalUint32Extension )); + Assert.assertTrue(message.hasExtension(UnittestProto.optionalUint64Extension )); + Assert.assertTrue(message.hasExtension(UnittestProto.optionalSint32Extension )); + Assert.assertTrue(message.hasExtension(UnittestProto.optionalSint64Extension )); + Assert.assertTrue(message.hasExtension(UnittestProto.optionalFixed32Extension )); + Assert.assertTrue(message.hasExtension(UnittestProto.optionalFixed64Extension )); + Assert.assertTrue(message.hasExtension(UnittestProto.optionalSfixed32Extension)); + Assert.assertTrue(message.hasExtension(UnittestProto.optionalSfixed64Extension)); + Assert.assertTrue(message.hasExtension(UnittestProto.optionalFloatExtension )); + Assert.assertTrue(message.hasExtension(UnittestProto.optionalDoubleExtension )); + Assert.assertTrue(message.hasExtension(UnittestProto.optionalBoolExtension )); + Assert.assertTrue(message.hasExtension(UnittestProto.optionalStringExtension )); + Assert.assertTrue(message.hasExtension(UnittestProto.optionalBytesExtension )); + + Assert.assertTrue(message.hasExtension(UnittestProto.optionalGroupExtension )); + Assert.assertTrue(message.hasExtension(UnittestProto.optionalNestedMessageExtension )); + Assert.assertTrue(message.hasExtension(UnittestProto.optionalForeignMessageExtension)); + Assert.assertTrue(message.hasExtension(UnittestProto.optionalImportMessageExtension )); + + Assert.assertTrue(message.getExtension(UnittestProto.optionalGroupExtension ).hasA()); + Assert.assertTrue(message.getExtension(UnittestProto.optionalNestedMessageExtension ).hasBb()); + Assert.assertTrue(message.getExtension(UnittestProto.optionalForeignMessageExtension).hasC()); + Assert.assertTrue(message.getExtension(UnittestProto.optionalImportMessageExtension ).hasD()); + + Assert.assertTrue(message.hasExtension(UnittestProto.optionalNestedEnumExtension )); + Assert.assertTrue(message.hasExtension(UnittestProto.optionalForeignEnumExtension)); + Assert.assertTrue(message.hasExtension(UnittestProto.optionalImportEnumExtension )); + + Assert.assertTrue(message.hasExtension(UnittestProto.optionalStringPieceExtension)); + Assert.assertTrue(message.hasExtension(UnittestProto.optionalCordExtension)); + + assertEqualsExactType(101 , message.getExtension(UnittestProto.optionalInt32Extension )); + assertEqualsExactType(102L , message.getExtension(UnittestProto.optionalInt64Extension )); + assertEqualsExactType(103 , message.getExtension(UnittestProto.optionalUint32Extension )); + assertEqualsExactType(104L , message.getExtension(UnittestProto.optionalUint64Extension )); + assertEqualsExactType(105 , message.getExtension(UnittestProto.optionalSint32Extension )); + assertEqualsExactType(106L , message.getExtension(UnittestProto.optionalSint64Extension )); + assertEqualsExactType(107 , message.getExtension(UnittestProto.optionalFixed32Extension )); + assertEqualsExactType(108L , message.getExtension(UnittestProto.optionalFixed64Extension )); + assertEqualsExactType(109 , message.getExtension(UnittestProto.optionalSfixed32Extension)); + assertEqualsExactType(110L , message.getExtension(UnittestProto.optionalSfixed64Extension)); + assertEqualsExactType(111F , message.getExtension(UnittestProto.optionalFloatExtension )); + assertEqualsExactType(112D , message.getExtension(UnittestProto.optionalDoubleExtension )); + assertEqualsExactType(true , message.getExtension(UnittestProto.optionalBoolExtension )); + assertEqualsExactType("115", message.getExtension(UnittestProto.optionalStringExtension )); + assertEqualsExactType(toBytes("116"), message.getExtension(UnittestProto.optionalBytesExtension)); + + assertEqualsExactType(117, message.getExtension(UnittestProto.optionalGroupExtension ).getA()); + assertEqualsExactType(118, message.getExtension(UnittestProto.optionalNestedMessageExtension ).getBb()); + assertEqualsExactType(119, message.getExtension(UnittestProto.optionalForeignMessageExtension).getC()); + assertEqualsExactType(120, message.getExtension(UnittestProto.optionalImportMessageExtension ).getD()); + + assertEqualsExactType(TestAllTypes.NestedEnum.BAZ, + message.getExtension(UnittestProto.optionalNestedEnumExtension)); + assertEqualsExactType(ForeignEnum.FOREIGN_BAZ, + message.getExtension(UnittestProto.optionalForeignEnumExtension)); + assertEqualsExactType(ImportEnum.IMPORT_BAZ, + message.getExtension(UnittestProto.optionalImportEnumExtension)); + + assertEqualsExactType("124", message.getExtension(UnittestProto.optionalStringPieceExtension)); + assertEqualsExactType("125", message.getExtension(UnittestProto.optionalCordExtension)); + + // ----------------------------------------------------------------- + + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedInt32Extension )); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedInt64Extension )); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedUint32Extension )); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedUint64Extension )); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedSint32Extension )); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedSint64Extension )); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedFixed32Extension )); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedFixed64Extension )); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedSfixed32Extension)); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedSfixed64Extension)); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedFloatExtension )); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedDoubleExtension )); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedBoolExtension )); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedStringExtension )); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedBytesExtension )); + + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedGroupExtension )); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedNestedMessageExtension )); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedForeignMessageExtension)); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedImportMessageExtension )); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedNestedEnumExtension )); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedForeignEnumExtension )); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedImportEnumExtension )); + + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedStringPieceExtension)); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedCordExtension)); + + assertEqualsExactType(201 , message.getExtension(UnittestProto.repeatedInt32Extension , 0)); + assertEqualsExactType(202L , message.getExtension(UnittestProto.repeatedInt64Extension , 0)); + assertEqualsExactType(203 , message.getExtension(UnittestProto.repeatedUint32Extension , 0)); + assertEqualsExactType(204L , message.getExtension(UnittestProto.repeatedUint64Extension , 0)); + assertEqualsExactType(205 , message.getExtension(UnittestProto.repeatedSint32Extension , 0)); + assertEqualsExactType(206L , message.getExtension(UnittestProto.repeatedSint64Extension , 0)); + assertEqualsExactType(207 , message.getExtension(UnittestProto.repeatedFixed32Extension , 0)); + assertEqualsExactType(208L , message.getExtension(UnittestProto.repeatedFixed64Extension , 0)); + assertEqualsExactType(209 , message.getExtension(UnittestProto.repeatedSfixed32Extension, 0)); + assertEqualsExactType(210L , message.getExtension(UnittestProto.repeatedSfixed64Extension, 0)); + assertEqualsExactType(211F , message.getExtension(UnittestProto.repeatedFloatExtension , 0)); + assertEqualsExactType(212D , message.getExtension(UnittestProto.repeatedDoubleExtension , 0)); + assertEqualsExactType(true , message.getExtension(UnittestProto.repeatedBoolExtension , 0)); + assertEqualsExactType("215", message.getExtension(UnittestProto.repeatedStringExtension , 0)); + assertEqualsExactType(toBytes("216"), message.getExtension(UnittestProto.repeatedBytesExtension, 0)); + + assertEqualsExactType(217, message.getExtension(UnittestProto.repeatedGroupExtension , 0).getA()); + assertEqualsExactType(218, message.getExtension(UnittestProto.repeatedNestedMessageExtension , 0).getBb()); + assertEqualsExactType(219, message.getExtension(UnittestProto.repeatedForeignMessageExtension, 0).getC()); + assertEqualsExactType(220, message.getExtension(UnittestProto.repeatedImportMessageExtension , 0).getD()); + + assertEqualsExactType(TestAllTypes.NestedEnum.BAR, + message.getExtension(UnittestProto.repeatedNestedEnumExtension, 0)); + assertEqualsExactType(ForeignEnum.FOREIGN_BAR, + message.getExtension(UnittestProto.repeatedForeignEnumExtension, 0)); + assertEqualsExactType(ImportEnum.IMPORT_BAR, + message.getExtension(UnittestProto.repeatedImportEnumExtension, 0)); + + assertEqualsExactType("224", message.getExtension(UnittestProto.repeatedStringPieceExtension, 0)); + assertEqualsExactType("225", message.getExtension(UnittestProto.repeatedCordExtension, 0)); + + assertEqualsExactType(301 , message.getExtension(UnittestProto.repeatedInt32Extension , 1)); + assertEqualsExactType(302L , message.getExtension(UnittestProto.repeatedInt64Extension , 1)); + assertEqualsExactType(303 , message.getExtension(UnittestProto.repeatedUint32Extension , 1)); + assertEqualsExactType(304L , message.getExtension(UnittestProto.repeatedUint64Extension , 1)); + assertEqualsExactType(305 , message.getExtension(UnittestProto.repeatedSint32Extension , 1)); + assertEqualsExactType(306L , message.getExtension(UnittestProto.repeatedSint64Extension , 1)); + assertEqualsExactType(307 , message.getExtension(UnittestProto.repeatedFixed32Extension , 1)); + assertEqualsExactType(308L , message.getExtension(UnittestProto.repeatedFixed64Extension , 1)); + assertEqualsExactType(309 , message.getExtension(UnittestProto.repeatedSfixed32Extension, 1)); + assertEqualsExactType(310L , message.getExtension(UnittestProto.repeatedSfixed64Extension, 1)); + assertEqualsExactType(311F , message.getExtension(UnittestProto.repeatedFloatExtension , 1)); + assertEqualsExactType(312D , message.getExtension(UnittestProto.repeatedDoubleExtension , 1)); + assertEqualsExactType(false, message.getExtension(UnittestProto.repeatedBoolExtension , 1)); + assertEqualsExactType("315", message.getExtension(UnittestProto.repeatedStringExtension , 1)); + assertEqualsExactType(toBytes("316"), message.getExtension(UnittestProto.repeatedBytesExtension, 1)); + + assertEqualsExactType(317, message.getExtension(UnittestProto.repeatedGroupExtension , 1).getA()); + assertEqualsExactType(318, message.getExtension(UnittestProto.repeatedNestedMessageExtension , 1).getBb()); + assertEqualsExactType(319, message.getExtension(UnittestProto.repeatedForeignMessageExtension, 1).getC()); + assertEqualsExactType(320, message.getExtension(UnittestProto.repeatedImportMessageExtension , 1).getD()); + + assertEqualsExactType(TestAllTypes.NestedEnum.BAZ, + message.getExtension(UnittestProto.repeatedNestedEnumExtension, 1)); + assertEqualsExactType(ForeignEnum.FOREIGN_BAZ, + message.getExtension(UnittestProto.repeatedForeignEnumExtension, 1)); + assertEqualsExactType(ImportEnum.IMPORT_BAZ, + message.getExtension(UnittestProto.repeatedImportEnumExtension, 1)); + + assertEqualsExactType("324", message.getExtension(UnittestProto.repeatedStringPieceExtension, 1)); + assertEqualsExactType("325", message.getExtension(UnittestProto.repeatedCordExtension, 1)); + + // ----------------------------------------------------------------- + + Assert.assertTrue(message.hasExtension(UnittestProto.defaultInt32Extension )); + Assert.assertTrue(message.hasExtension(UnittestProto.defaultInt64Extension )); + Assert.assertTrue(message.hasExtension(UnittestProto.defaultUint32Extension )); + Assert.assertTrue(message.hasExtension(UnittestProto.defaultUint64Extension )); + Assert.assertTrue(message.hasExtension(UnittestProto.defaultSint32Extension )); + Assert.assertTrue(message.hasExtension(UnittestProto.defaultSint64Extension )); + Assert.assertTrue(message.hasExtension(UnittestProto.defaultFixed32Extension )); + Assert.assertTrue(message.hasExtension(UnittestProto.defaultFixed64Extension )); + Assert.assertTrue(message.hasExtension(UnittestProto.defaultSfixed32Extension)); + Assert.assertTrue(message.hasExtension(UnittestProto.defaultSfixed64Extension)); + Assert.assertTrue(message.hasExtension(UnittestProto.defaultFloatExtension )); + Assert.assertTrue(message.hasExtension(UnittestProto.defaultDoubleExtension )); + Assert.assertTrue(message.hasExtension(UnittestProto.defaultBoolExtension )); + Assert.assertTrue(message.hasExtension(UnittestProto.defaultStringExtension )); + Assert.assertTrue(message.hasExtension(UnittestProto.defaultBytesExtension )); + + Assert.assertTrue(message.hasExtension(UnittestProto.defaultNestedEnumExtension )); + Assert.assertTrue(message.hasExtension(UnittestProto.defaultForeignEnumExtension)); + Assert.assertTrue(message.hasExtension(UnittestProto.defaultImportEnumExtension )); + + Assert.assertTrue(message.hasExtension(UnittestProto.defaultStringPieceExtension)); + Assert.assertTrue(message.hasExtension(UnittestProto.defaultCordExtension)); + + assertEqualsExactType(401 , message.getExtension(UnittestProto.defaultInt32Extension )); + assertEqualsExactType(402L , message.getExtension(UnittestProto.defaultInt64Extension )); + assertEqualsExactType(403 , message.getExtension(UnittestProto.defaultUint32Extension )); + assertEqualsExactType(404L , message.getExtension(UnittestProto.defaultUint64Extension )); + assertEqualsExactType(405 , message.getExtension(UnittestProto.defaultSint32Extension )); + assertEqualsExactType(406L , message.getExtension(UnittestProto.defaultSint64Extension )); + assertEqualsExactType(407 , message.getExtension(UnittestProto.defaultFixed32Extension )); + assertEqualsExactType(408L , message.getExtension(UnittestProto.defaultFixed64Extension )); + assertEqualsExactType(409 , message.getExtension(UnittestProto.defaultSfixed32Extension)); + assertEqualsExactType(410L , message.getExtension(UnittestProto.defaultSfixed64Extension)); + assertEqualsExactType(411F , message.getExtension(UnittestProto.defaultFloatExtension )); + assertEqualsExactType(412D , message.getExtension(UnittestProto.defaultDoubleExtension )); + assertEqualsExactType(false, message.getExtension(UnittestProto.defaultBoolExtension )); + assertEqualsExactType("415", message.getExtension(UnittestProto.defaultStringExtension )); + assertEqualsExactType(toBytes("416"), message.getExtension(UnittestProto.defaultBytesExtension)); + + assertEqualsExactType(TestAllTypes.NestedEnum.FOO, + message.getExtension(UnittestProto.defaultNestedEnumExtension )); + assertEqualsExactType(ForeignEnum.FOREIGN_FOO, + message.getExtension(UnittestProto.defaultForeignEnumExtension)); + assertEqualsExactType(ImportEnum.IMPORT_FOO, + message.getExtension(UnittestProto.defaultImportEnumExtension)); + + assertEqualsExactType("424", message.getExtension(UnittestProto.defaultStringPieceExtension)); + assertEqualsExactType("425", message.getExtension(UnittestProto.defaultCordExtension)); + } + + // ------------------------------------------------------------------- + + /** + * Assert (using {@code junit.framework.Assert}} that all extensions of + * {@code message} are cleared, and that getting the extensions returns their + * default values. + */ + public static void assertExtensionsClear(TestAllExtensions message) { + // hasBlah() should initially be false for all optional fields. + Assert.assertFalse(message.hasExtension(UnittestProto.optionalInt32Extension )); + Assert.assertFalse(message.hasExtension(UnittestProto.optionalInt64Extension )); + Assert.assertFalse(message.hasExtension(UnittestProto.optionalUint32Extension )); + Assert.assertFalse(message.hasExtension(UnittestProto.optionalUint64Extension )); + Assert.assertFalse(message.hasExtension(UnittestProto.optionalSint32Extension )); + Assert.assertFalse(message.hasExtension(UnittestProto.optionalSint64Extension )); + Assert.assertFalse(message.hasExtension(UnittestProto.optionalFixed32Extension )); + Assert.assertFalse(message.hasExtension(UnittestProto.optionalFixed64Extension )); + Assert.assertFalse(message.hasExtension(UnittestProto.optionalSfixed32Extension)); + Assert.assertFalse(message.hasExtension(UnittestProto.optionalSfixed64Extension)); + Assert.assertFalse(message.hasExtension(UnittestProto.optionalFloatExtension )); + Assert.assertFalse(message.hasExtension(UnittestProto.optionalDoubleExtension )); + Assert.assertFalse(message.hasExtension(UnittestProto.optionalBoolExtension )); + Assert.assertFalse(message.hasExtension(UnittestProto.optionalStringExtension )); + Assert.assertFalse(message.hasExtension(UnittestProto.optionalBytesExtension )); + + Assert.assertFalse(message.hasExtension(UnittestProto.optionalGroupExtension )); + Assert.assertFalse(message.hasExtension(UnittestProto.optionalNestedMessageExtension )); + Assert.assertFalse(message.hasExtension(UnittestProto.optionalForeignMessageExtension)); + Assert.assertFalse(message.hasExtension(UnittestProto.optionalImportMessageExtension )); + + Assert.assertFalse(message.hasExtension(UnittestProto.optionalNestedEnumExtension )); + Assert.assertFalse(message.hasExtension(UnittestProto.optionalForeignEnumExtension)); + Assert.assertFalse(message.hasExtension(UnittestProto.optionalImportEnumExtension )); + + Assert.assertFalse(message.hasExtension(UnittestProto.optionalStringPieceExtension)); + Assert.assertFalse(message.hasExtension(UnittestProto.optionalCordExtension)); + + // Optional fields without defaults are set to zero or something like it. + assertEqualsExactType(0 , message.getExtension(UnittestProto.optionalInt32Extension )); + assertEqualsExactType(0L , message.getExtension(UnittestProto.optionalInt64Extension )); + assertEqualsExactType(0 , message.getExtension(UnittestProto.optionalUint32Extension )); + assertEqualsExactType(0L , message.getExtension(UnittestProto.optionalUint64Extension )); + assertEqualsExactType(0 , message.getExtension(UnittestProto.optionalSint32Extension )); + assertEqualsExactType(0L , message.getExtension(UnittestProto.optionalSint64Extension )); + assertEqualsExactType(0 , message.getExtension(UnittestProto.optionalFixed32Extension )); + assertEqualsExactType(0L , message.getExtension(UnittestProto.optionalFixed64Extension )); + assertEqualsExactType(0 , message.getExtension(UnittestProto.optionalSfixed32Extension)); + assertEqualsExactType(0L , message.getExtension(UnittestProto.optionalSfixed64Extension)); + assertEqualsExactType(0F , message.getExtension(UnittestProto.optionalFloatExtension )); + assertEqualsExactType(0D , message.getExtension(UnittestProto.optionalDoubleExtension )); + assertEqualsExactType(false, message.getExtension(UnittestProto.optionalBoolExtension )); + assertEqualsExactType("" , message.getExtension(UnittestProto.optionalStringExtension )); + assertEqualsExactType(ByteString.EMPTY, message.getExtension(UnittestProto.optionalBytesExtension)); + + // Embedded messages should also be clear. + Assert.assertFalse(message.getExtension(UnittestProto.optionalGroupExtension ).hasA()); + Assert.assertFalse(message.getExtension(UnittestProto.optionalNestedMessageExtension ).hasBb()); + Assert.assertFalse(message.getExtension(UnittestProto.optionalForeignMessageExtension).hasC()); + Assert.assertFalse(message.getExtension(UnittestProto.optionalImportMessageExtension ).hasD()); + + assertEqualsExactType(0, message.getExtension(UnittestProto.optionalGroupExtension ).getA()); + assertEqualsExactType(0, message.getExtension(UnittestProto.optionalNestedMessageExtension ).getBb()); + assertEqualsExactType(0, message.getExtension(UnittestProto.optionalForeignMessageExtension).getC()); + assertEqualsExactType(0, message.getExtension(UnittestProto.optionalImportMessageExtension ).getD()); + + // Enums without defaults are set to the first value in the enum. + assertEqualsExactType(TestAllTypes.NestedEnum.FOO, + message.getExtension(UnittestProto.optionalNestedEnumExtension )); + assertEqualsExactType(ForeignEnum.FOREIGN_FOO, + message.getExtension(UnittestProto.optionalForeignEnumExtension)); + assertEqualsExactType(ImportEnum.IMPORT_FOO, + message.getExtension(UnittestProto.optionalImportEnumExtension)); + + assertEqualsExactType("", message.getExtension(UnittestProto.optionalStringPieceExtension)); + assertEqualsExactType("", message.getExtension(UnittestProto.optionalCordExtension)); + + // Repeated fields are empty. + Assert.assertEquals(0, message.getExtensionCount(UnittestProto.repeatedInt32Extension )); + Assert.assertEquals(0, message.getExtensionCount(UnittestProto.repeatedInt64Extension )); + Assert.assertEquals(0, message.getExtensionCount(UnittestProto.repeatedUint32Extension )); + Assert.assertEquals(0, message.getExtensionCount(UnittestProto.repeatedUint64Extension )); + Assert.assertEquals(0, message.getExtensionCount(UnittestProto.repeatedSint32Extension )); + Assert.assertEquals(0, message.getExtensionCount(UnittestProto.repeatedSint64Extension )); + Assert.assertEquals(0, message.getExtensionCount(UnittestProto.repeatedFixed32Extension )); + Assert.assertEquals(0, message.getExtensionCount(UnittestProto.repeatedFixed64Extension )); + Assert.assertEquals(0, message.getExtensionCount(UnittestProto.repeatedSfixed32Extension)); + Assert.assertEquals(0, message.getExtensionCount(UnittestProto.repeatedSfixed64Extension)); + Assert.assertEquals(0, message.getExtensionCount(UnittestProto.repeatedFloatExtension )); + Assert.assertEquals(0, message.getExtensionCount(UnittestProto.repeatedDoubleExtension )); + Assert.assertEquals(0, message.getExtensionCount(UnittestProto.repeatedBoolExtension )); + Assert.assertEquals(0, message.getExtensionCount(UnittestProto.repeatedStringExtension )); + Assert.assertEquals(0, message.getExtensionCount(UnittestProto.repeatedBytesExtension )); + + Assert.assertEquals(0, message.getExtensionCount(UnittestProto.repeatedGroupExtension )); + Assert.assertEquals(0, message.getExtensionCount(UnittestProto.repeatedNestedMessageExtension )); + Assert.assertEquals(0, message.getExtensionCount(UnittestProto.repeatedForeignMessageExtension)); + Assert.assertEquals(0, message.getExtensionCount(UnittestProto.repeatedImportMessageExtension )); + Assert.assertEquals(0, message.getExtensionCount(UnittestProto.repeatedNestedEnumExtension )); + Assert.assertEquals(0, message.getExtensionCount(UnittestProto.repeatedForeignEnumExtension )); + Assert.assertEquals(0, message.getExtensionCount(UnittestProto.repeatedImportEnumExtension )); + + Assert.assertEquals(0, message.getExtensionCount(UnittestProto.repeatedStringPieceExtension)); + Assert.assertEquals(0, message.getExtensionCount(UnittestProto.repeatedCordExtension)); + + // hasBlah() should also be false for all default fields. + Assert.assertFalse(message.hasExtension(UnittestProto.defaultInt32Extension )); + Assert.assertFalse(message.hasExtension(UnittestProto.defaultInt64Extension )); + Assert.assertFalse(message.hasExtension(UnittestProto.defaultUint32Extension )); + Assert.assertFalse(message.hasExtension(UnittestProto.defaultUint64Extension )); + Assert.assertFalse(message.hasExtension(UnittestProto.defaultSint32Extension )); + Assert.assertFalse(message.hasExtension(UnittestProto.defaultSint64Extension )); + Assert.assertFalse(message.hasExtension(UnittestProto.defaultFixed32Extension )); + Assert.assertFalse(message.hasExtension(UnittestProto.defaultFixed64Extension )); + Assert.assertFalse(message.hasExtension(UnittestProto.defaultSfixed32Extension)); + Assert.assertFalse(message.hasExtension(UnittestProto.defaultSfixed64Extension)); + Assert.assertFalse(message.hasExtension(UnittestProto.defaultFloatExtension )); + Assert.assertFalse(message.hasExtension(UnittestProto.defaultDoubleExtension )); + Assert.assertFalse(message.hasExtension(UnittestProto.defaultBoolExtension )); + Assert.assertFalse(message.hasExtension(UnittestProto.defaultStringExtension )); + Assert.assertFalse(message.hasExtension(UnittestProto.defaultBytesExtension )); + + Assert.assertFalse(message.hasExtension(UnittestProto.defaultNestedEnumExtension )); + Assert.assertFalse(message.hasExtension(UnittestProto.defaultForeignEnumExtension)); + Assert.assertFalse(message.hasExtension(UnittestProto.defaultImportEnumExtension )); + + Assert.assertFalse(message.hasExtension(UnittestProto.defaultStringPieceExtension)); + Assert.assertFalse(message.hasExtension(UnittestProto.defaultCordExtension)); + + // Fields with defaults have their default values (duh). + assertEqualsExactType( 41 , message.getExtension(UnittestProto.defaultInt32Extension )); + assertEqualsExactType( 42L , message.getExtension(UnittestProto.defaultInt64Extension )); + assertEqualsExactType( 43 , message.getExtension(UnittestProto.defaultUint32Extension )); + assertEqualsExactType( 44L , message.getExtension(UnittestProto.defaultUint64Extension )); + assertEqualsExactType(-45 , message.getExtension(UnittestProto.defaultSint32Extension )); + assertEqualsExactType( 46L , message.getExtension(UnittestProto.defaultSint64Extension )); + assertEqualsExactType( 47 , message.getExtension(UnittestProto.defaultFixed32Extension )); + assertEqualsExactType( 48L , message.getExtension(UnittestProto.defaultFixed64Extension )); + assertEqualsExactType( 49 , message.getExtension(UnittestProto.defaultSfixed32Extension)); + assertEqualsExactType(-50L , message.getExtension(UnittestProto.defaultSfixed64Extension)); + assertEqualsExactType( 51.5F , message.getExtension(UnittestProto.defaultFloatExtension )); + assertEqualsExactType( 52e3D , message.getExtension(UnittestProto.defaultDoubleExtension )); + assertEqualsExactType(true , message.getExtension(UnittestProto.defaultBoolExtension )); + assertEqualsExactType("hello", message.getExtension(UnittestProto.defaultStringExtension )); + assertEqualsExactType(toBytes("world"), message.getExtension(UnittestProto.defaultBytesExtension)); + + assertEqualsExactType(TestAllTypes.NestedEnum.BAR, + message.getExtension(UnittestProto.defaultNestedEnumExtension )); + assertEqualsExactType(ForeignEnum.FOREIGN_BAR, + message.getExtension(UnittestProto.defaultForeignEnumExtension)); + assertEqualsExactType(ImportEnum.IMPORT_BAR, + message.getExtension(UnittestProto.defaultImportEnumExtension)); + + assertEqualsExactType("abc", message.getExtension(UnittestProto.defaultStringPieceExtension)); + assertEqualsExactType("123", message.getExtension(UnittestProto.defaultCordExtension)); + } + + // ------------------------------------------------------------------- + + /** + * Assert (using {@code junit.framework.Assert}} that all extensions of + * {@code message} are set to the values assigned by {@code setAllExtensions} + * followed by {@code modifyRepeatedExtensions}. + */ + public static void assertRepeatedExtensionsModified( + TestAllExtensions message) { + // ModifyRepeatedFields only sets the second repeated element of each + // field. In addition to verifying this, we also verify that the first + // element and size were *not* modified. + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedInt32Extension )); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedInt64Extension )); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedUint32Extension )); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedUint64Extension )); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedSint32Extension )); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedSint64Extension )); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedFixed32Extension )); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedFixed64Extension )); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedSfixed32Extension)); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedSfixed64Extension)); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedFloatExtension )); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedDoubleExtension )); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedBoolExtension )); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedStringExtension )); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedBytesExtension )); + + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedGroupExtension )); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedNestedMessageExtension )); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedForeignMessageExtension)); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedImportMessageExtension )); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedNestedEnumExtension )); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedForeignEnumExtension )); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedImportEnumExtension )); + + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedStringPieceExtension)); + Assert.assertEquals(2, message.getExtensionCount(UnittestProto.repeatedCordExtension)); + + assertEqualsExactType(201 , message.getExtension(UnittestProto.repeatedInt32Extension , 0)); + assertEqualsExactType(202L , message.getExtension(UnittestProto.repeatedInt64Extension , 0)); + assertEqualsExactType(203 , message.getExtension(UnittestProto.repeatedUint32Extension , 0)); + assertEqualsExactType(204L , message.getExtension(UnittestProto.repeatedUint64Extension , 0)); + assertEqualsExactType(205 , message.getExtension(UnittestProto.repeatedSint32Extension , 0)); + assertEqualsExactType(206L , message.getExtension(UnittestProto.repeatedSint64Extension , 0)); + assertEqualsExactType(207 , message.getExtension(UnittestProto.repeatedFixed32Extension , 0)); + assertEqualsExactType(208L , message.getExtension(UnittestProto.repeatedFixed64Extension , 0)); + assertEqualsExactType(209 , message.getExtension(UnittestProto.repeatedSfixed32Extension, 0)); + assertEqualsExactType(210L , message.getExtension(UnittestProto.repeatedSfixed64Extension, 0)); + assertEqualsExactType(211F , message.getExtension(UnittestProto.repeatedFloatExtension , 0)); + assertEqualsExactType(212D , message.getExtension(UnittestProto.repeatedDoubleExtension , 0)); + assertEqualsExactType(true , message.getExtension(UnittestProto.repeatedBoolExtension , 0)); + assertEqualsExactType("215", message.getExtension(UnittestProto.repeatedStringExtension , 0)); + assertEqualsExactType(toBytes("216"), message.getExtension(UnittestProto.repeatedBytesExtension, 0)); + + assertEqualsExactType(217, message.getExtension(UnittestProto.repeatedGroupExtension , 0).getA()); + assertEqualsExactType(218, message.getExtension(UnittestProto.repeatedNestedMessageExtension , 0).getBb()); + assertEqualsExactType(219, message.getExtension(UnittestProto.repeatedForeignMessageExtension, 0).getC()); + assertEqualsExactType(220, message.getExtension(UnittestProto.repeatedImportMessageExtension , 0).getD()); + + assertEqualsExactType(TestAllTypes.NestedEnum.BAR, + message.getExtension(UnittestProto.repeatedNestedEnumExtension, 0)); + assertEqualsExactType(ForeignEnum.FOREIGN_BAR, + message.getExtension(UnittestProto.repeatedForeignEnumExtension, 0)); + assertEqualsExactType(ImportEnum.IMPORT_BAR, + message.getExtension(UnittestProto.repeatedImportEnumExtension, 0)); + + assertEqualsExactType("224", message.getExtension(UnittestProto.repeatedStringPieceExtension, 0)); + assertEqualsExactType("225", message.getExtension(UnittestProto.repeatedCordExtension, 0)); + + // Actually verify the second (modified) elements now. + assertEqualsExactType(501 , message.getExtension(UnittestProto.repeatedInt32Extension , 1)); + assertEqualsExactType(502L , message.getExtension(UnittestProto.repeatedInt64Extension , 1)); + assertEqualsExactType(503 , message.getExtension(UnittestProto.repeatedUint32Extension , 1)); + assertEqualsExactType(504L , message.getExtension(UnittestProto.repeatedUint64Extension , 1)); + assertEqualsExactType(505 , message.getExtension(UnittestProto.repeatedSint32Extension , 1)); + assertEqualsExactType(506L , message.getExtension(UnittestProto.repeatedSint64Extension , 1)); + assertEqualsExactType(507 , message.getExtension(UnittestProto.repeatedFixed32Extension , 1)); + assertEqualsExactType(508L , message.getExtension(UnittestProto.repeatedFixed64Extension , 1)); + assertEqualsExactType(509 , message.getExtension(UnittestProto.repeatedSfixed32Extension, 1)); + assertEqualsExactType(510L , message.getExtension(UnittestProto.repeatedSfixed64Extension, 1)); + assertEqualsExactType(511F , message.getExtension(UnittestProto.repeatedFloatExtension , 1)); + assertEqualsExactType(512D , message.getExtension(UnittestProto.repeatedDoubleExtension , 1)); + assertEqualsExactType(true , message.getExtension(UnittestProto.repeatedBoolExtension , 1)); + assertEqualsExactType("515", message.getExtension(UnittestProto.repeatedStringExtension , 1)); + assertEqualsExactType(toBytes("516"), message.getExtension(UnittestProto.repeatedBytesExtension, 1)); + + assertEqualsExactType(517, message.getExtension(UnittestProto.repeatedGroupExtension , 1).getA()); + assertEqualsExactType(518, message.getExtension(UnittestProto.repeatedNestedMessageExtension , 1).getBb()); + assertEqualsExactType(519, message.getExtension(UnittestProto.repeatedForeignMessageExtension, 1).getC()); + assertEqualsExactType(520, message.getExtension(UnittestProto.repeatedImportMessageExtension , 1).getD()); + + assertEqualsExactType(TestAllTypes.NestedEnum.FOO, + message.getExtension(UnittestProto.repeatedNestedEnumExtension, 1)); + assertEqualsExactType(ForeignEnum.FOREIGN_FOO, + message.getExtension(UnittestProto.repeatedForeignEnumExtension, 1)); + assertEqualsExactType(ImportEnum.IMPORT_FOO, + message.getExtension(UnittestProto.repeatedImportEnumExtension, 1)); + + assertEqualsExactType("524", message.getExtension(UnittestProto.repeatedStringPieceExtension, 1)); + assertEqualsExactType("525", message.getExtension(UnittestProto.repeatedCordExtension, 1)); + } + + // =================================================================== + + /** + * Performs the same things that the methods of {@code TestUtil} do, but + * via the reflection interface. This is its own class because it needs + * to know what descriptor to use. + */ + public static class ReflectionTester { + private final Descriptors.Descriptor baseDescriptor; + private final ExtensionRegistry extensionRegistry; + + private final Descriptors.FileDescriptor file; + private final Descriptors.FileDescriptor importFile; + + private final Descriptors.Descriptor optionalGroup; + private final Descriptors.Descriptor repeatedGroup; + private final Descriptors.Descriptor nestedMessage; + private final Descriptors.Descriptor foreignMessage; + private final Descriptors.Descriptor importMessage; + + private final Descriptors.FieldDescriptor groupA; + private final Descriptors.FieldDescriptor repeatedGroupA; + private final Descriptors.FieldDescriptor nestedB; + private final Descriptors.FieldDescriptor foreignC; + private final Descriptors.FieldDescriptor importD; + + private final Descriptors.EnumDescriptor nestedEnum; + private final Descriptors.EnumDescriptor foreignEnum; + private final Descriptors.EnumDescriptor importEnum; + + private final Descriptors.EnumValueDescriptor nestedFoo; + private final Descriptors.EnumValueDescriptor nestedBar; + private final Descriptors.EnumValueDescriptor nestedBaz; + private final Descriptors.EnumValueDescriptor foreignFoo; + private final Descriptors.EnumValueDescriptor foreignBar; + private final Descriptors.EnumValueDescriptor foreignBaz; + private final Descriptors.EnumValueDescriptor importFoo; + private final Descriptors.EnumValueDescriptor importBar; + private final Descriptors.EnumValueDescriptor importBaz; + + /** + * Construct a {@code ReflectionTester} that will expect messages using + * the given descriptor. + * + * Normally {@code baseDescriptor} should be a descriptor for the type + * {@code TestAllTypes}, defined in + * {@code google/protobuf/unittest.proto}. However, if + * {@code extensionRegistry} is non-null, then {@code baseDescriptor} should + * be for {@code TestAllExtensions} instead, and instead of reading and + * writing normal fields, the tester will read and write extensions. + * All of {@code TestAllExtensions}' extensions must be registered in the + * registry. + */ + public ReflectionTester(Descriptors.Descriptor baseDescriptor, + ExtensionRegistry extensionRegistry) { + this.baseDescriptor = baseDescriptor; + this.extensionRegistry = extensionRegistry; + + this.file = baseDescriptor.getFile(); + Assert.assertEquals(1, file.getDependencies().size()); + this.importFile = file.getDependencies().get(0); + + Descriptors.Descriptor testAllTypes; + if (extensionRegistry == null) { + testAllTypes = baseDescriptor; + } else { + testAllTypes = file.findMessageTypeByName("TestAllTypes"); + Assert.assertNotNull(testAllTypes); + } + + if (extensionRegistry == null) { + this.optionalGroup = + baseDescriptor.findNestedTypeByName("OptionalGroup"); + this.repeatedGroup = + baseDescriptor.findNestedTypeByName("RepeatedGroup"); + } else { + this.optionalGroup = + file.findMessageTypeByName("OptionalGroup_extension"); + this.repeatedGroup = + file.findMessageTypeByName("RepeatedGroup_extension"); + } + this.nestedMessage = testAllTypes.findNestedTypeByName("NestedMessage"); + this.foreignMessage = file.findMessageTypeByName("ForeignMessage"); + this.importMessage = importFile.findMessageTypeByName("ImportMessage"); + + this.nestedEnum = testAllTypes.findEnumTypeByName("NestedEnum"); + this.foreignEnum = file.findEnumTypeByName("ForeignEnum"); + this.importEnum = importFile.findEnumTypeByName("ImportEnum"); + + Assert.assertNotNull(optionalGroup ); + Assert.assertNotNull(repeatedGroup ); + Assert.assertNotNull(nestedMessage ); + Assert.assertNotNull(foreignMessage); + Assert.assertNotNull(importMessage ); + Assert.assertNotNull(nestedEnum ); + Assert.assertNotNull(foreignEnum ); + Assert.assertNotNull(importEnum ); + + this.nestedB = nestedMessage .findFieldByName("bb"); + this.foreignC = foreignMessage.findFieldByName("c"); + this.importD = importMessage .findFieldByName("d"); + this.nestedFoo = nestedEnum.findValueByName("FOO"); + this.nestedBar = nestedEnum.findValueByName("BAR"); + this.nestedBaz = nestedEnum.findValueByName("BAZ"); + this.foreignFoo = foreignEnum.findValueByName("FOREIGN_FOO"); + this.foreignBar = foreignEnum.findValueByName("FOREIGN_BAR"); + this.foreignBaz = foreignEnum.findValueByName("FOREIGN_BAZ"); + this.importFoo = importEnum.findValueByName("IMPORT_FOO"); + this.importBar = importEnum.findValueByName("IMPORT_BAR"); + this.importBaz = importEnum.findValueByName("IMPORT_BAZ"); + + this.groupA = optionalGroup.findFieldByName("a"); + this.repeatedGroupA = repeatedGroup.findFieldByName("a"); + + Assert.assertNotNull(groupA ); + Assert.assertNotNull(repeatedGroupA); + Assert.assertNotNull(nestedB ); + Assert.assertNotNull(foreignC ); + Assert.assertNotNull(importD ); + Assert.assertNotNull(nestedFoo ); + Assert.assertNotNull(nestedBar ); + Assert.assertNotNull(nestedBaz ); + Assert.assertNotNull(foreignFoo ); + Assert.assertNotNull(foreignBar ); + Assert.assertNotNull(foreignBaz ); + Assert.assertNotNull(importFoo ); + Assert.assertNotNull(importBar ); + Assert.assertNotNull(importBaz ); + } + + /** + * Shorthand to get a FieldDescriptor for a field of unittest::TestAllTypes. + */ + private Descriptors.FieldDescriptor f(String name) { + Descriptors.FieldDescriptor result; + if (extensionRegistry == null) { + result = baseDescriptor.findFieldByName(name); + } else { + result = file.findExtensionByName(name + "_extension"); + } + Assert.assertNotNull(result); + return result; + } + + /** + * Calls {@code parent.newBuilderForField()} or uses the + * {@code ExtensionRegistry} to find an appropriate builder, depending + * on what type is being tested. + */ + private Message.Builder newBuilderForField( + Message.Builder parent, Descriptors.FieldDescriptor field) { + if (extensionRegistry == null) { + return parent.newBuilderForField(field); + } else { + ExtensionRegistry.ExtensionInfo extension = + extensionRegistry.findExtensionByNumber(field.getContainingType(), + field.getNumber()); + Assert.assertNotNull(extension); + Assert.assertNotNull(extension.defaultInstance); + return extension.defaultInstance.newBuilderForType(); + } + } + + // ------------------------------------------------------------------- + + /** + * Set every field of {@code message} to the values expected by + * {@code assertAllFieldsSet()}, using the {@link Message.Builder} + * reflection interface. + */ + void setAllFieldsViaReflection(Message.Builder message) { + message.setField(f("optional_int32" ), 101 ); + message.setField(f("optional_int64" ), 102L); + message.setField(f("optional_uint32" ), 103 ); + message.setField(f("optional_uint64" ), 104L); + message.setField(f("optional_sint32" ), 105 ); + message.setField(f("optional_sint64" ), 106L); + message.setField(f("optional_fixed32" ), 107 ); + message.setField(f("optional_fixed64" ), 108L); + message.setField(f("optional_sfixed32"), 109 ); + message.setField(f("optional_sfixed64"), 110L); + message.setField(f("optional_float" ), 111F); + message.setField(f("optional_double" ), 112D); + message.setField(f("optional_bool" ), true); + message.setField(f("optional_string" ), "115"); + message.setField(f("optional_bytes" ), toBytes("116")); + + message.setField(f("optionalgroup"), + newBuilderForField(message, f("optionalgroup")) + .setField(groupA, 117).build()); + message.setField(f("optional_nested_message"), + newBuilderForField(message, f("optional_nested_message")) + .setField(nestedB, 118).build()); + message.setField(f("optional_foreign_message"), + newBuilderForField(message, f("optional_foreign_message")) + .setField(foreignC, 119).build()); + message.setField(f("optional_import_message"), + newBuilderForField(message, f("optional_import_message")) + .setField(importD, 120).build()); + + message.setField(f("optional_nested_enum" ), nestedBaz); + message.setField(f("optional_foreign_enum"), foreignBaz); + message.setField(f("optional_import_enum" ), importBaz); + + message.setField(f("optional_string_piece" ), "124"); + message.setField(f("optional_cord" ), "125"); + + // ----------------------------------------------------------------- + + message.addRepeatedField(f("repeated_int32" ), 201 ); + message.addRepeatedField(f("repeated_int64" ), 202L); + message.addRepeatedField(f("repeated_uint32" ), 203 ); + message.addRepeatedField(f("repeated_uint64" ), 204L); + message.addRepeatedField(f("repeated_sint32" ), 205 ); + message.addRepeatedField(f("repeated_sint64" ), 206L); + message.addRepeatedField(f("repeated_fixed32" ), 207 ); + message.addRepeatedField(f("repeated_fixed64" ), 208L); + message.addRepeatedField(f("repeated_sfixed32"), 209 ); + message.addRepeatedField(f("repeated_sfixed64"), 210L); + message.addRepeatedField(f("repeated_float" ), 211F); + message.addRepeatedField(f("repeated_double" ), 212D); + message.addRepeatedField(f("repeated_bool" ), true); + message.addRepeatedField(f("repeated_string" ), "215"); + message.addRepeatedField(f("repeated_bytes" ), toBytes("216")); + + message.addRepeatedField(f("repeatedgroup"), + newBuilderForField(message, f("repeatedgroup")) + .setField(repeatedGroupA, 217).build()); + message.addRepeatedField(f("repeated_nested_message"), + newBuilderForField(message, f("repeated_nested_message")) + .setField(nestedB, 218).build()); + message.addRepeatedField(f("repeated_foreign_message"), + newBuilderForField(message, f("repeated_foreign_message")) + .setField(foreignC, 219).build()); + message.addRepeatedField(f("repeated_import_message"), + newBuilderForField(message, f("repeated_import_message")) + .setField(importD, 220).build()); + + message.addRepeatedField(f("repeated_nested_enum" ), nestedBar); + message.addRepeatedField(f("repeated_foreign_enum"), foreignBar); + message.addRepeatedField(f("repeated_import_enum" ), importBar); + + message.addRepeatedField(f("repeated_string_piece" ), "224"); + message.addRepeatedField(f("repeated_cord" ), "225"); + + // Add a second one of each field. + message.addRepeatedField(f("repeated_int32" ), 301 ); + message.addRepeatedField(f("repeated_int64" ), 302L); + message.addRepeatedField(f("repeated_uint32" ), 303 ); + message.addRepeatedField(f("repeated_uint64" ), 304L); + message.addRepeatedField(f("repeated_sint32" ), 305 ); + message.addRepeatedField(f("repeated_sint64" ), 306L); + message.addRepeatedField(f("repeated_fixed32" ), 307 ); + message.addRepeatedField(f("repeated_fixed64" ), 308L); + message.addRepeatedField(f("repeated_sfixed32"), 309 ); + message.addRepeatedField(f("repeated_sfixed64"), 310L); + message.addRepeatedField(f("repeated_float" ), 311F); + message.addRepeatedField(f("repeated_double" ), 312D); + message.addRepeatedField(f("repeated_bool" ), false); + message.addRepeatedField(f("repeated_string" ), "315"); + message.addRepeatedField(f("repeated_bytes" ), toBytes("316")); + + message.addRepeatedField(f("repeatedgroup"), + newBuilderForField(message, f("repeatedgroup")) + .setField(repeatedGroupA, 317).build()); + message.addRepeatedField(f("repeated_nested_message"), + newBuilderForField(message, f("repeated_nested_message")) + .setField(nestedB, 318).build()); + message.addRepeatedField(f("repeated_foreign_message"), + newBuilderForField(message, f("repeated_foreign_message")) + .setField(foreignC, 319).build()); + message.addRepeatedField(f("repeated_import_message"), + newBuilderForField(message, f("repeated_import_message")) + .setField(importD, 320).build()); + + message.addRepeatedField(f("repeated_nested_enum" ), nestedBaz); + message.addRepeatedField(f("repeated_foreign_enum"), foreignBaz); + message.addRepeatedField(f("repeated_import_enum" ), importBaz); + + message.addRepeatedField(f("repeated_string_piece" ), "324"); + message.addRepeatedField(f("repeated_cord" ), "325"); + + // ----------------------------------------------------------------- + + message.setField(f("default_int32" ), 401 ); + message.setField(f("default_int64" ), 402L); + message.setField(f("default_uint32" ), 403 ); + message.setField(f("default_uint64" ), 404L); + message.setField(f("default_sint32" ), 405 ); + message.setField(f("default_sint64" ), 406L); + message.setField(f("default_fixed32" ), 407 ); + message.setField(f("default_fixed64" ), 408L); + message.setField(f("default_sfixed32"), 409 ); + message.setField(f("default_sfixed64"), 410L); + message.setField(f("default_float" ), 411F); + message.setField(f("default_double" ), 412D); + message.setField(f("default_bool" ), false); + message.setField(f("default_string" ), "415"); + message.setField(f("default_bytes" ), toBytes("416")); + + message.setField(f("default_nested_enum" ), nestedFoo); + message.setField(f("default_foreign_enum"), foreignFoo); + message.setField(f("default_import_enum" ), importFoo); + + message.setField(f("default_string_piece" ), "424"); + message.setField(f("default_cord" ), "425"); + } + + // ------------------------------------------------------------------- + + /** + * Modify the repeated fields of {@code message} to contain the values + * expected by {@code assertRepeatedFieldsModified()}, using the + * {@link Message.Builder} reflection interface. + */ + void modifyRepeatedFieldsViaReflection(Message.Builder message) { + message.setRepeatedField(f("repeated_int32" ), 1, 501 ); + message.setRepeatedField(f("repeated_int64" ), 1, 502L); + message.setRepeatedField(f("repeated_uint32" ), 1, 503 ); + message.setRepeatedField(f("repeated_uint64" ), 1, 504L); + message.setRepeatedField(f("repeated_sint32" ), 1, 505 ); + message.setRepeatedField(f("repeated_sint64" ), 1, 506L); + message.setRepeatedField(f("repeated_fixed32" ), 1, 507 ); + message.setRepeatedField(f("repeated_fixed64" ), 1, 508L); + message.setRepeatedField(f("repeated_sfixed32"), 1, 509 ); + message.setRepeatedField(f("repeated_sfixed64"), 1, 510L); + message.setRepeatedField(f("repeated_float" ), 1, 511F); + message.setRepeatedField(f("repeated_double" ), 1, 512D); + message.setRepeatedField(f("repeated_bool" ), 1, true); + message.setRepeatedField(f("repeated_string" ), 1, "515"); + message.setRepeatedField(f("repeated_bytes" ), 1, toBytes("516")); + + message.setRepeatedField(f("repeatedgroup"), 1, + newBuilderForField(message, f("repeatedgroup")) + .setField(repeatedGroupA, 517).build()); + message.setRepeatedField(f("repeated_nested_message"), 1, + newBuilderForField(message, f("repeated_nested_message")) + .setField(nestedB, 518).build()); + message.setRepeatedField(f("repeated_foreign_message"), 1, + newBuilderForField(message, f("repeated_foreign_message")) + .setField(foreignC, 519).build()); + message.setRepeatedField(f("repeated_import_message"), 1, + newBuilderForField(message, f("repeated_import_message")) + .setField(importD, 520).build()); + + message.setRepeatedField(f("repeated_nested_enum" ), 1, nestedFoo); + message.setRepeatedField(f("repeated_foreign_enum"), 1, foreignFoo); + message.setRepeatedField(f("repeated_import_enum" ), 1, importFoo); + + message.setRepeatedField(f("repeated_string_piece"), 1, "524"); + message.setRepeatedField(f("repeated_cord"), 1, "525"); + } + + // ------------------------------------------------------------------- + + /** + * Assert (using {@code junit.framework.Assert}} that all fields of + * {@code message} are set to the values assigned by {@code setAllFields}, + * using the {@link Message} reflection interface. + */ + public void assertAllFieldsSetViaReflection(Message message) { + Assert.assertTrue(message.hasField(f("optional_int32" ))); + Assert.assertTrue(message.hasField(f("optional_int64" ))); + Assert.assertTrue(message.hasField(f("optional_uint32" ))); + Assert.assertTrue(message.hasField(f("optional_uint64" ))); + Assert.assertTrue(message.hasField(f("optional_sint32" ))); + Assert.assertTrue(message.hasField(f("optional_sint64" ))); + Assert.assertTrue(message.hasField(f("optional_fixed32" ))); + Assert.assertTrue(message.hasField(f("optional_fixed64" ))); + Assert.assertTrue(message.hasField(f("optional_sfixed32"))); + Assert.assertTrue(message.hasField(f("optional_sfixed64"))); + Assert.assertTrue(message.hasField(f("optional_float" ))); + Assert.assertTrue(message.hasField(f("optional_double" ))); + Assert.assertTrue(message.hasField(f("optional_bool" ))); + Assert.assertTrue(message.hasField(f("optional_string" ))); + Assert.assertTrue(message.hasField(f("optional_bytes" ))); + + Assert.assertTrue(message.hasField(f("optionalgroup" ))); + Assert.assertTrue(message.hasField(f("optional_nested_message" ))); + Assert.assertTrue(message.hasField(f("optional_foreign_message"))); + Assert.assertTrue(message.hasField(f("optional_import_message" ))); + + Assert.assertTrue( + ((Message)message.getField(f("optionalgroup"))).hasField(groupA)); + Assert.assertTrue( + ((Message)message.getField(f("optional_nested_message"))) + .hasField(nestedB)); + Assert.assertTrue( + ((Message)message.getField(f("optional_foreign_message"))) + .hasField(foreignC)); + Assert.assertTrue( + ((Message)message.getField(f("optional_import_message"))) + .hasField(importD)); + + Assert.assertTrue(message.hasField(f("optional_nested_enum" ))); + Assert.assertTrue(message.hasField(f("optional_foreign_enum"))); + Assert.assertTrue(message.hasField(f("optional_import_enum" ))); + + Assert.assertTrue(message.hasField(f("optional_string_piece"))); + Assert.assertTrue(message.hasField(f("optional_cord"))); + + Assert.assertEquals(101 , message.getField(f("optional_int32" ))); + Assert.assertEquals(102L , message.getField(f("optional_int64" ))); + Assert.assertEquals(103 , message.getField(f("optional_uint32" ))); + Assert.assertEquals(104L , message.getField(f("optional_uint64" ))); + Assert.assertEquals(105 , message.getField(f("optional_sint32" ))); + Assert.assertEquals(106L , message.getField(f("optional_sint64" ))); + Assert.assertEquals(107 , message.getField(f("optional_fixed32" ))); + Assert.assertEquals(108L , message.getField(f("optional_fixed64" ))); + Assert.assertEquals(109 , message.getField(f("optional_sfixed32"))); + Assert.assertEquals(110L , message.getField(f("optional_sfixed64"))); + Assert.assertEquals(111F , message.getField(f("optional_float" ))); + Assert.assertEquals(112D , message.getField(f("optional_double" ))); + Assert.assertEquals(true , message.getField(f("optional_bool" ))); + Assert.assertEquals("115", message.getField(f("optional_string" ))); + Assert.assertEquals(toBytes("116"), message.getField(f("optional_bytes"))); + + Assert.assertEquals(117, + ((Message)message.getField(f("optionalgroup"))).getField(groupA)); + Assert.assertEquals(118, + ((Message)message.getField(f("optional_nested_message"))) + .getField(nestedB)); + Assert.assertEquals(119, + ((Message)message.getField(f("optional_foreign_message"))) + .getField(foreignC)); + Assert.assertEquals(120, + ((Message)message.getField(f("optional_import_message"))) + .getField(importD)); + + Assert.assertEquals( nestedBaz, message.getField(f("optional_nested_enum" ))); + Assert.assertEquals(foreignBaz, message.getField(f("optional_foreign_enum"))); + Assert.assertEquals( importBaz, message.getField(f("optional_import_enum" ))); + + Assert.assertEquals("124", message.getField(f("optional_string_piece"))); + Assert.assertEquals("125", message.getField(f("optional_cord"))); + + // ----------------------------------------------------------------- + + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_int32" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_int64" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_uint32" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_uint64" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_sint32" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_sint64" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_fixed32" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_fixed64" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_sfixed32"))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_sfixed64"))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_float" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_double" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_bool" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_string" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_bytes" ))); + + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeatedgroup" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_nested_message" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_foreign_message"))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_import_message" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_nested_enum" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_foreign_enum" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_import_enum" ))); + + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_string_piece"))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_cord"))); + + Assert.assertEquals(201 , message.getRepeatedField(f("repeated_int32" ), 0)); + Assert.assertEquals(202L , message.getRepeatedField(f("repeated_int64" ), 0)); + Assert.assertEquals(203 , message.getRepeatedField(f("repeated_uint32" ), 0)); + Assert.assertEquals(204L , message.getRepeatedField(f("repeated_uint64" ), 0)); + Assert.assertEquals(205 , message.getRepeatedField(f("repeated_sint32" ), 0)); + Assert.assertEquals(206L , message.getRepeatedField(f("repeated_sint64" ), 0)); + Assert.assertEquals(207 , message.getRepeatedField(f("repeated_fixed32" ), 0)); + Assert.assertEquals(208L , message.getRepeatedField(f("repeated_fixed64" ), 0)); + Assert.assertEquals(209 , message.getRepeatedField(f("repeated_sfixed32"), 0)); + Assert.assertEquals(210L , message.getRepeatedField(f("repeated_sfixed64"), 0)); + Assert.assertEquals(211F , message.getRepeatedField(f("repeated_float" ), 0)); + Assert.assertEquals(212D , message.getRepeatedField(f("repeated_double" ), 0)); + Assert.assertEquals(true , message.getRepeatedField(f("repeated_bool" ), 0)); + Assert.assertEquals("215", message.getRepeatedField(f("repeated_string" ), 0)); + Assert.assertEquals(toBytes("216"), message.getRepeatedField(f("repeated_bytes"), 0)); + + Assert.assertEquals(217, + ((Message)message.getRepeatedField(f("repeatedgroup"), 0)) + .getField(repeatedGroupA)); + Assert.assertEquals(218, + ((Message)message.getRepeatedField(f("repeated_nested_message"), 0)) + .getField(nestedB)); + Assert.assertEquals(219, + ((Message)message.getRepeatedField(f("repeated_foreign_message"), 0)) + .getField(foreignC)); + Assert.assertEquals(220, + ((Message)message.getRepeatedField(f("repeated_import_message"), 0)) + .getField(importD)); + + Assert.assertEquals( nestedBar, message.getRepeatedField(f("repeated_nested_enum" ),0)); + Assert.assertEquals(foreignBar, message.getRepeatedField(f("repeated_foreign_enum"),0)); + Assert.assertEquals( importBar, message.getRepeatedField(f("repeated_import_enum" ),0)); + + Assert.assertEquals("224", message.getRepeatedField(f("repeated_string_piece"), 0)); + Assert.assertEquals("225", message.getRepeatedField(f("repeated_cord"), 0)); + + Assert.assertEquals(301 , message.getRepeatedField(f("repeated_int32" ), 1)); + Assert.assertEquals(302L , message.getRepeatedField(f("repeated_int64" ), 1)); + Assert.assertEquals(303 , message.getRepeatedField(f("repeated_uint32" ), 1)); + Assert.assertEquals(304L , message.getRepeatedField(f("repeated_uint64" ), 1)); + Assert.assertEquals(305 , message.getRepeatedField(f("repeated_sint32" ), 1)); + Assert.assertEquals(306L , message.getRepeatedField(f("repeated_sint64" ), 1)); + Assert.assertEquals(307 , message.getRepeatedField(f("repeated_fixed32" ), 1)); + Assert.assertEquals(308L , message.getRepeatedField(f("repeated_fixed64" ), 1)); + Assert.assertEquals(309 , message.getRepeatedField(f("repeated_sfixed32"), 1)); + Assert.assertEquals(310L , message.getRepeatedField(f("repeated_sfixed64"), 1)); + Assert.assertEquals(311F , message.getRepeatedField(f("repeated_float" ), 1)); + Assert.assertEquals(312D , message.getRepeatedField(f("repeated_double" ), 1)); + Assert.assertEquals(false, message.getRepeatedField(f("repeated_bool" ), 1)); + Assert.assertEquals("315", message.getRepeatedField(f("repeated_string" ), 1)); + Assert.assertEquals(toBytes("316"), message.getRepeatedField(f("repeated_bytes"), 1)); + + Assert.assertEquals(317, + ((Message)message.getRepeatedField(f("repeatedgroup"), 1)) + .getField(repeatedGroupA)); + Assert.assertEquals(318, + ((Message)message.getRepeatedField(f("repeated_nested_message"), 1)) + .getField(nestedB)); + Assert.assertEquals(319, + ((Message)message.getRepeatedField(f("repeated_foreign_message"), 1)) + .getField(foreignC)); + Assert.assertEquals(320, + ((Message)message.getRepeatedField(f("repeated_import_message"), 1)) + .getField(importD)); + + Assert.assertEquals( nestedBaz, message.getRepeatedField(f("repeated_nested_enum" ),1)); + Assert.assertEquals(foreignBaz, message.getRepeatedField(f("repeated_foreign_enum"),1)); + Assert.assertEquals( importBaz, message.getRepeatedField(f("repeated_import_enum" ),1)); + + Assert.assertEquals("324", message.getRepeatedField(f("repeated_string_piece"), 1)); + Assert.assertEquals("325", message.getRepeatedField(f("repeated_cord"), 1)); + + // ----------------------------------------------------------------- + + Assert.assertTrue(message.hasField(f("default_int32" ))); + Assert.assertTrue(message.hasField(f("default_int64" ))); + Assert.assertTrue(message.hasField(f("default_uint32" ))); + Assert.assertTrue(message.hasField(f("default_uint64" ))); + Assert.assertTrue(message.hasField(f("default_sint32" ))); + Assert.assertTrue(message.hasField(f("default_sint64" ))); + Assert.assertTrue(message.hasField(f("default_fixed32" ))); + Assert.assertTrue(message.hasField(f("default_fixed64" ))); + Assert.assertTrue(message.hasField(f("default_sfixed32"))); + Assert.assertTrue(message.hasField(f("default_sfixed64"))); + Assert.assertTrue(message.hasField(f("default_float" ))); + Assert.assertTrue(message.hasField(f("default_double" ))); + Assert.assertTrue(message.hasField(f("default_bool" ))); + Assert.assertTrue(message.hasField(f("default_string" ))); + Assert.assertTrue(message.hasField(f("default_bytes" ))); + + Assert.assertTrue(message.hasField(f("default_nested_enum" ))); + Assert.assertTrue(message.hasField(f("default_foreign_enum"))); + Assert.assertTrue(message.hasField(f("default_import_enum" ))); + + Assert.assertTrue(message.hasField(f("default_string_piece"))); + Assert.assertTrue(message.hasField(f("default_cord"))); + + Assert.assertEquals(401 , message.getField(f("default_int32" ))); + Assert.assertEquals(402L , message.getField(f("default_int64" ))); + Assert.assertEquals(403 , message.getField(f("default_uint32" ))); + Assert.assertEquals(404L , message.getField(f("default_uint64" ))); + Assert.assertEquals(405 , message.getField(f("default_sint32" ))); + Assert.assertEquals(406L , message.getField(f("default_sint64" ))); + Assert.assertEquals(407 , message.getField(f("default_fixed32" ))); + Assert.assertEquals(408L , message.getField(f("default_fixed64" ))); + Assert.assertEquals(409 , message.getField(f("default_sfixed32"))); + Assert.assertEquals(410L , message.getField(f("default_sfixed64"))); + Assert.assertEquals(411F , message.getField(f("default_float" ))); + Assert.assertEquals(412D , message.getField(f("default_double" ))); + Assert.assertEquals(false, message.getField(f("default_bool" ))); + Assert.assertEquals("415", message.getField(f("default_string" ))); + Assert.assertEquals(toBytes("416"), message.getField(f("default_bytes"))); + + Assert.assertEquals( nestedFoo, message.getField(f("default_nested_enum" ))); + Assert.assertEquals(foreignFoo, message.getField(f("default_foreign_enum"))); + Assert.assertEquals( importFoo, message.getField(f("default_import_enum" ))); + + Assert.assertEquals("424", message.getField(f("default_string_piece"))); + Assert.assertEquals("425", message.getField(f("default_cord"))); + } + + // ------------------------------------------------------------------- + + /** + * Assert (using {@code junit.framework.Assert}} that all fields of + * {@code message} are cleared, and that getting the fields returns their + * default values, using the {@link Message} reflection interface. + */ + public void assertClearViaReflection(Message message) { + // has_blah() should initially be false for all optional fields. + Assert.assertFalse(message.hasField(f("optional_int32" ))); + Assert.assertFalse(message.hasField(f("optional_int64" ))); + Assert.assertFalse(message.hasField(f("optional_uint32" ))); + Assert.assertFalse(message.hasField(f("optional_uint64" ))); + Assert.assertFalse(message.hasField(f("optional_sint32" ))); + Assert.assertFalse(message.hasField(f("optional_sint64" ))); + Assert.assertFalse(message.hasField(f("optional_fixed32" ))); + Assert.assertFalse(message.hasField(f("optional_fixed64" ))); + Assert.assertFalse(message.hasField(f("optional_sfixed32"))); + Assert.assertFalse(message.hasField(f("optional_sfixed64"))); + Assert.assertFalse(message.hasField(f("optional_float" ))); + Assert.assertFalse(message.hasField(f("optional_double" ))); + Assert.assertFalse(message.hasField(f("optional_bool" ))); + Assert.assertFalse(message.hasField(f("optional_string" ))); + Assert.assertFalse(message.hasField(f("optional_bytes" ))); + + Assert.assertFalse(message.hasField(f("optionalgroup" ))); + Assert.assertFalse(message.hasField(f("optional_nested_message" ))); + Assert.assertFalse(message.hasField(f("optional_foreign_message"))); + Assert.assertFalse(message.hasField(f("optional_import_message" ))); + + Assert.assertFalse(message.hasField(f("optional_nested_enum" ))); + Assert.assertFalse(message.hasField(f("optional_foreign_enum"))); + Assert.assertFalse(message.hasField(f("optional_import_enum" ))); + + Assert.assertFalse(message.hasField(f("optional_string_piece"))); + Assert.assertFalse(message.hasField(f("optional_cord"))); + + // Optional fields without defaults are set to zero or something like it. + Assert.assertEquals(0 , message.getField(f("optional_int32" ))); + Assert.assertEquals(0L , message.getField(f("optional_int64" ))); + Assert.assertEquals(0 , message.getField(f("optional_uint32" ))); + Assert.assertEquals(0L , message.getField(f("optional_uint64" ))); + Assert.assertEquals(0 , message.getField(f("optional_sint32" ))); + Assert.assertEquals(0L , message.getField(f("optional_sint64" ))); + Assert.assertEquals(0 , message.getField(f("optional_fixed32" ))); + Assert.assertEquals(0L , message.getField(f("optional_fixed64" ))); + Assert.assertEquals(0 , message.getField(f("optional_sfixed32"))); + Assert.assertEquals(0L , message.getField(f("optional_sfixed64"))); + Assert.assertEquals(0F , message.getField(f("optional_float" ))); + Assert.assertEquals(0D , message.getField(f("optional_double" ))); + Assert.assertEquals(false, message.getField(f("optional_bool" ))); + Assert.assertEquals("" , message.getField(f("optional_string" ))); + Assert.assertEquals(ByteString.EMPTY, message.getField(f("optional_bytes"))); + + // Embedded messages should also be clear. + Assert.assertFalse( + ((Message)message.getField(f("optionalgroup"))).hasField(groupA)); + Assert.assertFalse( + ((Message)message.getField(f("optional_nested_message"))) + .hasField(nestedB)); + Assert.assertFalse( + ((Message)message.getField(f("optional_foreign_message"))) + .hasField(foreignC)); + Assert.assertFalse( + ((Message)message.getField(f("optional_import_message"))) + .hasField(importD)); + + Assert.assertEquals(0, + ((Message)message.getField(f("optionalgroup"))).getField(groupA)); + Assert.assertEquals(0, + ((Message)message.getField(f("optional_nested_message"))) + .getField(nestedB)); + Assert.assertEquals(0, + ((Message)message.getField(f("optional_foreign_message"))) + .getField(foreignC)); + Assert.assertEquals(0, + ((Message)message.getField(f("optional_import_message"))) + .getField(importD)); + + // Enums without defaults are set to the first value in the enum. + Assert.assertEquals( nestedFoo, message.getField(f("optional_nested_enum" ))); + Assert.assertEquals(foreignFoo, message.getField(f("optional_foreign_enum"))); + Assert.assertEquals( importFoo, message.getField(f("optional_import_enum" ))); + + Assert.assertEquals("", message.getField(f("optional_string_piece"))); + Assert.assertEquals("", message.getField(f("optional_cord"))); + + // Repeated fields are empty. + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_int32" ))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_int64" ))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_uint32" ))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_uint64" ))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_sint32" ))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_sint64" ))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_fixed32" ))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_fixed64" ))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_sfixed32"))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_sfixed64"))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_float" ))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_double" ))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_bool" ))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_string" ))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_bytes" ))); + + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeatedgroup" ))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_nested_message" ))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_foreign_message"))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_import_message" ))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_nested_enum" ))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_foreign_enum" ))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_import_enum" ))); + + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_string_piece"))); + Assert.assertEquals(0, message.getRepeatedFieldCount(f("repeated_cord"))); + + // has_blah() should also be false for all default fields. + Assert.assertFalse(message.hasField(f("default_int32" ))); + Assert.assertFalse(message.hasField(f("default_int64" ))); + Assert.assertFalse(message.hasField(f("default_uint32" ))); + Assert.assertFalse(message.hasField(f("default_uint64" ))); + Assert.assertFalse(message.hasField(f("default_sint32" ))); + Assert.assertFalse(message.hasField(f("default_sint64" ))); + Assert.assertFalse(message.hasField(f("default_fixed32" ))); + Assert.assertFalse(message.hasField(f("default_fixed64" ))); + Assert.assertFalse(message.hasField(f("default_sfixed32"))); + Assert.assertFalse(message.hasField(f("default_sfixed64"))); + Assert.assertFalse(message.hasField(f("default_float" ))); + Assert.assertFalse(message.hasField(f("default_double" ))); + Assert.assertFalse(message.hasField(f("default_bool" ))); + Assert.assertFalse(message.hasField(f("default_string" ))); + Assert.assertFalse(message.hasField(f("default_bytes" ))); + + Assert.assertFalse(message.hasField(f("default_nested_enum" ))); + Assert.assertFalse(message.hasField(f("default_foreign_enum"))); + Assert.assertFalse(message.hasField(f("default_import_enum" ))); + + Assert.assertFalse(message.hasField(f("default_string_piece" ))); + Assert.assertFalse(message.hasField(f("default_cord" ))); + + // Fields with defaults have their default values (duh). + Assert.assertEquals( 41 , message.getField(f("default_int32" ))); + Assert.assertEquals( 42L , message.getField(f("default_int64" ))); + Assert.assertEquals( 43 , message.getField(f("default_uint32" ))); + Assert.assertEquals( 44L , message.getField(f("default_uint64" ))); + Assert.assertEquals(-45 , message.getField(f("default_sint32" ))); + Assert.assertEquals( 46L , message.getField(f("default_sint64" ))); + Assert.assertEquals( 47 , message.getField(f("default_fixed32" ))); + Assert.assertEquals( 48L , message.getField(f("default_fixed64" ))); + Assert.assertEquals( 49 , message.getField(f("default_sfixed32"))); + Assert.assertEquals(-50L , message.getField(f("default_sfixed64"))); + Assert.assertEquals( 51.5F , message.getField(f("default_float" ))); + Assert.assertEquals( 52e3D , message.getField(f("default_double" ))); + Assert.assertEquals(true , message.getField(f("default_bool" ))); + Assert.assertEquals("hello", message.getField(f("default_string" ))); + Assert.assertEquals(toBytes("world"), message.getField(f("default_bytes"))); + + Assert.assertEquals( nestedBar, message.getField(f("default_nested_enum" ))); + Assert.assertEquals(foreignBar, message.getField(f("default_foreign_enum"))); + Assert.assertEquals( importBar, message.getField(f("default_import_enum" ))); + + Assert.assertEquals("abc", message.getField(f("default_string_piece"))); + Assert.assertEquals("123", message.getField(f("default_cord"))); + } + + // --------------------------------------------------------------- + + public void assertRepeatedFieldsModifiedViaReflection(Message message) { + // ModifyRepeatedFields only sets the second repeated element of each + // field. In addition to verifying this, we also verify that the first + // element and size were *not* modified. + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_int32" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_int64" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_uint32" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_uint64" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_sint32" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_sint64" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_fixed32" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_fixed64" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_sfixed32"))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_sfixed64"))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_float" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_double" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_bool" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_string" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_bytes" ))); + + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeatedgroup" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_nested_message" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_foreign_message"))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_import_message" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_nested_enum" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_foreign_enum" ))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_import_enum" ))); + + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_string_piece"))); + Assert.assertEquals(2, message.getRepeatedFieldCount(f("repeated_cord"))); + + Assert.assertEquals(201 , message.getRepeatedField(f("repeated_int32" ), 0)); + Assert.assertEquals(202L , message.getRepeatedField(f("repeated_int64" ), 0)); + Assert.assertEquals(203 , message.getRepeatedField(f("repeated_uint32" ), 0)); + Assert.assertEquals(204L , message.getRepeatedField(f("repeated_uint64" ), 0)); + Assert.assertEquals(205 , message.getRepeatedField(f("repeated_sint32" ), 0)); + Assert.assertEquals(206L , message.getRepeatedField(f("repeated_sint64" ), 0)); + Assert.assertEquals(207 , message.getRepeatedField(f("repeated_fixed32" ), 0)); + Assert.assertEquals(208L , message.getRepeatedField(f("repeated_fixed64" ), 0)); + Assert.assertEquals(209 , message.getRepeatedField(f("repeated_sfixed32"), 0)); + Assert.assertEquals(210L , message.getRepeatedField(f("repeated_sfixed64"), 0)); + Assert.assertEquals(211F , message.getRepeatedField(f("repeated_float" ), 0)); + Assert.assertEquals(212D , message.getRepeatedField(f("repeated_double" ), 0)); + Assert.assertEquals(true , message.getRepeatedField(f("repeated_bool" ), 0)); + Assert.assertEquals("215", message.getRepeatedField(f("repeated_string" ), 0)); + Assert.assertEquals(toBytes("216"), message.getRepeatedField(f("repeated_bytes"), 0)); + + Assert.assertEquals(217, + ((Message)message.getRepeatedField(f("repeatedgroup"), 0)) + .getField(repeatedGroupA)); + Assert.assertEquals(218, + ((Message)message.getRepeatedField(f("repeated_nested_message"), 0)) + .getField(nestedB)); + Assert.assertEquals(219, + ((Message)message.getRepeatedField(f("repeated_foreign_message"), 0)) + .getField(foreignC)); + Assert.assertEquals(220, + ((Message)message.getRepeatedField(f("repeated_import_message"), 0)) + .getField(importD)); + + Assert.assertEquals( nestedBar, message.getRepeatedField(f("repeated_nested_enum" ),0)); + Assert.assertEquals(foreignBar, message.getRepeatedField(f("repeated_foreign_enum"),0)); + Assert.assertEquals( importBar, message.getRepeatedField(f("repeated_import_enum" ),0)); + + Assert.assertEquals("224", message.getRepeatedField(f("repeated_string_piece"), 0)); + Assert.assertEquals("225", message.getRepeatedField(f("repeated_cord"), 0)); + + Assert.assertEquals(501 , message.getRepeatedField(f("repeated_int32" ), 1)); + Assert.assertEquals(502L , message.getRepeatedField(f("repeated_int64" ), 1)); + Assert.assertEquals(503 , message.getRepeatedField(f("repeated_uint32" ), 1)); + Assert.assertEquals(504L , message.getRepeatedField(f("repeated_uint64" ), 1)); + Assert.assertEquals(505 , message.getRepeatedField(f("repeated_sint32" ), 1)); + Assert.assertEquals(506L , message.getRepeatedField(f("repeated_sint64" ), 1)); + Assert.assertEquals(507 , message.getRepeatedField(f("repeated_fixed32" ), 1)); + Assert.assertEquals(508L , message.getRepeatedField(f("repeated_fixed64" ), 1)); + Assert.assertEquals(509 , message.getRepeatedField(f("repeated_sfixed32"), 1)); + Assert.assertEquals(510L , message.getRepeatedField(f("repeated_sfixed64"), 1)); + Assert.assertEquals(511F , message.getRepeatedField(f("repeated_float" ), 1)); + Assert.assertEquals(512D , message.getRepeatedField(f("repeated_double" ), 1)); + Assert.assertEquals(true , message.getRepeatedField(f("repeated_bool" ), 1)); + Assert.assertEquals("515", message.getRepeatedField(f("repeated_string" ), 1)); + Assert.assertEquals(toBytes("516"), message.getRepeatedField(f("repeated_bytes"), 1)); + + Assert.assertEquals(517, + ((Message)message.getRepeatedField(f("repeatedgroup"), 1)) + .getField(repeatedGroupA)); + Assert.assertEquals(518, + ((Message)message.getRepeatedField(f("repeated_nested_message"), 1)) + .getField(nestedB)); + Assert.assertEquals(519, + ((Message)message.getRepeatedField(f("repeated_foreign_message"), 1)) + .getField(foreignC)); + Assert.assertEquals(520, + ((Message)message.getRepeatedField(f("repeated_import_message"), 1)) + .getField(importD)); + + Assert.assertEquals( nestedFoo, message.getRepeatedField(f("repeated_nested_enum" ),1)); + Assert.assertEquals(foreignFoo, message.getRepeatedField(f("repeated_foreign_enum"),1)); + Assert.assertEquals( importFoo, message.getRepeatedField(f("repeated_import_enum" ),1)); + + Assert.assertEquals("524", message.getRepeatedField(f("repeated_string_piece"), 1)); + Assert.assertEquals("525", message.getRepeatedField(f("repeated_cord"), 1)); + } + } + + /** + * @param filePath The path relative to + * {@link com.google.testing.util.TestUtil#getDefaultSrcDir}. + */ + public static String readTextFromFile(String filePath) { + return readBytesFromFile(filePath).toStringUtf8(); + } + + private static File getTestDataDir() { + // Search each parent directory looking for "src/google/protobuf". + File ancestor = new File("."); + try { + ancestor = ancestor.getCanonicalFile(); + } catch (IOException e) { + throw new RuntimeException( + "Couldn't get canonical name of working directory.", e); + } + while (ancestor != null && ancestor.exists()) { + if (new File(ancestor, "src/google/protobuf").exists()) { + return new File(ancestor, "src/google/protobuf/testdata"); + } + ancestor = ancestor.getParentFile(); + } + + throw new RuntimeException( + "Could not find golden files. This test must be run from within the " + + "protobuf source package so that it can read test data files from the " + + "C++ source tree."); + } + + /** + * @param filePath The path relative to + * {@link com.google.testing.util.TestUtil#getDefaultSrcDir}. + */ + public static ByteString readBytesFromFile(String filename) { + File fullPath = new File(getTestDataDir(), filename); + try { + RandomAccessFile file = new RandomAccessFile(fullPath, "r"); + byte[] content = new byte[(int) file.length()]; + file.readFully(content); + return ByteString.copyFrom(content); + } catch (IOException e) { + // Throw a RuntimeException here so that we can call this function from + // static initializers. + throw new IllegalArgumentException( + "Couldn't read file: " + fullPath.getPath(), e); + } + } + + /** + * Get the bytes of the "golden message". This is a serialized TestAllTypes + * with all fields set as they would be by + * {@link setAllFields(TestAllTypes.Builder)}, but it is loaded from a file + * on disk rather than generated dynamically. The file is actually generated + * by C++ code, so testing against it verifies compatibility with C++. + */ + public static ByteString getGoldenMessage() { + if (goldenMessage == null) { + goldenMessage = readBytesFromFile("golden_message"); + } + return goldenMessage; + } + private static ByteString goldenMessage = null; +} diff --git a/java/src/test/java/com/google/protobuf/TextFormatTest.java b/java/src/test/java/com/google/protobuf/TextFormatTest.java new file mode 100644 index 00000000..8b7af107 --- /dev/null +++ b/java/src/test/java/com/google/protobuf/TextFormatTest.java @@ -0,0 +1,575 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.protobuf; + +import protobuf_unittest.UnittestProto.TestAllTypes; +import protobuf_unittest.UnittestProto.TestAllExtensions; +import protobuf_unittest.UnittestProto.TestEmptyMessage; +import protobuf_unittest.UnittestMset.TestMessageSet; +import protobuf_unittest.UnittestMset.TestMessageSetExtension1; +import protobuf_unittest.UnittestMset.TestMessageSetExtension2; + +import junit.framework.TestCase; + +import java.io.StringReader; + +/** + * Test case for {@link TextFormat}. + * + * TODO(wenboz): ExtensionTest and rest of text_format_unittest.cc. + * + * @author wenboz@google.com (Wenbo Zhu) + */ +public class TextFormatTest extends TestCase { + + // A basic string with different escapable characters for testing. + private final static String kEscapeTestString = + "\"A string with ' characters \n and \r newlines and \t tabs and \001 " + + "slashes \\"; + + // A representation of the above string with all the characters escaped. + private final static String kEscapeTestStringEscaped = + "\"\\\"A string with \\' characters \\n and \\r newlines " + + "and \\t tabs and \\001 slashes \\\\\""; + + private static String allFieldsSetText = TestUtil.readTextFromFile( + "text_format_unittest_data.txt"); + private static String allExtensionsSetText = TestUtil.readTextFromFile( + "text_format_unittest_extensions_data.txt"); + + private String exoticText = + "repeated_int32: -1\n" + + "repeated_int32: -2147483648\n" + + "repeated_int64: -1\n" + + "repeated_int64: -9223372036854775808\n" + + "repeated_uint32: 4294967295\n" + + "repeated_uint32: 2147483648\n" + + "repeated_uint64: 18446744073709551615\n" + + "repeated_uint64: 9223372036854775808\n" + + "repeated_double: 123.0\n" + + "repeated_double: 123.5\n" + + "repeated_double: 0.125\n" + + "repeated_double: 1.23E17\n" + + "repeated_double: 1.235E22\n" + + "repeated_double: 1.235E-18\n" + + "repeated_double: 123.456789\n" + + "repeated_double: Infinity\n" + + "repeated_double: -Infinity\n" + + "repeated_double: NaN\n" + + "repeated_string: \"\\000\\001\\a\\b\\f\\n\\r\\t\\v\\\\\\'\\\"" + + "\\341\\210\\264\"\n" + + "repeated_bytes: \"\\000\\001\\a\\b\\f\\n\\r\\t\\v\\\\\\'\\\"\\376\"\n"; + + private String messageSetText = + "[protobuf_unittest.TestMessageSetExtension1] {\n" + + " i: 123\n" + + "}\n" + + "[protobuf_unittest.TestMessageSetExtension2] {\n" + + " str: \"foo\"\n" + + "}\n"; + + /** Print TestAllTypes and compare with golden file. */ + public void testPrintMessage() throws Exception { + String javaText = TextFormat.printToString(TestUtil.getAllSet()); + + // Java likes to add a trailing ".0" to floats and doubles. C printf + // (with %g format) does not. Our golden files are used for both + // C++ and Java TextFormat classes, so we need to conform. + javaText = javaText.replace(".0\n", "\n"); + + assertEquals(allFieldsSetText, javaText); + } + + /** Print TestAllExtensions and compare with golden file. */ + public void testPrintExtensions() throws Exception { + String javaText = TextFormat.printToString(TestUtil.getAllExtensionsSet()); + + // Java likes to add a trailing ".0" to floats and doubles. C printf + // (with %g format) does not. Our golden files are used for both + // C++ and Java TextFormat classes, so we need to conform. + javaText = javaText.replace(".0\n", "\n"); + + assertEquals(allExtensionsSetText, javaText); + } + + public void testPrintUnknownFields() throws Exception { + // Test printing of unknown fields in a message. + + TestEmptyMessage message = + TestEmptyMessage.newBuilder() + .setUnknownFields( + UnknownFieldSet.newBuilder() + .addField(5, + UnknownFieldSet.Field.newBuilder() + .addVarint(1) + .addFixed32(2) + .addFixed64(3) + .addLengthDelimited(ByteString.copyFromUtf8("4")) + .addGroup( + UnknownFieldSet.newBuilder() + .addField(10, + UnknownFieldSet.Field.newBuilder() + .addVarint(5) + .build()) + .build()) + .build()) + .addField(8, + UnknownFieldSet.Field.newBuilder() + .addVarint(1) + .addVarint(2) + .addVarint(3) + .build()) + .addField(15, + UnknownFieldSet.Field.newBuilder() + .addVarint(0xABCDEF1234567890L) + .addFixed32(0xABCD1234) + .addFixed64(0xABCDEF1234567890L) + .build()) + .build()) + .build(); + + assertEquals( + "5: 1\n" + + "5: 0x00000002\n" + + "5: 0x0000000000000003\n" + + "5: \"4\"\n" + + "5 {\n" + + " 10: 5\n" + + "}\n" + + "8: 1\n" + + "8: 2\n" + + "8: 3\n" + + "15: 12379813812177893520\n" + + "15: 0xabcd1234\n" + + "15: 0xabcdef1234567890\n", + TextFormat.printToString(message)); + } + + /** + * Helper to construct a ByteString from a String containing only 8-bit + * characters. The characters are converted directly to bytes, *not* + * encoded using UTF-8. + */ + private ByteString bytes(String str) throws Exception { + return ByteString.copyFrom(str.getBytes("ISO-8859-1")); + } + + /** + * Helper to construct a ByteString from a bunch of bytes. The inputs are + * actually ints so that I can use hex notation and not get stupid errors + * about precision. + */ + private ByteString bytes(int... bytesAsInts) { + byte[] bytes = new byte[bytesAsInts.length]; + for (int i = 0; i < bytesAsInts.length; i++) { + bytes[i] = (byte) bytesAsInts[i]; + } + return ByteString.copyFrom(bytes); + } + + public void testPrintExotic() throws Exception { + Message message = TestAllTypes.newBuilder() + // Signed vs. unsigned numbers. + .addRepeatedInt32 (-1) + .addRepeatedUint32(-1) + .addRepeatedInt64 (-1) + .addRepeatedUint64(-1) + + .addRepeatedInt32 (1 << 31) + .addRepeatedUint32(1 << 31) + .addRepeatedInt64 (1l << 63) + .addRepeatedUint64(1l << 63) + + // Floats of various precisions and exponents. + .addRepeatedDouble(123) + .addRepeatedDouble(123.5) + .addRepeatedDouble(0.125) + .addRepeatedDouble(123e15) + .addRepeatedDouble(123.5e20) + .addRepeatedDouble(123.5e-20) + .addRepeatedDouble(123.456789) + .addRepeatedDouble(Double.POSITIVE_INFINITY) + .addRepeatedDouble(Double.NEGATIVE_INFINITY) + .addRepeatedDouble(Double.NaN) + + // Strings and bytes that needing escaping. + .addRepeatedString("\0\001\007\b\f\n\r\t\013\\\'\"\u1234") + .addRepeatedBytes(bytes("\0\001\007\b\f\n\r\t\013\\\'\"\u00fe")) + .build(); + + assertEquals(exoticText, message.toString()); + } + + public void testPrintMessageSet() throws Exception { + TestMessageSet messageSet = + TestMessageSet.newBuilder() + .setExtension( + TestMessageSetExtension1.messageSetExtension, + TestMessageSetExtension1.newBuilder().setI(123).build()) + .setExtension( + TestMessageSetExtension2.messageSetExtension, + TestMessageSetExtension2.newBuilder().setStr("foo").build()) + .build(); + + assertEquals(messageSetText, messageSet.toString()); + } + + // ================================================================= + + public void testParse() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TextFormat.merge(allFieldsSetText, builder); + TestUtil.assertAllFieldsSet(builder.build()); + } + + public void testParseReader() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TextFormat.merge(new StringReader(allFieldsSetText), builder); + TestUtil.assertAllFieldsSet(builder.build()); + } + + public void testParseExtensions() throws Exception { + TestAllExtensions.Builder builder = TestAllExtensions.newBuilder(); + TextFormat.merge(allExtensionsSetText, + TestUtil.getExtensionRegistry(), + builder); + TestUtil.assertAllExtensionsSet(builder.build()); + } + + public void testParseCompatibility() throws Exception { + String original = "repeated_float: inf\n" + + "repeated_float: -inf\n" + + "repeated_float: nan\n" + + "repeated_float: inff\n" + + "repeated_float: -inff\n" + + "repeated_float: nanf\n" + + "repeated_float: 1.0f\n" + + "repeated_float: infinityf\n" + + "repeated_float: -Infinityf\n" + + "repeated_double: infinity\n" + + "repeated_double: -infinity\n" + + "repeated_double: nan\n"; + String canonical = "repeated_float: Infinity\n" + + "repeated_float: -Infinity\n" + + "repeated_float: NaN\n" + + "repeated_float: Infinity\n" + + "repeated_float: -Infinity\n" + + "repeated_float: NaN\n" + + "repeated_float: 1.0\n" + + "repeated_float: Infinity\n" + + "repeated_float: -Infinity\n" + + "repeated_double: Infinity\n" + + "repeated_double: -Infinity\n" + + "repeated_double: NaN\n"; + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TextFormat.merge(original, builder); + assertEquals(canonical, builder.build().toString()); + } + + public void testParseExotic() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TextFormat.merge(exoticText, builder); + + // Too lazy to check things individually. Don't try to debug this + // if testPrintExotic() is failing. + assertEquals(exoticText, builder.build().toString()); + } + + public void testParseMessageSet() throws Exception { + ExtensionRegistry extensionRegistry = ExtensionRegistry.newInstance(); + extensionRegistry.add(TestMessageSetExtension1.messageSetExtension); + extensionRegistry.add(TestMessageSetExtension2.messageSetExtension); + + TestMessageSet.Builder builder = TestMessageSet.newBuilder(); + TextFormat.merge(messageSetText, extensionRegistry, builder); + TestMessageSet messageSet = builder.build(); + + assertTrue(messageSet.hasExtension( + TestMessageSetExtension1.messageSetExtension)); + assertEquals(123, messageSet.getExtension( + TestMessageSetExtension1.messageSetExtension).getI()); + assertTrue(messageSet.hasExtension( + TestMessageSetExtension2.messageSetExtension)); + assertEquals("foo", messageSet.getExtension( + TestMessageSetExtension2.messageSetExtension).getStr()); + } + + public void testParseNumericEnum() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TextFormat.merge("optional_nested_enum: 2", builder); + assertEquals(TestAllTypes.NestedEnum.BAR, builder.getOptionalNestedEnum()); + } + + public void testParseAngleBrackets() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TextFormat.merge("OptionalGroup: < a: 1 >", builder); + assertTrue(builder.hasOptionalGroup()); + assertEquals(1, builder.getOptionalGroup().getA()); + } + + public void testParseComment() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TextFormat.merge( + "# this is a comment\n" + + "optional_int32: 1 # another comment\n" + + "optional_int64: 2\n" + + "# EOF comment", builder); + assertEquals(1, builder.getOptionalInt32()); + assertEquals(2, builder.getOptionalInt64()); + } + + private void assertParseError(String error, String text) { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + try { + TextFormat.merge(text, TestUtil.getExtensionRegistry(), builder); + fail("Expected parse exception."); + } catch (TextFormat.ParseException e) { + assertEquals(error, e.getMessage()); + } + } + + public void testParseErrors() throws Exception { + assertParseError( + "1:16: Expected \":\".", + "optional_int32 123"); + assertParseError( + "1:23: Expected identifier.", + "optional_nested_enum: ?"); + assertParseError( + "1:18: Couldn't parse integer: Number must be positive: -1", + "optional_uint32: -1"); + assertParseError( + "1:17: Couldn't parse integer: Number out of range for 32-bit signed " + + "integer: 82301481290849012385230157", + "optional_int32: 82301481290849012385230157"); + assertParseError( + "1:16: Expected \"true\" or \"false\".", + "optional_bool: maybe"); + assertParseError( + "1:18: Expected string.", + "optional_string: 123"); + assertParseError( + "1:18: String missing ending quote.", + "optional_string: \"ueoauaoe"); + assertParseError( + "1:18: String missing ending quote.", + "optional_string: \"ueoauaoe\n" + + "optional_int32: 123"); + assertParseError( + "1:18: Invalid escape sequence: '\\z'", + "optional_string: \"\\z\""); + assertParseError( + "1:18: String missing ending quote.", + "optional_string: \"ueoauaoe\n" + + "optional_int32: 123"); + assertParseError( + "1:2: Extension \"nosuchext\" not found in the ExtensionRegistry.", + "[nosuchext]: 123"); + assertParseError( + "1:20: Extension \"protobuf_unittest.optional_int32_extension\" does " + + "not extend message type \"protobuf_unittest.TestAllTypes\".", + "[protobuf_unittest.optional_int32_extension]: 123"); + assertParseError( + "1:1: Message type \"protobuf_unittest.TestAllTypes\" has no field " + + "named \"nosuchfield\".", + "nosuchfield: 123"); + assertParseError( + "1:21: Expected \">\".", + "OptionalGroup < a: 1"); + assertParseError( + "1:23: Enum type \"protobuf_unittest.TestAllTypes.NestedEnum\" has no " + + "value named \"NO_SUCH_VALUE\".", + "optional_nested_enum: NO_SUCH_VALUE"); + assertParseError( + "1:23: Enum type \"protobuf_unittest.TestAllTypes.NestedEnum\" has no " + + "value with number 123.", + "optional_nested_enum: 123"); + + // Delimiters must match. + assertParseError( + "1:22: Expected identifier.", + "OptionalGroup < a: 1 }"); + assertParseError( + "1:22: Expected identifier.", + "OptionalGroup { a: 1 >"); + } + + // ================================================================= + + public void testEscape() throws Exception { + // Escape sequences. + assertEquals("\\000\\001\\a\\b\\f\\n\\r\\t\\v\\\\\\'\\\"", + TextFormat.escapeBytes(bytes("\0\001\007\b\f\n\r\t\013\\\'\""))); + assertEquals("\\000\\001\\a\\b\\f\\n\\r\\t\\v\\\\\\'\\\"", + TextFormat.escapeText("\0\001\007\b\f\n\r\t\013\\\'\"")); + assertEquals(bytes("\0\001\007\b\f\n\r\t\013\\\'\""), + TextFormat.unescapeBytes("\\000\\001\\a\\b\\f\\n\\r\\t\\v\\\\\\'\\\"")); + assertEquals("\0\001\007\b\f\n\r\t\013\\\'\"", + TextFormat.unescapeText("\\000\\001\\a\\b\\f\\n\\r\\t\\v\\\\\\'\\\"")); + + // Unicode handling. + assertEquals("\\341\\210\\264", TextFormat.escapeText("\u1234")); + assertEquals("\\341\\210\\264", + TextFormat.escapeBytes(bytes(0xe1, 0x88, 0xb4))); + assertEquals("\u1234", TextFormat.unescapeText("\\341\\210\\264")); + assertEquals(bytes(0xe1, 0x88, 0xb4), + TextFormat.unescapeBytes("\\341\\210\\264")); + assertEquals("\u1234", TextFormat.unescapeText("\\xe1\\x88\\xb4")); + assertEquals(bytes(0xe1, 0x88, 0xb4), + TextFormat.unescapeBytes("\\xe1\\x88\\xb4")); + + // Errors. + try { + TextFormat.unescapeText("\\x"); + fail("Should have thrown an exception."); + } catch (TextFormat.InvalidEscapeSequence e) { + // success + } + + try { + TextFormat.unescapeText("\\z"); + fail("Should have thrown an exception."); + } catch (TextFormat.InvalidEscapeSequence e) { + // success + } + + try { + TextFormat.unescapeText("\\"); + fail("Should have thrown an exception."); + } catch (TextFormat.InvalidEscapeSequence e) { + // success + } + } + + public void testParseInteger() throws Exception { + assertEquals( 0, TextFormat.parseInt32( "0")); + assertEquals( 1, TextFormat.parseInt32( "1")); + assertEquals( -1, TextFormat.parseInt32( "-1")); + assertEquals( 12345, TextFormat.parseInt32( "12345")); + assertEquals( -12345, TextFormat.parseInt32( "-12345")); + assertEquals( 2147483647, TextFormat.parseInt32( "2147483647")); + assertEquals(-2147483648, TextFormat.parseInt32("-2147483648")); + + assertEquals( 0, TextFormat.parseUInt32( "0")); + assertEquals( 1, TextFormat.parseUInt32( "1")); + assertEquals( 12345, TextFormat.parseUInt32( "12345")); + assertEquals( 2147483647, TextFormat.parseUInt32("2147483647")); + assertEquals((int) 2147483648L, TextFormat.parseUInt32("2147483648")); + assertEquals((int) 4294967295L, TextFormat.parseUInt32("4294967295")); + + assertEquals( 0L, TextFormat.parseInt64( "0")); + assertEquals( 1L, TextFormat.parseInt64( "1")); + assertEquals( -1L, TextFormat.parseInt64( "-1")); + assertEquals( 12345L, TextFormat.parseInt64( "12345")); + assertEquals( -12345L, TextFormat.parseInt64( "-12345")); + assertEquals( 2147483647L, TextFormat.parseInt64( "2147483647")); + assertEquals(-2147483648L, TextFormat.parseInt64("-2147483648")); + assertEquals( 4294967295L, TextFormat.parseInt64( "4294967295")); + assertEquals( 4294967296L, TextFormat.parseInt64( "4294967296")); + assertEquals(9223372036854775807L, + TextFormat.parseInt64("9223372036854775807")); + assertEquals(-9223372036854775808L, + TextFormat.parseInt64("-9223372036854775808")); + + assertEquals( 0L, TextFormat.parseUInt64( "0")); + assertEquals( 1L, TextFormat.parseUInt64( "1")); + assertEquals( 12345L, TextFormat.parseUInt64( "12345")); + assertEquals( 2147483647L, TextFormat.parseUInt64( "2147483647")); + assertEquals( 4294967295L, TextFormat.parseUInt64( "4294967295")); + assertEquals( 4294967296L, TextFormat.parseUInt64( "4294967296")); + assertEquals(9223372036854775807L, + TextFormat.parseUInt64("9223372036854775807")); + assertEquals(-9223372036854775808L, + TextFormat.parseUInt64("9223372036854775808")); + assertEquals(-1L, TextFormat.parseUInt64("18446744073709551615")); + + // Hex + assertEquals(0x1234abcd, TextFormat.parseInt32("0x1234abcd")); + assertEquals(-0x1234abcd, TextFormat.parseInt32("-0x1234abcd")); + assertEquals(-1, TextFormat.parseUInt64("0xffffffffffffffff")); + assertEquals(0x7fffffffffffffffL, + TextFormat.parseInt64("0x7fffffffffffffff")); + + // Octal + assertEquals(01234567, TextFormat.parseInt32("01234567")); + + // Out-of-range + try { + TextFormat.parseInt32("2147483648"); + fail("Should have thrown an exception."); + } catch (NumberFormatException e) { + // success + } + + try { + TextFormat.parseInt32("-2147483649"); + fail("Should have thrown an exception."); + } catch (NumberFormatException e) { + // success + } + + try { + TextFormat.parseUInt32("4294967296"); + fail("Should have thrown an exception."); + } catch (NumberFormatException e) { + // success + } + + try { + TextFormat.parseUInt32("-1"); + fail("Should have thrown an exception."); + } catch (NumberFormatException e) { + // success + } + + try { + TextFormat.parseInt64("9223372036854775808"); + fail("Should have thrown an exception."); + } catch (NumberFormatException e) { + // success + } + + try { + TextFormat.parseInt64("-9223372036854775809"); + fail("Should have thrown an exception."); + } catch (NumberFormatException e) { + // success + } + + try { + TextFormat.parseUInt64("18446744073709551616"); + fail("Should have thrown an exception."); + } catch (NumberFormatException e) { + // success + } + + try { + TextFormat.parseUInt64("-1"); + fail("Should have thrown an exception."); + } catch (NumberFormatException e) { + // success + } + + // Not a number. + try { + TextFormat.parseInt32("abcd"); + fail("Should have thrown an exception."); + } catch (NumberFormatException e) { + // success + } + } +} diff --git a/java/src/test/java/com/google/protobuf/UnknownFieldSetTest.java b/java/src/test/java/com/google/protobuf/UnknownFieldSetTest.java new file mode 100644 index 00000000..8919414e --- /dev/null +++ b/java/src/test/java/com/google/protobuf/UnknownFieldSetTest.java @@ -0,0 +1,330 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.protobuf; + +import protobuf_unittest.UnittestProto; +import protobuf_unittest.UnittestProto.TestAllTypes; +import protobuf_unittest.UnittestProto.TestAllExtensions; +import protobuf_unittest.UnittestProto.TestEmptyMessage; +import protobuf_unittest.UnittestProto. + TestEmptyMessageWithExtensions; + +import junit.framework.TestCase; +import java.util.Arrays; +import java.util.Map; + +/** + * Tests related to unknown field handling. + * + * @author kenton@google.com (Kenton Varda) + */ +public class UnknownFieldSetTest extends TestCase { + public void setUp() throws Exception { + descriptor = TestAllTypes.getDescriptor(); + allFields = TestUtil.getAllSet(); + allFieldsData = allFields.toByteString(); + emptyMessage = TestEmptyMessage.parseFrom(allFieldsData); + unknownFields = emptyMessage.getUnknownFields(); + } + + UnknownFieldSet.Field getField(String name) { + Descriptors.FieldDescriptor field = descriptor.findFieldByName(name); + assertNotNull(field); + return unknownFields.getField(field.getNumber()); + } + + // Constructs a protocol buffer which contains fields with all the same + // numbers as allFieldsData except that each field is some other wire + // type. + ByteString getBizarroData() throws Exception { + UnknownFieldSet.Builder bizarroFields = UnknownFieldSet.newBuilder(); + + UnknownFieldSet.Field varintField = + UnknownFieldSet.Field.newBuilder().addVarint(1).build(); + UnknownFieldSet.Field fixed32Field = + UnknownFieldSet.Field.newBuilder().addFixed32(1).build(); + + for (Map.Entry<Integer, UnknownFieldSet.Field> entry : + unknownFields.asMap().entrySet()) { + if (entry.getValue().getVarintList().isEmpty()) { + // Original field is not a varint, so use a varint. + bizarroFields.addField(entry.getKey(), varintField); + } else { + // Original field *is* a varint, so use something else. + bizarroFields.addField(entry.getKey(), fixed32Field); + } + } + + return bizarroFields.build().toByteString(); + } + + Descriptors.Descriptor descriptor; + TestAllTypes allFields; + ByteString allFieldsData; + + // An empty message that has been parsed from allFieldsData. So, it has + // unknown fields of every type. + TestEmptyMessage emptyMessage; + UnknownFieldSet unknownFields; + + // ================================================================= + + public void testVarint() throws Exception { + UnknownFieldSet.Field field = getField("optional_int32"); + assertEquals(1, field.getVarintList().size()); + assertEquals(allFields.getOptionalInt32(), + (long) field.getVarintList().get(0)); + } + + public void testFixed32() throws Exception { + UnknownFieldSet.Field field = getField("optional_fixed32"); + assertEquals(1, field.getFixed32List().size()); + assertEquals(allFields.getOptionalFixed32(), + (int) field.getFixed32List().get(0)); + } + + public void testFixed64() throws Exception { + UnknownFieldSet.Field field = getField("optional_fixed64"); + assertEquals(1, field.getFixed64List().size()); + assertEquals(allFields.getOptionalFixed64(), + (long) field.getFixed64List().get(0)); + } + + public void testLengthDelimited() throws Exception { + UnknownFieldSet.Field field = getField("optional_bytes"); + assertEquals(1, field.getLengthDelimitedList().size()); + assertEquals(allFields.getOptionalBytes(), + field.getLengthDelimitedList().get(0)); + } + + public void testGroup() throws Exception { + Descriptors.FieldDescriptor nestedFieldDescriptor = + TestAllTypes.OptionalGroup.getDescriptor().findFieldByName("a"); + assertNotNull(nestedFieldDescriptor); + + UnknownFieldSet.Field field = getField("optionalgroup"); + assertEquals(1, field.getGroupList().size()); + + UnknownFieldSet group = field.getGroupList().get(0); + assertEquals(1, group.asMap().size()); + assertTrue(group.hasField(nestedFieldDescriptor.getNumber())); + + UnknownFieldSet.Field nestedField = + group.getField(nestedFieldDescriptor.getNumber()); + assertEquals(1, nestedField.getVarintList().size()); + assertEquals(allFields.getOptionalGroup().getA(), + (long) nestedField.getVarintList().get(0)); + } + + public void testSerialize() throws Exception { + // Check that serializing the UnknownFieldSet produces the original data + // again. + ByteString data = emptyMessage.toByteString(); + assertEquals(allFieldsData, data); + } + + public void testCopyFrom() throws Exception { + TestEmptyMessage message = + TestEmptyMessage.newBuilder().mergeFrom(emptyMessage).build(); + + assertEquals(emptyMessage.toString(), message.toString()); + } + + public void testMergeFrom() throws Exception { + TestEmptyMessage source = + TestEmptyMessage.newBuilder() + .setUnknownFields( + UnknownFieldSet.newBuilder() + .addField(2, + UnknownFieldSet.Field.newBuilder() + .addVarint(2).build()) + .addField(3, + UnknownFieldSet.Field.newBuilder() + .addVarint(4).build()) + .build()) + .build(); + TestEmptyMessage destination = + TestEmptyMessage.newBuilder() + .setUnknownFields( + UnknownFieldSet.newBuilder() + .addField(1, + UnknownFieldSet.Field.newBuilder() + .addVarint(1).build()) + .addField(3, + UnknownFieldSet.Field.newBuilder() + .addVarint(3).build()) + .build()) + .mergeFrom(source) + .build(); + + assertEquals( + "1: 1\n" + + "2: 2\n" + + "3: 3\n" + + "3: 4\n", + destination.toString()); + } + + public void testClear() throws Exception { + UnknownFieldSet fields = + UnknownFieldSet.newBuilder().mergeFrom(unknownFields).clear().build(); + assertTrue(fields.asMap().isEmpty()); + } + + public void testClearMessage() throws Exception { + TestEmptyMessage message = + TestEmptyMessage.newBuilder().mergeFrom(emptyMessage).clear().build(); + assertEquals(0, message.getSerializedSize()); + } + + public void testParseKnownAndUnknown() throws Exception { + // Test mixing known and unknown fields when parsing. + + UnknownFieldSet fields = + UnknownFieldSet.newBuilder(unknownFields) + .addField(123456, + UnknownFieldSet.Field.newBuilder().addVarint(654321).build()) + .build(); + + ByteString data = fields.toByteString(); + TestAllTypes destination = TestAllTypes.parseFrom(data); + + TestUtil.assertAllFieldsSet(destination); + assertEquals(1, destination.getUnknownFields().asMap().size()); + + UnknownFieldSet.Field field = + destination.getUnknownFields().getField(123456); + assertEquals(1, field.getVarintList().size()); + assertEquals(654321, (long) field.getVarintList().get(0)); + } + + public void testWrongTypeTreatedAsUnknown() throws Exception { + // Test that fields of the wrong wire type are treated like unknown fields + // when parsing. + + ByteString bizarroData = getBizarroData(); + TestAllTypes allTypesMessage = TestAllTypes.parseFrom(bizarroData); + TestEmptyMessage emptyMessage = TestEmptyMessage.parseFrom(bizarroData); + + // All fields should have been interpreted as unknown, so the debug strings + // should be the same. + assertEquals(emptyMessage.toString(), allTypesMessage.toString()); + } + + public void testUnknownExtensions() throws Exception { + // Make sure fields are properly parsed to the UnknownFieldSet even when + // they are declared as extension numbers. + + TestEmptyMessageWithExtensions message = + TestEmptyMessageWithExtensions.parseFrom(allFieldsData); + + assertEquals(unknownFields.asMap().size(), + message.getUnknownFields().asMap().size()); + assertEquals(allFieldsData, message.toByteString()); + } + + public void testWrongExtensionTypeTreatedAsUnknown() throws Exception { + // Test that fields of the wrong wire type are treated like unknown fields + // when parsing extensions. + + ByteString bizarroData = getBizarroData(); + TestAllExtensions allExtensionsMessage = + TestAllExtensions.parseFrom(bizarroData); + TestEmptyMessage emptyMessage = TestEmptyMessage.parseFrom(bizarroData); + + // All fields should have been interpreted as unknown, so the debug strings + // should be the same. + assertEquals(emptyMessage.toString(), + allExtensionsMessage.toString()); + } + + public void testParseUnknownEnumValue() throws Exception { + Descriptors.FieldDescriptor singularField = + TestAllTypes.getDescriptor().findFieldByName("optional_nested_enum"); + Descriptors.FieldDescriptor repeatedField = + TestAllTypes.getDescriptor().findFieldByName("repeated_nested_enum"); + assertNotNull(singularField); + assertNotNull(repeatedField); + + ByteString data = + UnknownFieldSet.newBuilder() + .addField(singularField.getNumber(), + UnknownFieldSet.Field.newBuilder() + .addVarint(TestAllTypes.NestedEnum.BAR.getNumber()) + .addVarint(5) // not valid + .build()) + .addField(repeatedField.getNumber(), + UnknownFieldSet.Field.newBuilder() + .addVarint(TestAllTypes.NestedEnum.FOO.getNumber()) + .addVarint(4) // not valid + .addVarint(TestAllTypes.NestedEnum.BAZ.getNumber()) + .addVarint(6) // not valid + .build()) + .build() + .toByteString(); + + { + TestAllTypes message = TestAllTypes.parseFrom(data); + assertEquals(TestAllTypes.NestedEnum.BAR, + message.getOptionalNestedEnum()); + assertEquals( + Arrays.asList(TestAllTypes.NestedEnum.FOO, TestAllTypes.NestedEnum.BAZ), + message.getRepeatedNestedEnumList()); + assertEquals(Arrays.asList(5L), + message.getUnknownFields() + .getField(singularField.getNumber()) + .getVarintList()); + assertEquals(Arrays.asList(4L, 6L), + message.getUnknownFields() + .getField(repeatedField.getNumber()) + .getVarintList()); + } + + { + TestAllExtensions message = + TestAllExtensions.parseFrom(data, TestUtil.getExtensionRegistry()); + assertEquals(TestAllTypes.NestedEnum.BAR, + message.getExtension(UnittestProto.optionalNestedEnumExtension)); + assertEquals( + Arrays.asList(TestAllTypes.NestedEnum.FOO, TestAllTypes.NestedEnum.BAZ), + message.getExtension(UnittestProto.repeatedNestedEnumExtension)); + assertEquals(Arrays.asList(5L), + message.getUnknownFields() + .getField(singularField.getNumber()) + .getVarintList()); + assertEquals(Arrays.asList(4L, 6L), + message.getUnknownFields() + .getField(repeatedField.getNumber()) + .getVarintList()); + } + } + + public void testLargeVarint() throws Exception { + ByteString data = + UnknownFieldSet.newBuilder() + .addField(1, + UnknownFieldSet.Field.newBuilder() + .addVarint(0x7FFFFFFFFFFFFFFFL) + .build()) + .build() + .toByteString(); + UnknownFieldSet parsed = UnknownFieldSet.parseFrom(data); + UnknownFieldSet.Field field = parsed.getField(1); + assertEquals(1, field.getVarintList().size()); + assertEquals(0x7FFFFFFFFFFFFFFFL, (long)field.getVarintList().get(0)); + } +} diff --git a/java/src/test/java/com/google/protobuf/WireFormatTest.java b/java/src/test/java/com/google/protobuf/WireFormatTest.java new file mode 100644 index 00000000..84cc89f8 --- /dev/null +++ b/java/src/test/java/com/google/protobuf/WireFormatTest.java @@ -0,0 +1,226 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.protobuf; + +import junit.framework.TestCase; +import protobuf_unittest.UnittestProto; +import protobuf_unittest.UnittestProto.TestAllTypes; +import protobuf_unittest.UnittestProto.TestAllExtensions; +import protobuf_unittest.UnittestProto.TestFieldOrderings; +import protobuf_unittest.UnittestMset.TestMessageSet; +import protobuf_unittest.UnittestMset.RawMessageSet; +import protobuf_unittest.UnittestMset.TestMessageSetExtension1; +import protobuf_unittest.UnittestMset.TestMessageSetExtension2; + +/** + * Tests related to parsing and serialization. + * + * @author kenton@google.com (Kenton Varda) + */ +public class WireFormatTest extends TestCase { + public void testSerialization() throws Exception { + TestAllTypes message = TestUtil.getAllSet(); + + ByteString rawBytes = message.toByteString(); + assertEquals(rawBytes.size(), message.getSerializedSize()); + + TestAllTypes message2 = TestAllTypes.parseFrom(rawBytes); + + TestUtil.assertAllFieldsSet(message2); + } + + public void testSerializeExtensions() throws Exception { + // TestAllTypes and TestAllExtensions should have compatible wire formats, + // so if we serealize a TestAllExtensions then parse it as TestAllTypes + // it should work. + + TestAllExtensions message = TestUtil.getAllExtensionsSet(); + ByteString rawBytes = message.toByteString(); + assertEquals(rawBytes.size(), message.getSerializedSize()); + + TestAllTypes message2 = TestAllTypes.parseFrom(rawBytes); + + TestUtil.assertAllFieldsSet(message2); + } + + public void testParseExtensions() throws Exception { + // TestAllTypes and TestAllExtensions should have compatible wire formats, + // so if we serealize a TestAllTypes then parse it as TestAllExtensions + // it should work. + + TestAllTypes message = TestUtil.getAllSet(); + ByteString rawBytes = message.toByteString(); + + ExtensionRegistry registry = ExtensionRegistry.newInstance(); + TestUtil.registerAllExtensions(registry); + registry = registry.getUnmodifiable(); + + TestAllExtensions message2 = + TestAllExtensions.parseFrom(rawBytes, registry); + + TestUtil.assertAllExtensionsSet(message2); + } + + public void testExtensionsSerializedSize() throws Exception { + assertEquals(TestUtil.getAllSet().getSerializedSize(), + TestUtil.getAllExtensionsSet().getSerializedSize()); + } + + private void assertFieldsInOrder(ByteString data) throws Exception { + CodedInputStream input = data.newCodedInput(); + int previousTag = 0; + + while (true) { + int tag = input.readTag(); + if (tag == 0) { + break; + } + + assertTrue(tag > previousTag); + input.skipField(tag); + } + } + + public void testInterleavedFieldsAndExtensions() throws Exception { + // Tests that fields are written in order even when extension ranges + // are interleaved with field numbers. + ByteString data = + TestFieldOrderings.newBuilder() + .setMyInt(1) + .setMyString("foo") + .setMyFloat(1.0F) + .setExtension(UnittestProto.myExtensionInt, 23) + .setExtension(UnittestProto.myExtensionString, "bar") + .build().toByteString(); + assertFieldsInOrder(data); + + Descriptors.Descriptor descriptor = TestFieldOrderings.getDescriptor(); + ByteString dynamic_data = + DynamicMessage.newBuilder(TestFieldOrderings.getDescriptor()) + .setField(descriptor.findFieldByName("my_int"), 1L) + .setField(descriptor.findFieldByName("my_string"), "foo") + .setField(descriptor.findFieldByName("my_float"), 1.0F) + .setField(UnittestProto.myExtensionInt.getDescriptor(), 23) + .setField(UnittestProto.myExtensionString.getDescriptor(), "bar") + .build().toByteString(); + assertFieldsInOrder(dynamic_data); + } + + private static final int UNKNOWN_TYPE_ID = 1550055; + private static final int TYPE_ID_1 = + TestMessageSetExtension1.getDescriptor().getExtensions().get(0).getNumber(); + private static final int TYPE_ID_2 = + TestMessageSetExtension2.getDescriptor().getExtensions().get(0).getNumber(); + + public void testSerializeMessageSet() throws Exception { + // Set up a TestMessageSet with two known messages and an unknown one. + TestMessageSet messageSet = + TestMessageSet.newBuilder() + .setExtension( + TestMessageSetExtension1.messageSetExtension, + TestMessageSetExtension1.newBuilder().setI(123).build()) + .setExtension( + TestMessageSetExtension2.messageSetExtension, + TestMessageSetExtension2.newBuilder().setStr("foo").build()) + .setUnknownFields( + UnknownFieldSet.newBuilder() + .addField(UNKNOWN_TYPE_ID, + UnknownFieldSet.Field.newBuilder() + .addLengthDelimited(ByteString.copyFromUtf8("bar")) + .build()) + .build()) + .build(); + + ByteString data = messageSet.toByteString(); + + // Parse back using RawMessageSet and check the contents. + RawMessageSet raw = RawMessageSet.parseFrom(data); + + assertTrue(raw.getUnknownFields().asMap().isEmpty()); + + assertEquals(3, raw.getItemCount()); + assertEquals(TYPE_ID_1, raw.getItem(0).getTypeId()); + assertEquals(TYPE_ID_2, raw.getItem(1).getTypeId()); + assertEquals(UNKNOWN_TYPE_ID, raw.getItem(2).getTypeId()); + + TestMessageSetExtension1 message1 = + TestMessageSetExtension1.parseFrom( + raw.getItem(0).getMessage().toByteArray()); + assertEquals(123, message1.getI()); + + TestMessageSetExtension2 message2 = + TestMessageSetExtension2.parseFrom( + raw.getItem(1).getMessage().toByteArray()); + assertEquals("foo", message2.getStr()); + + assertEquals("bar", raw.getItem(2).getMessage().toStringUtf8()); + } + + public void testParseMessageSet() throws Exception { + ExtensionRegistry extensionRegistry = ExtensionRegistry.newInstance(); + extensionRegistry.add(TestMessageSetExtension1.messageSetExtension); + extensionRegistry.add(TestMessageSetExtension2.messageSetExtension); + + // Set up a RawMessageSet with two known messages and an unknown one. + RawMessageSet raw = + RawMessageSet.newBuilder() + .addItem( + RawMessageSet.Item.newBuilder() + .setTypeId(TYPE_ID_1) + .setMessage( + TestMessageSetExtension1.newBuilder() + .setI(123) + .build().toByteString()) + .build()) + .addItem( + RawMessageSet.Item.newBuilder() + .setTypeId(TYPE_ID_2) + .setMessage( + TestMessageSetExtension2.newBuilder() + .setStr("foo") + .build().toByteString()) + .build()) + .addItem( + RawMessageSet.Item.newBuilder() + .setTypeId(UNKNOWN_TYPE_ID) + .setMessage(ByteString.copyFromUtf8("bar")) + .build()) + .build(); + + ByteString data = raw.toByteString(); + + // Parse as a TestMessageSet and check the contents. + TestMessageSet messageSet = + TestMessageSet.parseFrom(data, extensionRegistry); + + assertEquals(123, messageSet.getExtension( + TestMessageSetExtension1.messageSetExtension).getI()); + assertEquals("foo", messageSet.getExtension( + TestMessageSetExtension2.messageSetExtension).getStr()); + + // Check for unknown field with type LENGTH_DELIMITED, + // number UNKNOWN_TYPE_ID, and contents "bar". + UnknownFieldSet unknownFields = messageSet.getUnknownFields(); + assertEquals(1, unknownFields.asMap().size()); + assertTrue(unknownFields.hasField(UNKNOWN_TYPE_ID)); + + UnknownFieldSet.Field field = unknownFields.getField(UNKNOWN_TYPE_ID); + assertEquals(1, field.getLengthDelimitedList().size()); + assertEquals("bar", field.getLengthDelimitedList().get(0).toStringUtf8()); + } +} + diff --git a/java/src/test/java/com/google/protobuf/multiple_files_test.proto b/java/src/test/java/com/google/protobuf/multiple_files_test.proto new file mode 100644 index 00000000..1dbadfe0 --- /dev/null +++ b/java/src/test/java/com/google/protobuf/multiple_files_test.proto @@ -0,0 +1,53 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Author: kenton@google.com (Kenton Varda) +// +// A proto file which tests the java_multiple_files option. + + +import "google/protobuf/unittest.proto"; + +package protobuf_unittest; + +option java_multiple_files = true; +option java_outer_classname = "MultipleFilesTestProto"; + +message MessageWithNoOuter { + message NestedMessage { + optional int32 i = 1; + } + enum NestedEnum { + BAZ = 3; + } + optional NestedMessage nested = 1; + repeated TestAllTypes foreign = 2; + optional NestedEnum nested_enum = 3; + optional EnumWithNoOuter foreign_enum = 4; +} + +enum EnumWithNoOuter { + FOO = 1; + BAR = 2; +} + +service ServiceWithNoOuter { + rpc Foo(MessageWithNoOuter) returns(TestAllTypes); +} + +extend TestAllExtensions { + optional int32 extension_with_outer = 1234567; +} |