aboutsummaryrefslogtreecommitdiff
path: root/java
diff options
context:
space:
mode:
authorliujisi@google.com <liujisi@google.com@630680e5-0e50-0410-840e-4b1c322b438d>2010-11-02 13:14:58 +0000
committerliujisi@google.com <liujisi@google.com@630680e5-0e50-0410-840e-4b1c322b438d>2010-11-02 13:14:58 +0000
commit33165fe0d5c265c92f2a67fc2b437b567c24e294 (patch)
tree52def0850ddd2e976da238d1a437fbda79c96e44 /java
parent80aa23df6c63750e8cdfdcf3996fbc37d63cac61 (diff)
downloadprotobuf-33165fe0d5c265c92f2a67fc2b437b567c24e294.tar.gz
protobuf-33165fe0d5c265c92f2a67fc2b437b567c24e294.tar.bz2
protobuf-33165fe0d5c265c92f2a67fc2b437b567c24e294.zip
Submit recent changes from internal branch. See CHANGES.txt for more details.
Diffstat (limited to 'java')
-rw-r--r--java/pom.xml6
-rw-r--r--java/src/main/java/com/google/protobuf/AbstractMessage.java60
-rw-r--r--java/src/main/java/com/google/protobuf/ByteString.java12
-rw-r--r--java/src/main/java/com/google/protobuf/CodedInputStream.java50
-rw-r--r--java/src/main/java/com/google/protobuf/CodedOutputStream.java58
-rw-r--r--java/src/main/java/com/google/protobuf/Descriptors.java8
-rw-r--r--java/src/main/java/com/google/protobuf/FieldSet.java246
-rw-r--r--java/src/main/java/com/google/protobuf/GeneratedMessage.java786
-rw-r--r--java/src/main/java/com/google/protobuf/GeneratedMessageLite.java347
-rw-r--r--java/src/main/java/com/google/protobuf/Internal.java85
-rw-r--r--java/src/main/java/com/google/protobuf/LazyStringArrayList.java155
-rw-r--r--java/src/main/java/com/google/protobuf/LazyStringList.java72
-rw-r--r--java/src/main/java/com/google/protobuf/Message.java104
-rw-r--r--java/src/main/java/com/google/protobuf/MessageLite.java46
-rw-r--r--java/src/main/java/com/google/protobuf/MessageLiteOrBuilder.java56
-rw-r--r--java/src/main/java/com/google/protobuf/MessageOrBuilder.java110
-rw-r--r--java/src/main/java/com/google/protobuf/RepeatedFieldBuilder.java696
-rw-r--r--java/src/main/java/com/google/protobuf/ServiceException.java10
-rw-r--r--java/src/main/java/com/google/protobuf/SingleFieldBuilder.java241
-rw-r--r--java/src/main/java/com/google/protobuf/SmallSortedMap.java618
-rw-r--r--java/src/main/java/com/google/protobuf/TextFormat.java488
-rw-r--r--java/src/main/java/com/google/protobuf/UnmodifiableLazyStringList.java146
-rw-r--r--java/src/main/java/com/google/protobuf/WireFormat.java12
-rw-r--r--java/src/test/java/com/google/protobuf/AbstractMessageTest.java13
-rw-r--r--java/src/test/java/com/google/protobuf/CodedInputStreamTest.java21
-rw-r--r--java/src/test/java/com/google/protobuf/DeprecatedFieldTest.java80
-rw-r--r--java/src/test/java/com/google/protobuf/DescriptorsTest.java1
-rw-r--r--java/src/test/java/com/google/protobuf/ForceFieldBuildersPreRun.java48
-rw-r--r--java/src/test/java/com/google/protobuf/GeneratedMessageTest.java376
-rw-r--r--java/src/test/java/com/google/protobuf/LazyStringArrayListTest.java118
-rw-r--r--java/src/test/java/com/google/protobuf/LazyStringEndToEndTest.java115
-rw-r--r--java/src/test/java/com/google/protobuf/LiteTest.java29
-rw-r--r--java/src/test/java/com/google/protobuf/NestedBuildersTest.java185
-rw-r--r--java/src/test/java/com/google/protobuf/RepeatedFieldBuilderTest.java190
-rw-r--r--java/src/test/java/com/google/protobuf/ServiceTest.java8
-rw-r--r--java/src/test/java/com/google/protobuf/SingleFieldBuilderTest.java155
-rw-r--r--java/src/test/java/com/google/protobuf/SmallSortedMapTest.java378
-rw-r--r--java/src/test/java/com/google/protobuf/TestBadIdentifiers.java49
-rw-r--r--java/src/test/java/com/google/protobuf/TestUtil.java71
-rw-r--r--java/src/test/java/com/google/protobuf/TextFormatTest.java158
-rw-r--r--java/src/test/java/com/google/protobuf/UnmodifiableLazyStringListTest.java152
-rw-r--r--java/src/test/java/com/google/protobuf/multiple_files_test.proto4
-rw-r--r--java/src/test/java/com/google/protobuf/nested_builders_test.proto53
-rw-r--r--java/src/test/java/com/google/protobuf/nested_extension.proto45
-rw-r--r--java/src/test/java/com/google/protobuf/nested_extension_lite.proto48
-rw-r--r--java/src/test/java/com/google/protobuf/non_nested_extension.proto48
-rw-r--r--java/src/test/java/com/google/protobuf/non_nested_extension_lite.proto50
-rw-r--r--java/src/test/java/com/google/protobuf/test_bad_identifiers.proto66
48 files changed, 6146 insertions, 727 deletions
diff --git a/java/pom.xml b/java/pom.xml
index 94f1d7f6..cdf85de4 100644
--- a/java/pom.xml
+++ b/java/pom.xml
@@ -105,6 +105,12 @@
<arg value="../src/google/protobuf/unittest_mset.proto" />
<arg
value="src/test/java/com/google/protobuf/multiple_files_test.proto" />
+ <arg value="src/test/java/com/google/protobuf/nested_builders_test.proto" />
+ <arg value="src/test/java/com/google/protobuf/nested_extension.proto" />
+ <arg value="src/test/java/com/google/protobuf/nested_extension_lite.proto" />
+ <arg value="src/test/java/com/google/protobuf/non_nested_extension.proto" />
+ <arg value="src/test/java/com/google/protobuf/non_nested_extension_lite.proto" />
+ <arg value="src/test/java/com/google/protobuf/test_bad_identifiers.proto" />
<arg
value="../src/google/protobuf/unittest_optimize_for.proto" />
<arg
diff --git a/java/src/main/java/com/google/protobuf/AbstractMessage.java b/java/src/main/java/com/google/protobuf/AbstractMessage.java
index fb416bdc..b9d83016 100644
--- a/java/src/main/java/com/google/protobuf/AbstractMessage.java
+++ b/java/src/main/java/com/google/protobuf/AbstractMessage.java
@@ -32,9 +32,10 @@ package com.google.protobuf;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
+import com.google.protobuf.Internal.EnumLite;
-import java.io.InputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -167,11 +168,66 @@ public abstract class AbstractMessage extends AbstractMessageLite
public int hashCode() {
int hash = 41;
hash = (19 * hash) + getDescriptorForType().hashCode();
- hash = (53 * hash) + getAllFields().hashCode();
+ hash = hashFields(hash, getAllFields());
hash = (29 * hash) + getUnknownFields().hashCode();
return hash;
}
+ /** Get a hash code for given fields and values, using the given seed. */
+ @SuppressWarnings("unchecked")
+ protected int hashFields(int hash, Map<FieldDescriptor, Object> map) {
+ for (Map.Entry<FieldDescriptor, Object> entry : map.entrySet()) {
+ FieldDescriptor field = entry.getKey();
+ Object value = entry.getValue();
+ hash = (37 * hash) + field.getNumber();
+ if (field.getType() != FieldDescriptor.Type.ENUM){
+ hash = (53 * hash) + value.hashCode();
+ } else if (field.isRepeated()) {
+ List<? extends EnumLite> list = (List<? extends EnumLite>) value;
+ hash = (53 * hash) + hashEnumList(list);
+ } else {
+ hash = (53 * hash) + hashEnum((EnumLite) value);
+ }
+ }
+ return hash;
+ }
+
+ /**
+ * Helper method for implementing {@link Message#hashCode()}.
+ * @see Boolean#hashCode()
+ */
+ protected static int hashLong(long n) {
+ return (int) (n ^ (n >>> 32));
+ }
+
+ /**
+ * Helper method for implementing {@link Message#hashCode()}.
+ * @see Boolean#hashCode()
+ */
+ protected static int hashBoolean(boolean b) {
+ return b ? 1231 : 1237;
+ }
+
+ /**
+ * Helper method for implementing {@link Message#hashCode()}.
+ * <p>
+ * This is needed because {@link java.lang.Enum#hashCode()} is final, but we
+ * need to use the field number as the hash code to ensure compatibility
+ * between statically and dynamically generated enum objects.
+ */
+ protected static int hashEnum(EnumLite e) {
+ return e.getNumber();
+ }
+
+ /** Helper method for implementing {@link Message#hashCode()}. */
+ protected static int hashEnumList(List<? extends EnumLite> list) {
+ int hash = 1;
+ for (EnumLite e : list) {
+ hash = 31 * hash + hashEnum(e);
+ }
+ return hash;
+ }
+
// =================================================================
/**
diff --git a/java/src/main/java/com/google/protobuf/ByteString.java b/java/src/main/java/com/google/protobuf/ByteString.java
index 5fade03a..91356357 100644
--- a/java/src/main/java/com/google/protobuf/ByteString.java
+++ b/java/src/main/java/com/google/protobuf/ByteString.java
@@ -194,6 +194,18 @@ public final class ByteString {
}
/**
+ * Copies bytes into a ByteBuffer.
+ *
+ * @param target ByteBuffer to copy into.
+ * @throws ReadOnlyBufferException if the {@code target} is read-only
+ * @throws BufferOverflowException if the {@code target}'s remaining()
+ * space is not large enough to hold the data.
+ */
+ public void copyTo(ByteBuffer target) {
+ target.put(bytes, 0, bytes.length);
+ }
+
+ /**
* Copies bytes to a {@code byte[]}.
*/
public byte[] toByteArray() {
diff --git a/java/src/main/java/com/google/protobuf/CodedInputStream.java b/java/src/main/java/com/google/protobuf/CodedInputStream.java
index ad43f96d..b3e08555 100644
--- a/java/src/main/java/com/google/protobuf/CodedInputStream.java
+++ b/java/src/main/java/com/google/protobuf/CodedInputStream.java
@@ -67,7 +67,25 @@ public final class CodedInputStream {
*/
public static CodedInputStream newInstance(final byte[] buf, final int off,
final int len) {
- return new CodedInputStream(buf, off, len);
+ CodedInputStream result = new CodedInputStream(buf, off, len);
+ try {
+ // Some uses of CodedInputStream can be more efficient if they know
+ // exactly how many bytes are available. By pushing the end point of the
+ // buffer as a limit, we allow them to get this information via
+ // getBytesUntilLimit(). Pushing a limit that we know is at the end of
+ // the stream can never hurt, since we can never past that point anyway.
+ result.pushLimit(len);
+ } catch (InvalidProtocolBufferException ex) {
+ // The only reason pushLimit() might throw an exception here is if len
+ // is negative. Normally pushLimit()'s parameter comes directly off the
+ // wire, so it's important to catch exceptions in case of corrupt or
+ // malicious data. However, in this case, we expect that len is not a
+ // user-supplied value, so we can assume that it being negative indicates
+ // a programming error. Therefore, throwing an unchecked exception is
+ // appropriate.
+ throw new IllegalArgumentException(ex);
+ }
+ return result;
}
// -----------------------------------------------------------------
@@ -263,7 +281,9 @@ public final class CodedInputStream {
/** Read a {@code bytes} field value from the stream. */
public ByteString readBytes() throws IOException {
final int size = readRawVarint32();
- if (size <= (bufferSize - bufferPos) && size > 0) {
+ if (size == 0) {
+ return ByteString.EMPTY;
+ } else if (size <= (bufferSize - bufferPos) && size > 0) {
// Fast path: We already have the bytes in a contiguous buffer, so
// just copy directly from it.
final ByteString result = ByteString.copyFrom(buffer, bufferPos, size);
@@ -368,8 +388,8 @@ public final class CodedInputStream {
* has already read one byte. This allows the caller to determine if EOF
* has been reached before attempting to read.
*/
- static int readRawVarint32(final int firstByte,
- final InputStream input) throws IOException {
+ public static int readRawVarint32(
+ final int firstByte, final InputStream input) throws IOException {
if ((firstByte & 0x80) == 0) {
return firstByte;
}
@@ -847,19 +867,19 @@ public final class CodedInputStream {
} else {
// Skipping more bytes than are in the buffer. First skip what we have.
int pos = bufferSize - bufferPos;
- totalBytesRetired += pos;
- bufferPos = 0;
- bufferSize = 0;
+ bufferPos = bufferSize;
- // Then skip directly from the InputStream for the rest.
- while (pos < size) {
- final int n = (input == null) ? -1 : (int) input.skip(size - pos);
- if (n <= 0) {
- throw InvalidProtocolBufferException.truncatedMessage();
- }
- pos += n;
- totalBytesRetired += n;
+ // Keep refilling the buffer until we get to the point we wanted to skip
+ // to. This has the side effect of ensuring the limits are updated
+ // correctly.
+ refillBuffer(true);
+ while (size - pos > bufferSize) {
+ pos += bufferSize;
+ bufferPos = bufferSize;
+ refillBuffer(true);
}
+
+ bufferPos = size - pos;
}
}
}
diff --git a/java/src/main/java/com/google/protobuf/CodedOutputStream.java b/java/src/main/java/com/google/protobuf/CodedOutputStream.java
index 58dd1506..51a932a3 100644
--- a/java/src/main/java/com/google/protobuf/CodedOutputStream.java
+++ b/java/src/main/java/com/google/protobuf/CodedOutputStream.java
@@ -33,6 +33,7 @@ package com.google.protobuf;
import java.io.OutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
+import java.io.InputStream;
/**
* Encodes and writes protocol message fields.
@@ -381,9 +382,8 @@ public final class CodedOutputStream {
/** Write a {@code bytes} field to the stream. */
public void writeBytesNoTag(final ByteString value) throws IOException {
- final byte[] bytes = value.toByteArray();
- writeRawVarint32(bytes.length);
- writeRawBytes(bytes);
+ writeRawVarint32(value.size());
+ writeRawBytes(value);
}
/** Write a {@code uint32} field to the stream. */
@@ -870,6 +870,11 @@ public final class CodedOutputStream {
writeRawByte((byte) value);
}
+ /** Write a byte string. */
+ public void writeRawBytes(final ByteString value) throws IOException {
+ writeRawBytes(value, 0, value.size());
+ }
+
/** Write an array of bytes. */
public void writeRawBytes(final byte[] value) throws IOException {
writeRawBytes(value, 0, value.length);
@@ -906,6 +911,53 @@ public final class CodedOutputStream {
}
}
+ /** Write part of a byte string. */
+ public void writeRawBytes(final ByteString value, int offset, int length)
+ throws IOException {
+ if (limit - position >= length) {
+ // We have room in the current buffer.
+ value.copyTo(buffer, offset, position, length);
+ position += length;
+ } else {
+ // Write extends past current buffer. Fill the rest of this buffer and
+ // flush.
+ final int bytesWritten = limit - position;
+ value.copyTo(buffer, offset, position, bytesWritten);
+ offset += bytesWritten;
+ length -= bytesWritten;
+ position = limit;
+ refreshBuffer();
+
+ // Now deal with the rest.
+ // Since we have an output stream, this is our buffer
+ // and buffer offset == 0
+ if (length <= limit) {
+ // Fits in new buffer.
+ value.copyTo(buffer, offset, 0, length);
+ position = length;
+ } else {
+ // Write is very big, but we can't do it all at once without allocating
+ // an a copy of the byte array since ByteString does not give us access
+ // to the underlying bytes. Use the InputStream interface on the
+ // ByteString and our buffer to copy between the two.
+ InputStream inputStreamFrom = value.newInput();
+ if (offset != inputStreamFrom.skip(offset)) {
+ throw new IllegalStateException("Skip failed? Should never happen.");
+ }
+ // Use the buffer as the temporary buffer to avoid allocating memory.
+ while (length > 0) {
+ int bytesToRead = Math.min(length, limit);
+ int bytesRead = inputStreamFrom.read(buffer, 0, bytesToRead);
+ if (bytesRead != bytesToRead) {
+ throw new IllegalStateException("Read failed? Should never happen");
+ }
+ output.write(buffer, 0, bytesRead);
+ length -= bytesRead;
+ }
+ }
+ }
+ }
+
/** Encode and write a tag. */
public void writeTag(final int fieldNumber, final int wireType)
throws IOException {
diff --git a/java/src/main/java/com/google/protobuf/Descriptors.java b/java/src/main/java/com/google/protobuf/Descriptors.java
index c5e9a04b..2ee84594 100644
--- a/java/src/main/java/com/google/protobuf/Descriptors.java
+++ b/java/src/main/java/com/google/protobuf/Descriptors.java
@@ -46,6 +46,11 @@ import java.io.UnsupportedEncodingException;
* its fields and other information about a type. You can get a message
* type's descriptor by calling {@code MessageType.getDescriptor()}, or
* (given a message object of the type) {@code message.getDescriptorForType()}.
+ * Furthermore, each message is associated with a {@link FileDescriptor} for
+ * a relevant {@code .proto} file. You can obtain it by calling
+ * {@code Descriptor.getFile()}. A {@link FileDescriptor} contains descriptors
+ * for all the messages defined in that file, and file descriptors for all the
+ * imported {@code .proto} files.
*
* Descriptors are built from DescriptorProtos, as defined in
* {@code google/protobuf/descriptor.proto}.
@@ -55,6 +60,9 @@ import java.io.UnsupportedEncodingException;
public final class Descriptors {
/**
* Describes a {@code .proto} file, including everything defined within.
+ * That includes, in particular, descriptors for all the messages and
+ * file descriptors for all other imported {@code .proto} files
+ * (dependencies).
*/
public static final class FileDescriptor {
/** Convert the descriptor to its protocol message representation. */
diff --git a/java/src/main/java/com/google/protobuf/FieldSet.java b/java/src/main/java/com/google/protobuf/FieldSet.java
index bc1bb797..a85dbaa6 100644
--- a/java/src/main/java/com/google/protobuf/FieldSet.java
+++ b/java/src/main/java/com/google/protobuf/FieldSet.java
@@ -33,7 +33,6 @@ package com.google.protobuf;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
-import java.util.TreeMap;
import java.util.List;
import java.util.Map;
import java.io.IOException;
@@ -67,16 +66,12 @@ final class FieldSet<FieldDescriptorType extends
MessageLite.Builder to, MessageLite from);
}
- private Map<FieldDescriptorType, Object> fields;
+ private final SmallSortedMap<FieldDescriptorType, Object> fields;
+ private boolean isImmutable;
/** Construct a new FieldSet. */
private FieldSet() {
- // Use a TreeMap because fields need to be in canonical order when
- // serializing.
- // TODO(kenton): Maybe use some sort of sparse array instead? It would
- // even make sense to store the first 16 or so tags in a flat array
- // to make DynamicMessage faster.
- fields = new TreeMap<FieldDescriptorType, Object>();
+ this.fields = SmallSortedMap.newFieldMap(16);
}
/**
@@ -84,7 +79,8 @@ final class FieldSet<FieldDescriptorType extends
* DEFAULT_INSTANCE.
*/
private FieldSet(final boolean dummy) {
- this.fields = Collections.emptyMap();
+ this.fields = SmallSortedMap.newFieldMap(0);
+ makeImmutable();
}
/** Construct a new FieldSet. */
@@ -105,14 +101,45 @@ final class FieldSet<FieldDescriptorType extends
/** Make this FieldSet immutable from this point forward. */
@SuppressWarnings("unchecked")
public void makeImmutable() {
- for (final Map.Entry<FieldDescriptorType, Object> entry:
- fields.entrySet()) {
- if (entry.getKey().isRepeated()) {
- final List value = (List)entry.getValue();
- fields.put(entry.getKey(), Collections.unmodifiableList(value));
- }
+ if (isImmutable) {
+ return;
}
- fields = Collections.unmodifiableMap(fields);
+ fields.makeImmutable();
+ isImmutable = true;
+ }
+
+ /**
+ * Retuns whether the FieldSet is immutable. This is true if it is the
+ * {@link #emptySet} or if {@link #makeImmutable} were called.
+ *
+ * @return whether the FieldSet is immutable.
+ */
+ public boolean isImmutable() {
+ return isImmutable;
+ }
+
+ /**
+ * Clones the FieldSet. The returned FieldSet will be mutable even if the
+ * original FieldSet was immutable.
+ *
+ * @return the newly cloned FieldSet
+ */
+ @Override
+ public FieldSet<FieldDescriptorType> clone() {
+ // We can't just call fields.clone because List objects in the map
+ // should not be shared.
+ FieldSet<FieldDescriptorType> clone = FieldSet.newFieldSet();
+ for (int i = 0; i < fields.getNumArrayEntries(); i++) {
+ Map.Entry<FieldDescriptorType, Object> entry = fields.getArrayEntryAt(i);
+ FieldDescriptorType descriptor = entry.getKey();
+ clone.setField(descriptor, entry.getValue());
+ }
+ for (Map.Entry<FieldDescriptorType, Object> entry :
+ fields.getOverflowEntries()) {
+ FieldDescriptorType descriptor = entry.getKey();
+ clone.setField(descriptor, entry.getValue());
+ }
+ return clone;
}
// =================================================================
@@ -126,12 +153,13 @@ final class FieldSet<FieldDescriptorType extends
* Get a simple map containing all the fields.
*/
public Map<FieldDescriptorType, Object> getAllFields() {
- return Collections.unmodifiableMap(fields);
+ return fields.isImmutable() ? fields : Collections.unmodifiableMap(fields);
}
/**
- * Get an iterator to the field map. This iterator should not be leaked
- * out of the protobuf library as it is not protected from mutation.
+ * Get an iterator to the field map. This iterator should not be leaked out
+ * of the protobuf library as it is not protected from mutation when
+ * fields is not immutable.
*/
public Iterator<Map.Entry<FieldDescriptorType, Object>> iterator() {
return fields.entrySet().iterator();
@@ -336,27 +364,39 @@ final class FieldSet<FieldDescriptorType extends
* 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 (final Map.Entry<FieldDescriptorType, Object> entry:
- fields.entrySet()) {
- final FieldDescriptorType descriptor = entry.getKey();
- if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE) {
- if (descriptor.isRepeated()) {
- for (final MessageLite element:
- (List<MessageLite>) entry.getValue()) {
- if (!element.isInitialized()) {
- return false;
- }
- }
- } else {
- if (!((MessageLite) entry.getValue()).isInitialized()) {
+ for (int i = 0; i < fields.getNumArrayEntries(); i++) {
+ if (!isInitialized(fields.getArrayEntryAt(i))) {
+ return false;
+ }
+ }
+ for (final Map.Entry<FieldDescriptorType, Object> entry :
+ fields.getOverflowEntries()) {
+ if (!isInitialized(entry)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @SuppressWarnings("unchecked")
+ private boolean isInitialized(
+ final Map.Entry<FieldDescriptorType, Object> entry) {
+ final FieldDescriptorType descriptor = entry.getKey();
+ if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE) {
+ if (descriptor.isRepeated()) {
+ for (final MessageLite element:
+ (List<MessageLite>) entry.getValue()) {
+ if (!element.isInitialized()) {
return false;
}
}
+ } else {
+ if (!((MessageLite) entry.getValue()).isInitialized()) {
+ return false;
+ }
}
}
-
return true;
}
@@ -378,39 +418,48 @@ final class FieldSet<FieldDescriptorType extends
/**
* Like {@link #mergeFrom(Message)}, but merges from another {@link FieldSet}.
*/
- @SuppressWarnings("unchecked")
public void mergeFrom(final FieldSet<FieldDescriptorType> other) {
- for (final Map.Entry<FieldDescriptorType, Object> entry:
- other.fields.entrySet()) {
- final FieldDescriptorType descriptor = entry.getKey();
- final Object otherValue = entry.getValue();
+ for (int i = 0; i < other.fields.getNumArrayEntries(); i++) {
+ mergeFromField(other.fields.getArrayEntryAt(i));
+ }
+ for (final Map.Entry<FieldDescriptorType, Object> entry :
+ other.fields.getOverflowEntries()) {
+ mergeFromField(entry);
+ }
+ }
- if (descriptor.isRepeated()) {
- Object value = fields.get(descriptor);
- if (value == null) {
- // Our list is empty, but we still need to make a defensive copy of
- // the other list since we don't know if the other FieldSet is still
- // mutable.
- fields.put(descriptor, new ArrayList((List) otherValue));
- } else {
- // Concatenate the lists.
- ((List) value).addAll((List) otherValue);
- }
- } else if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE) {
- Object value = fields.get(descriptor);
- if (value == null) {
- fields.put(descriptor, otherValue);
- } else {
- // Merge the messages.
- fields.put(descriptor,
- descriptor.internalMergeFrom(
- ((MessageLite) value).toBuilder(), (MessageLite) otherValue)
- .build());
- }
+ @SuppressWarnings("unchecked")
+ private void mergeFromField(
+ final Map.Entry<FieldDescriptorType, Object> entry) {
+ final FieldDescriptorType descriptor = entry.getKey();
+ final Object otherValue = entry.getValue();
+ if (descriptor.isRepeated()) {
+ Object value = fields.get(descriptor);
+ if (value == null) {
+ // Our list is empty, but we still need to make a defensive copy of
+ // the other list since we don't know if the other FieldSet is still
+ // mutable.
+ fields.put(descriptor, new ArrayList((List) otherValue));
} else {
+ // Concatenate the lists.
+ ((List) value).addAll((List) otherValue);
+ }
+ } else if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE) {
+ Object value = fields.get(descriptor);
+ if (value == null) {
fields.put(descriptor, otherValue);
+ } else {
+ // Merge the messages.
+ fields.put(
+ descriptor,
+ descriptor.internalMergeFrom(
+ ((MessageLite) value).toBuilder(), (MessageLite) otherValue)
+ .build());
}
+
+ } else {
+ fields.put(descriptor, otherValue);
}
}
@@ -468,8 +517,13 @@ final class FieldSet<FieldDescriptorType extends
/** See {@link Message#writeTo(CodedOutputStream)}. */
public void writeTo(final CodedOutputStream output)
throws IOException {
- for (final Map.Entry<FieldDescriptorType, Object> entry:
- fields.entrySet()) {
+ for (int i = 0; i < fields.getNumArrayEntries(); i++) {
+ final Map.Entry<FieldDescriptorType, Object> entry =
+ fields.getArrayEntryAt(i);
+ writeField(entry.getKey(), entry.getValue(), output);
+ }
+ for (final Map.Entry<FieldDescriptorType, Object> entry :
+ fields.getOverflowEntries()) {
writeField(entry.getKey(), entry.getValue(), output);
}
}
@@ -479,16 +533,25 @@ final class FieldSet<FieldDescriptorType extends
*/
public void writeMessageSetTo(final CodedOutputStream output)
throws IOException {
- for (final Map.Entry<FieldDescriptorType, Object> entry:
- fields.entrySet()) {
- final FieldDescriptorType descriptor = entry.getKey();
- if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE &&
- !descriptor.isRepeated() && !descriptor.isPacked()) {
- output.writeMessageSetExtension(entry.getKey().getNumber(),
- (MessageLite) entry.getValue());
- } else {
- writeField(descriptor, entry.getValue(), output);
- }
+ for (int i = 0; i < fields.getNumArrayEntries(); i++) {
+ writeMessageSetTo(fields.getArrayEntryAt(i), output);
+ }
+ for (final Map.Entry<FieldDescriptorType, Object> entry :
+ fields.getOverflowEntries()) {
+ writeMessageSetTo(entry, output);
+ }
+ }
+
+ private void writeMessageSetTo(
+ final Map.Entry<FieldDescriptorType, Object> entry,
+ final CodedOutputStream output) throws IOException {
+ final FieldDescriptorType descriptor = entry.getKey();
+ if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE &&
+ !descriptor.isRepeated() && !descriptor.isPacked()) {
+ output.writeMessageSetExtension(entry.getKey().getNumber(),
+ (MessageLite) entry.getValue());
+ } else {
+ writeField(descriptor, entry.getValue(), output);
}
}
@@ -593,8 +656,13 @@ final class FieldSet<FieldDescriptorType extends
*/
public int getSerializedSize() {
int size = 0;
- for (final Map.Entry<FieldDescriptorType, Object> entry:
- fields.entrySet()) {
+ for (int i = 0; i < fields.getNumArrayEntries(); i++) {
+ final Map.Entry<FieldDescriptorType, Object> entry =
+ fields.getArrayEntryAt(i);
+ size += computeFieldSize(entry.getKey(), entry.getValue());
+ }
+ for (final Map.Entry<FieldDescriptorType, Object> entry :
+ fields.getOverflowEntries()) {
size += computeFieldSize(entry.getKey(), entry.getValue());
}
return size;
@@ -605,20 +673,28 @@ final class FieldSet<FieldDescriptorType extends
*/
public int getMessageSetSerializedSize() {
int size = 0;
- for (final Map.Entry<FieldDescriptorType, Object> entry:
- fields.entrySet()) {
- final FieldDescriptorType descriptor = entry.getKey();
- if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE &&
- !descriptor.isRepeated() && !descriptor.isPacked()) {
- size += CodedOutputStream.computeMessageSetExtensionSize(
- entry.getKey().getNumber(), (MessageLite) entry.getValue());
- } else {
- size += computeFieldSize(descriptor, entry.getValue());
- }
+ for (int i = 0; i < fields.getNumArrayEntries(); i++) {
+ size += getMessageSetSerializedSize(fields.getArrayEntryAt(i));
+ }
+ for (final Map.Entry<FieldDescriptorType, Object> entry :
+ fields.getOverflowEntries()) {
+ size += getMessageSetSerializedSize(entry);
}
return size;
}
+ private int getMessageSetSerializedSize(
+ final Map.Entry<FieldDescriptorType, Object> entry) {
+ final FieldDescriptorType descriptor = entry.getKey();
+ if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE &&
+ !descriptor.isRepeated() && !descriptor.isPacked()) {
+ return CodedOutputStream.computeMessageSetExtensionSize(
+ entry.getKey().getNumber(), (MessageLite) entry.getValue());
+ } else {
+ return computeFieldSize(descriptor, entry.getValue());
+ }
+ }
+
/**
* Compute the number of bytes that would be needed to encode a
* single tag/value pair of arbitrary type.
diff --git a/java/src/main/java/com/google/protobuf/GeneratedMessage.java b/java/src/main/java/com/google/protobuf/GeneratedMessage.java
index 42ccbfd8..fc2e5303 100644
--- a/java/src/main/java/com/google/protobuf/GeneratedMessage.java
+++ b/java/src/main/java/com/google/protobuf/GeneratedMessage.java
@@ -35,8 +35,10 @@ import com.google.protobuf.Descriptors.EnumValueDescriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import java.io.IOException;
-import java.lang.reflect.Method;
+import java.io.ObjectStreamException;
+import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
@@ -52,10 +54,35 @@ import java.util.TreeMap;
*
* @author kenton@google.com Kenton Varda
*/
-public abstract class GeneratedMessage extends AbstractMessage {
- protected GeneratedMessage() {}
+public abstract class GeneratedMessage extends AbstractMessage
+ implements Serializable {
+
+ private final UnknownFieldSet unknownFields;
+
+ /**
+ * For testing. Allows a test to disable the optimization that avoids using
+ * field builders for nested messages until they are requested. By disabling
+ * this optimization, existing tests can be reused to test the field builders.
+ */
+ protected static boolean alwaysUseFieldBuilders = false;
- private UnknownFieldSet unknownFields = UnknownFieldSet.getDefaultInstance();
+ protected GeneratedMessage() {
+ this.unknownFields = UnknownFieldSet.getDefaultInstance();
+ }
+
+ protected GeneratedMessage(Builder<?> builder) {
+ this.unknownFields = builder.getUnknownFields();
+ }
+
+ /**
+ * For testing. Allows a test to disable the optimization that avoids using
+ * field builders for nested messages until they are requested. By disabling
+ * this optimization, existing tests can be reused to test the field builders.
+ * See {@link RepeatedFieldBuilder} and {@link SingleFieldBuilder}.
+ */
+ static void enableAlwaysUseFieldBuildersForTesting() {
+ alwaysUseFieldBuilders = true;
+ }
/**
* Get the FieldAccessorTable for this type. We can't have the message
@@ -64,6 +91,7 @@ public abstract class GeneratedMessage extends AbstractMessage {
*/
protected abstract FieldAccessorTable internalGetFieldAccessorTable();
+ //@Override (Java 1.6 override semantics, but we must support 1.5)
public Descriptor getDescriptorForType() {
return internalGetFieldAccessorTable().descriptor;
}
@@ -118,36 +146,115 @@ public abstract class GeneratedMessage extends AbstractMessage {
return true;
}
+ //@Override (Java 1.6 override semantics, but we must support 1.5)
public Map<FieldDescriptor, Object> getAllFields() {
return Collections.unmodifiableMap(getAllFieldsMutable());
}
+ //@Override (Java 1.6 override semantics, but we must support 1.5)
public boolean hasField(final FieldDescriptor field) {
return internalGetFieldAccessorTable().getField(field).has(this);
}
+ //@Override (Java 1.6 override semantics, but we must support 1.5)
public Object getField(final FieldDescriptor field) {
return internalGetFieldAccessorTable().getField(field).get(this);
}
+ //@Override (Java 1.6 override semantics, but we must support 1.5)
public int getRepeatedFieldCount(final FieldDescriptor field) {
return internalGetFieldAccessorTable().getField(field)
.getRepeatedCount(this);
}
+ //@Override (Java 1.6 override semantics, but we must support 1.5)
public Object getRepeatedField(final FieldDescriptor field, final int index) {
return internalGetFieldAccessorTable().getField(field)
.getRepeated(this, index);
}
+ //@Override (Java 1.6 override semantics, but we must support 1.5)
public final UnknownFieldSet getUnknownFields() {
return unknownFields;
}
+ protected abstract Message.Builder newBuilderForType(BuilderParent parent);
+
+ /**
+ * Interface for the parent of a Builder that allows the builder to
+ * communicate invalidations back to the parent for use when using nested
+ * builders.
+ */
+ protected interface BuilderParent {
+
+ /**
+ * A builder becomes dirty whenever a field is modified -- including fields
+ * in nested builders -- and becomes clean when build() is called. Thus,
+ * when a builder becomes dirty, all its parents become dirty as well, and
+ * when it becomes clean, all its children become clean. The dirtiness
+ * state is used to invalidate certain cached values.
+ * <br>
+ * To this end, a builder calls markAsDirty() on its parent whenever it
+ * transitions from clean to dirty. The parent must propagate this call to
+ * its own parent, unless it was already dirty, in which case the
+ * grandparent must necessarily already be dirty as well. The parent can
+ * only transition back to "clean" after calling build() on all children.
+ */
+ void markDirty();
+ }
+
@SuppressWarnings("unchecked")
public abstract static class Builder <BuilderType extends Builder>
extends AbstractMessage.Builder<BuilderType> {
- protected Builder() {}
+
+ private BuilderParent builderParent;
+
+ private BuilderParentImpl meAsParent;
+
+ // Indicates that we've built a message and so we are now obligated
+ // to dispatch dirty invalidations. See GeneratedMessage.BuilderListener.
+ private boolean isClean;
+
+ private UnknownFieldSet unknownFields =
+ UnknownFieldSet.getDefaultInstance();
+
+ protected Builder() {
+ this(null);
+ }
+
+ protected Builder(BuilderParent builderParent) {
+ this.builderParent = builderParent;
+ }
+
+ void dispose() {
+ builderParent = null;
+ }
+
+ /**
+ * Called by the subclass when a message is built.
+ */
+ protected void onBuilt() {
+ if (builderParent != null) {
+ markClean();
+ }
+ }
+
+ /**
+ * Called by the subclass or a builder to notify us that a message was
+ * built and may be cached and therefore invalidations are needed.
+ */
+ protected void markClean() {
+ this.isClean = true;
+ }
+
+ /**
+ * Gets whether invalidations are needed
+ *
+ * @return whether invalidations are needed
+ */
+ protected boolean isClean() {
+ return isClean;
+ }
// This is implemented here only to work around an apparent bug in the
// Java compiler and/or build system. See bug #1898463. The mere presence
@@ -159,26 +266,50 @@ public abstract class GeneratedMessage extends AbstractMessage {
}
/**
- * Get the message being built. We don't just pass this to the
- * constructor because it becomes null when build() is called.
+ * Called by the initialization and clear code paths to allow subclasses to
+ * reset any of their builtin fields back to the initial values.
*/
- protected abstract GeneratedMessage internalGetResult();
+ public BuilderType clear() {
+ unknownFields = UnknownFieldSet.getDefaultInstance();
+ onChanged();
+ return (BuilderType) this;
+ }
/**
* Get the FieldAccessorTable for this type. We can't have the message
* class pass this in to the constructor because of bootstrapping trouble
* with DescriptorProtos.
*/
- private FieldAccessorTable internalGetFieldAccessorTable() {
- return internalGetResult().internalGetFieldAccessorTable();
- }
+ protected abstract FieldAccessorTable internalGetFieldAccessorTable();
+ //@Override (Java 1.6 override semantics, but we must support 1.5)
public Descriptor getDescriptorForType() {
return internalGetFieldAccessorTable().descriptor;
}
+ //@Override (Java 1.6 override semantics, but we must support 1.5)
public Map<FieldDescriptor, Object> getAllFields() {
- return internalGetResult().getAllFields();
+ return Collections.unmodifiableMap(getAllFieldsMutable());
+ }
+
+ /** Internal helper which returns a mutable map. */
+ private Map<FieldDescriptor, Object> getAllFieldsMutable() {
+ final TreeMap<FieldDescriptor, Object> result =
+ new TreeMap<FieldDescriptor, Object>();
+ final Descriptor descriptor = internalGetFieldAccessorTable().descriptor;
+ for (final FieldDescriptor field : descriptor.getFields()) {
+ if (field.isRepeated()) {
+ final List value = (List) getField(field);
+ if (!value.isEmpty()) {
+ result.put(field, value);
+ }
+ } else {
+ if (hasField(field)) {
+ result.put(field, getField(field));
+ }
+ }
+ }
+ return result;
}
public Message.Builder newBuilderForField(
@@ -186,18 +317,20 @@ public abstract class GeneratedMessage extends AbstractMessage {
return internalGetFieldAccessorTable().getField(field).newBuilder();
}
+ //@Override (Java 1.6 override semantics, but we must support 1.5)
public boolean hasField(final FieldDescriptor field) {
- return internalGetResult().hasField(field);
+ return internalGetFieldAccessorTable().getField(field).has(this);
}
+ //@Override (Java 1.6 override semantics, but we must support 1.5)
public Object getField(final FieldDescriptor field) {
+ Object object = internalGetFieldAccessorTable().getField(field).get(this);
if (field.isRepeated()) {
// The underlying list object is still modifiable at this point.
// Make sure not to expose the modifiable list to the caller.
- return Collections.unmodifiableList(
- (List) internalGetResult().getField(field));
+ return Collections.unmodifiableList((List) object);
} else {
- return internalGetResult().getField(field);
+ return object;
}
}
@@ -207,18 +340,23 @@ public abstract class GeneratedMessage extends AbstractMessage {
return (BuilderType) this;
}
+ //@Override (Java 1.6 override semantics, but we must support 1.5)
public BuilderType clearField(final FieldDescriptor field) {
internalGetFieldAccessorTable().getField(field).clear(this);
return (BuilderType) this;
}
+ //@Override (Java 1.6 override semantics, but we must support 1.5)
public int getRepeatedFieldCount(final FieldDescriptor field) {
- return internalGetResult().getRepeatedFieldCount(field);
+ return internalGetFieldAccessorTable().getField(field)
+ .getRepeatedCount(this);
}
+ //@Override (Java 1.6 override semantics, but we must support 1.5)
public Object getRepeatedField(final FieldDescriptor field,
final int index) {
- return internalGetResult().getRepeatedField(field, index);
+ return internalGetFieldAccessorTable().getField(field)
+ .getRepeated(this, index);
}
public BuilderType setRepeatedField(final FieldDescriptor field,
@@ -234,29 +372,57 @@ public abstract class GeneratedMessage extends AbstractMessage {
return (BuilderType) this;
}
- public final UnknownFieldSet getUnknownFields() {
- return internalGetResult().unknownFields;
- }
-
public final BuilderType setUnknownFields(
final UnknownFieldSet unknownFields) {
- internalGetResult().unknownFields = unknownFields;
+ this.unknownFields = unknownFields;
+ onChanged();
return (BuilderType) this;
}
@Override
public final BuilderType mergeUnknownFields(
final UnknownFieldSet unknownFields) {
- final GeneratedMessage result = internalGetResult();
- result.unknownFields =
- UnknownFieldSet.newBuilder(result.unknownFields)
+ this.unknownFields =
+ UnknownFieldSet.newBuilder(this.unknownFields)
.mergeFrom(unknownFields)
.build();
+ onChanged();
return (BuilderType) this;
}
+ //@Override (Java 1.6 override semantics, but we must support 1.5)
public boolean isInitialized() {
- return internalGetResult().isInitialized();
+ for (final FieldDescriptor field : getDescriptorForType().getFields()) {
+ // Check that all required fields are present.
+ if (field.isRequired()) {
+ if (!hasField(field)) {
+ return false;
+ }
+ }
+ // Check that embedded messages are initialized.
+ if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
+ if (field.isRepeated()) {
+ @SuppressWarnings("unchecked") final
+ List<Message> messageList = (List<Message>) getField(field);
+ for (final Message element : messageList) {
+ if (!element.isInitialized()) {
+ return false;
+ }
+ }
+ } else {
+ if (hasField(field) &&
+ !((Message) getField(field)).isInitialized()) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public final UnknownFieldSet getUnknownFields() {
+ return unknownFields;
}
/**
@@ -270,11 +436,68 @@ public abstract class GeneratedMessage extends AbstractMessage {
final int tag) throws IOException {
return unknownFields.mergeFieldFrom(tag, input);
}
+
+ /**
+ * Implementation of {@link BuilderParent} for giving to our children. This
+ * small inner class makes it so we don't publicly expose the BuilderParent
+ * methods.
+ */
+ private class BuilderParentImpl implements BuilderParent {
+
+ @Override
+ public void markDirty() {
+ onChanged();
+ }
+ }
+
+ /**
+ * Gets the {@link BuilderParent} for giving to our children.
+ * @return The builder parent for our children.
+ */
+ protected BuilderParent getParentForChildren() {
+ if (meAsParent == null) {
+ meAsParent = new BuilderParentImpl();
+ }
+ return meAsParent;
+ }
+
+ /**
+ * Called when a the builder or one of its nested children has changed
+ * and any parent should be notified of its invalidation.
+ */
+ protected final void onChanged() {
+ if (isClean && builderParent != null) {
+ builderParent.markDirty();
+
+ // Don't keep dispatching invalidations until build is called again.
+ isClean = false;
+ }
+ }
}
// =================================================================
// Extensions-related stuff
+ public interface ExtendableMessageOrBuilder<
+ MessageType extends ExtendableMessage> extends MessageOrBuilder {
+
+ /** Check if a singular extension is present. */
+ <Type> boolean hasExtension(
+ GeneratedExtension<MessageType, Type> extension);
+
+ /** Get the number of elements in a repeated extension. */
+ <Type> int getExtensionCount(
+ GeneratedExtension<MessageType, List<Type>> extension);
+
+ /** Get the value of an extension. */
+ <Type> Type getExtension(GeneratedExtension<MessageType, Type> extension);
+
+ /** Get one element of a repeated extension. */
+ <Type> Type getExtension(
+ GeneratedExtension<MessageType, List<Type>> extension,
+ int index);
+ }
+
/**
* Generated message classes for message types that contain extension ranges
* subclass this.
@@ -312,9 +535,20 @@ public abstract class GeneratedMessage extends AbstractMessage {
*/
public abstract static class ExtendableMessage<
MessageType extends ExtendableMessage>
- extends GeneratedMessage {
- protected ExtendableMessage() {}
- private final FieldSet<FieldDescriptor> extensions = FieldSet.newFieldSet();
+ extends GeneratedMessage
+ implements ExtendableMessageOrBuilder<MessageType> {
+
+ private final FieldSet<FieldDescriptor> extensions;
+
+ protected ExtendableMessage() {
+ this.extensions = FieldSet.newFieldSet();
+ }
+
+ protected ExtendableMessage(
+ ExtendableBuilder<MessageType, ?> builder) {
+ super(builder);
+ this.extensions = builder.buildExtensions();
+ }
private void verifyExtensionContainingType(
final GeneratedExtension<MessageType, ?> extension) {
@@ -330,13 +564,15 @@ public abstract class GeneratedMessage extends AbstractMessage {
}
/** Check if a singular extension is present. */
- public final boolean hasExtension(
- final GeneratedExtension<MessageType, ?> extension) {
+ //@Override (Java 1.6 override semantics, but we must support 1.5)
+ public final <Type> boolean hasExtension(
+ final GeneratedExtension<MessageType, Type> extension) {
verifyExtensionContainingType(extension);
return extensions.hasField(extension.getDescriptor());
}
/** Get the number of elements in a repeated extension. */
+ //@Override (Java 1.6 override semantics, but we must support 1.5)
public final <Type> int getExtensionCount(
final GeneratedExtension<MessageType, List<Type>> extension) {
verifyExtensionContainingType(extension);
@@ -345,6 +581,7 @@ public abstract class GeneratedMessage extends AbstractMessage {
}
/** Get the value of an extension. */
+ //@Override (Java 1.6 override semantics, but we must support 1.5)
@SuppressWarnings("unchecked")
public final <Type> Type getExtension(
final GeneratedExtension<MessageType, Type> extension) {
@@ -367,6 +604,7 @@ public abstract class GeneratedMessage extends AbstractMessage {
}
/** Get one element of a repeated extension. */
+ //@Override (Java 1.6 override semantics, but we must support 1.5)
@SuppressWarnings("unchecked")
public final <Type> Type getExtension(
final GeneratedExtension<MessageType, List<Type>> extension,
@@ -448,10 +686,14 @@ public abstract class GeneratedMessage extends AbstractMessage {
// ---------------------------------------------------------------
// Reflection
+ protected Map<FieldDescriptor, Object> getExtensionFields() {
+ return extensions.getAllFields();
+ }
+
@Override
public Map<FieldDescriptor, Object> getAllFields() {
final Map<FieldDescriptor, Object> result = super.getAllFieldsMutable();
- result.putAll(extensions.getAllFields());
+ result.putAll(getExtensionFields());
return Collections.unmodifiableMap(result);
}
@@ -556,9 +798,24 @@ public abstract class GeneratedMessage extends AbstractMessage {
public abstract static class ExtendableBuilder<
MessageType extends ExtendableMessage,
BuilderType extends ExtendableBuilder>
- extends Builder<BuilderType> {
+ extends Builder<BuilderType>
+ implements ExtendableMessageOrBuilder<MessageType> {
+
+ private FieldSet<FieldDescriptor> extensions = FieldSet.emptySet();
+
protected ExtendableBuilder() {}
+ protected ExtendableBuilder(
+ BuilderParent parent) {
+ super(parent);
+ }
+
+ @Override
+ public BuilderType clear() {
+ extensions = FieldSet.emptySet();
+ return super.clear();
+ }
+
// This is implemented here only to work around an apparent bug in the
// Java compiler and/or build system. See bug #1898463. The mere presence
// of this dummy clone() implementation makes it go away.
@@ -568,43 +825,84 @@ public abstract class GeneratedMessage extends AbstractMessage {
"This is supposed to be overridden by subclasses.");
}
- @Override
- protected abstract ExtendableMessage<MessageType> internalGetResult();
+ private void ensureExtensionsIsMutable() {
+ if (extensions.isImmutable()) {
+ extensions = extensions.clone();
+ }
+ }
- /** Check if a singular extension is present. */
- public final boolean hasExtension(
+ private void verifyExtensionContainingType(
final GeneratedExtension<MessageType, ?> extension) {
- return internalGetResult().hasExtension(extension);
+ if (extension.getDescriptor().getContainingType() !=
+ getDescriptorForType()) {
+ // This can only happen if someone uses unchecked operations.
+ throw new IllegalArgumentException(
+ "Extension is for type \"" +
+ extension.getDescriptor().getContainingType().getFullName() +
+ "\" which does not match message type \"" +
+ getDescriptorForType().getFullName() + "\".");
+ }
+ }
+
+ /** Check if a singular extension is present. */
+ //@Override (Java 1.6 override semantics, but we must support 1.5)
+ public final <Type> boolean hasExtension(
+ final GeneratedExtension<MessageType, Type> extension) {
+ verifyExtensionContainingType(extension);
+ return extensions.hasField(extension.getDescriptor());
}
/** Get the number of elements in a repeated extension. */
+ //@Override (Java 1.6 override semantics, but we must support 1.5)
public final <Type> int getExtensionCount(
final GeneratedExtension<MessageType, List<Type>> extension) {
- return internalGetResult().getExtensionCount(extension);
+ verifyExtensionContainingType(extension);
+ final FieldDescriptor descriptor = extension.getDescriptor();
+ return extensions.getRepeatedFieldCount(descriptor);
}
/** Get the value of an extension. */
+ //@Override (Java 1.6 override semantics, but we must support 1.5)
public final <Type> Type getExtension(
final GeneratedExtension<MessageType, Type> extension) {
- return internalGetResult().getExtension(extension);
+ verifyExtensionContainingType(extension);
+ FieldDescriptor descriptor = extension.getDescriptor();
+ final Object value = extensions.getField(descriptor);
+ if (value == null) {
+ if (descriptor.isRepeated()) {
+ return (Type) Collections.emptyList();
+ } else if (descriptor.getJavaType() ==
+ FieldDescriptor.JavaType.MESSAGE) {
+ return (Type) extension.getMessageDefaultInstance();
+ } else {
+ return (Type) extension.fromReflectionType(
+ descriptor.getDefaultValue());
+ }
+ } else {
+ return (Type) extension.fromReflectionType(value);
+ }
}
/** Get one element of a repeated extension. */
+ //@Override (Java 1.6 override semantics, but we must support 1.5)
public final <Type> Type getExtension(
final GeneratedExtension<MessageType, List<Type>> extension,
final int index) {
- return internalGetResult().getExtension(extension, index);
+ verifyExtensionContainingType(extension);
+ FieldDescriptor descriptor = extension.getDescriptor();
+ return (Type) extension.singularFromReflectionType(
+ extensions.getRepeatedField(descriptor, index));
}
/** Set the value of an extension. */
public final <Type> BuilderType setExtension(
final GeneratedExtension<MessageType, Type> extension,
final Type value) {
- final ExtendableMessage<MessageType> message = internalGetResult();
- message.verifyExtensionContainingType(extension);
+ verifyExtensionContainingType(extension);
+ ensureExtensionsIsMutable();
final FieldDescriptor descriptor = extension.getDescriptor();
- message.extensions.setField(descriptor,
- extension.toReflectionType(value));
+ extensions.setField(descriptor, extension.toReflectionType(value));
+ onChanged();
return (BuilderType) this;
}
@@ -612,12 +910,13 @@ public abstract class GeneratedMessage extends AbstractMessage {
public final <Type> BuilderType setExtension(
final GeneratedExtension<MessageType, List<Type>> extension,
final int index, final Type value) {
- final ExtendableMessage<MessageType> message = internalGetResult();
- message.verifyExtensionContainingType(extension);
+ verifyExtensionContainingType(extension);
+ ensureExtensionsIsMutable();
final FieldDescriptor descriptor = extension.getDescriptor();
- message.extensions.setRepeatedField(
+ extensions.setRepeatedField(
descriptor, index,
extension.singularToReflectionType(value));
+ onChanged();
return (BuilderType) this;
}
@@ -625,23 +924,44 @@ public abstract class GeneratedMessage extends AbstractMessage {
public final <Type> BuilderType addExtension(
final GeneratedExtension<MessageType, List<Type>> extension,
final Type value) {
- final ExtendableMessage<MessageType> message = internalGetResult();
- message.verifyExtensionContainingType(extension);
+ verifyExtensionContainingType(extension);
+ ensureExtensionsIsMutable();
final FieldDescriptor descriptor = extension.getDescriptor();
- message.extensions.addRepeatedField(
+ extensions.addRepeatedField(
descriptor, extension.singularToReflectionType(value));
+ onChanged();
return (BuilderType) this;
}
/** Clear an extension. */
public final <Type> BuilderType clearExtension(
final GeneratedExtension<MessageType, ?> extension) {
- final ExtendableMessage<MessageType> message = internalGetResult();
- message.verifyExtensionContainingType(extension);
- message.extensions.clearField(extension.getDescriptor());
+ verifyExtensionContainingType(extension);
+ ensureExtensionsIsMutable();
+ extensions.clearField(extension.getDescriptor());
+ onChanged();
return (BuilderType) this;
}
+ /** Called by subclasses to check if all extensions are initialized. */
+ protected boolean extensionsAreInitialized() {
+ return extensions.isInitialized();
+ }
+
+ /**
+ * Called by the build code path to create a copy of the extensions for
+ * building the message.
+ */
+ private FieldSet<FieldDescriptor> buildExtensions() {
+ extensions.makeImmutable();
+ return extensions;
+ }
+
+ @Override
+ public boolean isInitialized() {
+ return super.isInitialized() && extensionsAreInitialized();
+ }
+
/**
* Called by subclasses to parse an unknown field or an extension.
* @return {@code true} unless the tag is an end-group tag.
@@ -659,16 +979,73 @@ public abstract class GeneratedMessage extends AbstractMessage {
// ---------------------------------------------------------------
// Reflection
- // We don't have to override the get*() methods here because they already
- // just forward to the underlying message.
+ @Override
+ public Map<FieldDescriptor, Object> getAllFields() {
+ final Map<FieldDescriptor, Object> result = super.getAllFieldsMutable();
+ result.putAll(extensions.getAllFields());
+ return Collections.unmodifiableMap(result);
+ }
+
+ @Override
+ public Object getField(final FieldDescriptor field) {
+ if (field.isExtension()) {
+ verifyContainingType(field);
+ final Object value = extensions.getField(field);
+ if (value == null) {
+ if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
+ // Lacking an ExtensionRegistry, we have no way to determine the
+ // extension's real type, so we return a DynamicMessage.
+ return DynamicMessage.getDefaultInstance(field.getMessageType());
+ } else {
+ return field.getDefaultValue();
+ }
+ } else {
+ return value;
+ }
+ } else {
+ return super.getField(field);
+ }
+ }
+
+ @Override
+ public int getRepeatedFieldCount(final FieldDescriptor field) {
+ if (field.isExtension()) {
+ verifyContainingType(field);
+ return extensions.getRepeatedFieldCount(field);
+ } else {
+ return super.getRepeatedFieldCount(field);
+ }
+ }
+
+ @Override
+ public Object getRepeatedField(final FieldDescriptor field,
+ final int index) {
+ if (field.isExtension()) {
+ verifyContainingType(field);
+ return extensions.getRepeatedField(field, index);
+ } else {
+ return super.getRepeatedField(field, index);
+ }
+ }
+
+ @Override
+ public boolean hasField(final FieldDescriptor field) {
+ if (field.isExtension()) {
+ verifyContainingType(field);
+ return extensions.hasField(field);
+ } else {
+ return super.hasField(field);
+ }
+ }
@Override
public BuilderType setField(final FieldDescriptor field,
final Object value) {
if (field.isExtension()) {
- final ExtendableMessage<MessageType> message = internalGetResult();
- message.verifyContainingType(field);
- message.extensions.setField(field, value);
+ verifyContainingType(field);
+ ensureExtensionsIsMutable();
+ extensions.setField(field, value);
+ onChanged();
return (BuilderType) this;
} else {
return super.setField(field, value);
@@ -678,9 +1055,10 @@ public abstract class GeneratedMessage extends AbstractMessage {
@Override
public BuilderType clearField(final FieldDescriptor field) {
if (field.isExtension()) {
- final ExtendableMessage<MessageType> message = internalGetResult();
- message.verifyContainingType(field);
- message.extensions.clearField(field);
+ verifyContainingType(field);
+ ensureExtensionsIsMutable();
+ extensions.clearField(field);
+ onChanged();
return (BuilderType) this;
} else {
return super.clearField(field);
@@ -691,9 +1069,10 @@ public abstract class GeneratedMessage extends AbstractMessage {
public BuilderType setRepeatedField(final FieldDescriptor field,
final int index, final Object value) {
if (field.isExtension()) {
- final ExtendableMessage<MessageType> message = internalGetResult();
- message.verifyContainingType(field);
- message.extensions.setRepeatedField(field, index, value);
+ verifyContainingType(field);
+ ensureExtensionsIsMutable();
+ extensions.setRepeatedField(field, index, value);
+ onChanged();
return (BuilderType) this;
} else {
return super.setRepeatedField(field, index, value);
@@ -704,9 +1083,10 @@ public abstract class GeneratedMessage extends AbstractMessage {
public BuilderType addRepeatedField(final FieldDescriptor field,
final Object value) {
if (field.isExtension()) {
- final ExtendableMessage<MessageType> message = internalGetResult();
- message.verifyContainingType(field);
- message.extensions.addRepeatedField(field, value);
+ verifyContainingType(field);
+ ensureExtensionsIsMutable();
+ extensions.addRepeatedField(field, value);
+ onChanged();
return (BuilderType) this;
} else {
return super.addRepeatedField(field, value);
@@ -714,17 +1094,63 @@ public abstract class GeneratedMessage extends AbstractMessage {
}
protected final void mergeExtensionFields(final ExtendableMessage other) {
- internalGetResult().extensions.mergeFrom(other.extensions);
+ ensureExtensionsIsMutable();
+ extensions.mergeFrom(other.extensions);
+ onChanged();
+ }
+
+ private void verifyContainingType(final FieldDescriptor field) {
+ if (field.getContainingType() != getDescriptorForType()) {
+ throw new IllegalArgumentException(
+ "FieldDescriptor does not match message type.");
+ }
}
}
// -----------------------------------------------------------------
+ /**
+ * Gets the descriptor for an extension. The implementation depends on whether
+ * the extension is scoped in the top level of a file or scoped in a Message.
+ */
+ private static interface ExtensionDescriptorRetriever {
+ FieldDescriptor getDescriptor();
+ }
+
/** For use by generated code only. */
public static <ContainingType extends Message, Type>
GeneratedExtension<ContainingType, Type>
- newGeneratedExtension() {
- return new GeneratedExtension<ContainingType, Type>();
+ newMessageScopedGeneratedExtension(final Message scope,
+ final int descriptorIndex,
+ final Class singularType,
+ final Message defaultInstance) {
+ // For extensions scoped within a Message, we use the Message to resolve
+ // the outer class's descriptor, from which the extension descriptor is
+ // obtained.
+ return new GeneratedExtension<ContainingType, Type>(
+ new ExtensionDescriptorRetriever() {
+ @Override
+ public FieldDescriptor getDescriptor() {
+ return scope.getDescriptorForType().getExtensions()
+ .get(descriptorIndex);
+ }
+ },
+ singularType,
+ defaultInstance);
+ }
+
+ /** For use by generated code only. */
+ public static <ContainingType extends Message, Type>
+ GeneratedExtension<ContainingType, Type>
+ newFileScopedGeneratedExtension(final Class singularType,
+ final Message defaultInstance) {
+ // For extensions scoped within a file, we rely on the outer class's
+ // static initializer to call internalInit() on the extension when the
+ // descriptor is available.
+ return new GeneratedExtension<ContainingType, Type>(
+ null, // ExtensionDescriptorRetriever is initialized in internalInit();
+ singularType,
+ defaultInstance);
}
/**
@@ -757,64 +1183,67 @@ public abstract class GeneratedMessage extends AbstractMessage {
// TODO(kenton): Find ways to avoid using Java reflection within this
// class. Also try to avoid suppressing unchecked warnings.
- // We can't always initialize a GeneratedExtension when we first construct
- // it due to initialization order difficulties (namely, the descriptor may
- // not have been constructed yet, since it is often constructed by the
- // initializer of a separate module). So, we construct an uninitialized
- // GeneratedExtension once, then call internalInit() on it later. Generated
- // code will always call internalInit() on all extensions as part of the
- // static initialization code, and internalInit() throws an exception if
- // called more than once, so this method is useless to users.
- private GeneratedExtension() {}
-
- /** For use by generated code only. */
- public void internalInit(final FieldDescriptor descriptor,
- final Class<?> type) {
- if (this.descriptor != null) {
- throw new IllegalStateException("Already initialized.");
- }
-
- if (!descriptor.isExtension()) {
+ // We can't always initialize the descriptor of a GeneratedExtension when
+ // we first construct it due to initialization order difficulties (namely,
+ // the descriptor may not have been constructed yet, since it is often
+ // constructed by the initializer of a separate module).
+ //
+ // In the case of nested extensions, we initialize the
+ // ExtensionDescriptorRetriever with an instance that uses the scoping
+ // Message's default instance to retrieve the extension's descriptor.
+ //
+ // In the case of non-nested extensions, we initialize the
+ // ExtensionDescriptorRetriever to null and rely on the outer class's static
+ // initializer to call internalInit() after the descriptor has been parsed.
+ private GeneratedExtension(ExtensionDescriptorRetriever descriptorRetriever,
+ Class singularType,
+ Message messageDefaultInstance) {
+ if (Message.class.isAssignableFrom(singularType) &&
+ !singularType.isInstance(messageDefaultInstance)) {
throw new IllegalArgumentException(
- "GeneratedExtension given a regular (non-extension) field.");
+ "Bad messageDefaultInstance for " + singularType.getName());
}
+ this.descriptorRetriever = descriptorRetriever;
+ this.singularType = singularType;
+ this.messageDefaultInstance = messageDefaultInstance;
- this.descriptor = descriptor;
- this.type = type;
+ if (ProtocolMessageEnum.class.isAssignableFrom(singularType)) {
+ this.enumValueOf = getMethodOrDie(singularType, "valueOf",
+ EnumValueDescriptor.class);
+ this.enumGetValueDescriptor =
+ getMethodOrDie(singularType, "getValueDescriptor");
+ } else {
+ this.enumValueOf = null;
+ this.enumGetValueDescriptor = null;
+ }
+ }
- switch (descriptor.getJavaType()) {
- case MESSAGE:
- enumValueOf = null;
- enumGetValueDescriptor = null;
- messageDefaultInstance =
- (Message) invokeOrDie(getMethodOrDie(type, "getDefaultInstance"),
- null);
- if (messageDefaultInstance == null) {
- throw new IllegalStateException(
- type.getName() + ".getDefaultInstance() returned 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;
+ /** For use by generated code only. */
+ public void internalInit(final FieldDescriptor descriptor) {
+ if (descriptorRetriever != null) {
+ throw new IllegalStateException("Already initialized.");
}
+ descriptorRetriever = new ExtensionDescriptorRetriever() {
+ @Override
+ public FieldDescriptor getDescriptor() {
+ return descriptor;
+ }
+ };
}
- private FieldDescriptor descriptor;
- private Class<?> type;
- private Method enumValueOf;
- private Method enumGetValueDescriptor;
- private Message messageDefaultInstance;
+ private ExtensionDescriptorRetriever descriptorRetriever;
+ private final Class singularType;
+ private final Message messageDefaultInstance;
+ private final Method enumValueOf;
+ private final Method enumGetValueDescriptor;
- public FieldDescriptor getDescriptor() { return descriptor; }
+ public FieldDescriptor getDescriptor() {
+ if (descriptorRetriever == null) {
+ throw new IllegalStateException(
+ "getDescriptor() called before internalInit()");
+ }
+ return descriptorRetriever.getDescriptor();
+ }
/**
* If the extension is an embedded message or group, returns the default
@@ -832,6 +1261,7 @@ public abstract class GeneratedMessage extends AbstractMessage {
*/
@SuppressWarnings("unchecked")
private Object fromReflectionType(final Object value) {
+ FieldDescriptor descriptor = getDescriptor();
if (descriptor.isRepeated()) {
if (descriptor.getJavaType() == FieldDescriptor.JavaType.MESSAGE ||
descriptor.getJavaType() == FieldDescriptor.JavaType.ENUM) {
@@ -854,9 +1284,10 @@ public abstract class GeneratedMessage extends AbstractMessage {
* type, this converts a single element.
*/
private Object singularFromReflectionType(final Object value) {
+ FieldDescriptor descriptor = getDescriptor();
switch (descriptor.getJavaType()) {
case MESSAGE:
- if (type.isInstance(value)) {
+ if (singularType.isInstance(value)) {
return value;
} else {
// It seems the copy of the embedded message stored inside the
@@ -883,6 +1314,7 @@ public abstract class GeneratedMessage extends AbstractMessage {
*/
@SuppressWarnings("unchecked")
private Object toReflectionType(final Object value) {
+ FieldDescriptor descriptor = getDescriptor();
if (descriptor.isRepeated()) {
if (descriptor.getJavaType() == FieldDescriptor.JavaType.ENUM) {
// Must convert the whole list.
@@ -904,6 +1336,7 @@ public abstract class GeneratedMessage extends AbstractMessage {
* type, this converts a single element.
*/
private Object singularToReflectionType(final Object value) {
+ FieldDescriptor descriptor = getDescriptor();
switch (descriptor.getJavaType()) {
case ENUM:
return invokeOrDie(enumGetValueDescriptor, value);
@@ -1025,13 +1458,17 @@ public abstract class GeneratedMessage extends AbstractMessage {
*/
private interface FieldAccessor {
Object get(GeneratedMessage message);
+ Object get(GeneratedMessage.Builder builder);
void set(Builder builder, Object value);
Object getRepeated(GeneratedMessage message, int index);
+ Object getRepeated(GeneratedMessage.Builder builder, int index);
void setRepeated(Builder builder,
int index, Object value);
void addRepeated(Builder builder, Object value);
boolean has(GeneratedMessage message);
+ boolean has(GeneratedMessage.Builder builder);
int getRepeatedCount(GeneratedMessage message);
+ int getRepeatedCount(GeneratedMessage.Builder builder);
void clear(Builder builder);
Message.Builder newBuilder();
}
@@ -1044,10 +1481,13 @@ public abstract class GeneratedMessage extends AbstractMessage {
final Class<? extends GeneratedMessage> messageClass,
final Class<? extends Builder> builderClass) {
getMethod = getMethodOrDie(messageClass, "get" + camelCaseName);
+ getMethodBuilder = getMethodOrDie(builderClass, "get" + camelCaseName);
type = getMethod.getReturnType();
setMethod = getMethodOrDie(builderClass, "set" + camelCaseName, type);
hasMethod =
- getMethodOrDie(messageClass, "has" + camelCaseName);
+ getMethodOrDie(messageClass, "has" + camelCaseName);
+ hasMethodBuilder =
+ getMethodOrDie(builderClass, "has" + camelCaseName);
clearMethod = getMethodOrDie(builderClass, "clear" + camelCaseName);
}
@@ -1056,13 +1496,18 @@ public abstract class GeneratedMessage extends AbstractMessage {
// checks.
protected final Class<?> type;
protected final Method getMethod;
+ protected final Method getMethodBuilder;
protected final Method setMethod;
protected final Method hasMethod;
+ protected final Method hasMethodBuilder;
protected final Method clearMethod;
public Object get(final GeneratedMessage message) {
return invokeOrDie(getMethod, message);
}
+ public Object get(GeneratedMessage.Builder builder) {
+ return invokeOrDie(getMethodBuilder, builder);
+ }
public void set(final Builder builder, final Object value) {
invokeOrDie(setMethod, builder, value);
}
@@ -1071,6 +1516,10 @@ public abstract class GeneratedMessage extends AbstractMessage {
throw new UnsupportedOperationException(
"getRepeatedField() called on a singular field.");
}
+ public Object getRepeated(GeneratedMessage.Builder builder, int index) {
+ throw new UnsupportedOperationException(
+ "getRepeatedField() called on a singular field.");
+ }
public void setRepeated(final Builder builder,
final int index, final Object value) {
throw new UnsupportedOperationException(
@@ -1083,10 +1532,17 @@ public abstract class GeneratedMessage extends AbstractMessage {
public boolean has(final GeneratedMessage message) {
return (Boolean) invokeOrDie(hasMethod, message);
}
+ public boolean has(GeneratedMessage.Builder builder) {
+ return (Boolean) invokeOrDie(hasMethodBuilder, builder);
+ }
public int getRepeatedCount(final GeneratedMessage message) {
throw new UnsupportedOperationException(
"getRepeatedFieldSize() called on a singular field.");
}
+ public int getRepeatedCount(GeneratedMessage.Builder builder) {
+ throw new UnsupportedOperationException(
+ "getRepeatedFieldSize() called on a singular field.");
+ }
public void clear(final Builder builder) {
invokeOrDie(clearMethod, builder);
}
@@ -1097,38 +1553,51 @@ public abstract class GeneratedMessage extends AbstractMessage {
}
private static class RepeatedFieldAccessor implements FieldAccessor {
+ protected final Class type;
+ protected final Method getMethod;
+ protected final Method getMethodBuilder;
+ protected final Method getRepeatedMethod;
+ protected final Method getRepeatedMethodBuilder;
+ protected final Method setRepeatedMethod;
+ protected final Method addRepeatedMethod;
+ protected final Method getCountMethod;
+ protected final Method getCountMethodBuilder;
+ protected final Method clearMethod;
+
RepeatedFieldAccessor(
final FieldDescriptor descriptor, final String camelCaseName,
final Class<? extends GeneratedMessage> messageClass,
final Class<? extends Builder> builderClass) {
getMethod = getMethodOrDie(messageClass,
"get" + camelCaseName + "List");
+ getMethodBuilder = getMethodOrDie(builderClass,
+ "get" + camelCaseName + "List");
+
getRepeatedMethod =
- getMethodOrDie(messageClass, "get" + camelCaseName, Integer.TYPE);
+ getMethodOrDie(messageClass, "get" + camelCaseName, Integer.TYPE);
+ getRepeatedMethodBuilder =
+ getMethodOrDie(builderClass, "get" + camelCaseName, Integer.TYPE);
type = getRepeatedMethod.getReturnType();
setRepeatedMethod =
- getMethodOrDie(builderClass, "set" + camelCaseName,
- Integer.TYPE, type);
+ getMethodOrDie(builderClass, "set" + camelCaseName,
+ Integer.TYPE, type);
addRepeatedMethod =
- getMethodOrDie(builderClass, "add" + camelCaseName, type);
+ getMethodOrDie(builderClass, "add" + camelCaseName, type);
getCountMethod =
- getMethodOrDie(messageClass, "get" + camelCaseName + "Count");
+ getMethodOrDie(messageClass, "get" + camelCaseName + "Count");
+ getCountMethodBuilder =
+ getMethodOrDie(builderClass, "get" + camelCaseName + "Count");
clearMethod = getMethodOrDie(builderClass, "clear" + camelCaseName);
}
- protected final Class<?> type;
- protected final Method getMethod;
- protected final Method getRepeatedMethod;
- protected final Method setRepeatedMethod;
- protected final Method addRepeatedMethod;
- protected final Method getCountMethod;
- protected final Method clearMethod;
-
public Object get(final GeneratedMessage message) {
return invokeOrDie(getMethod, message);
}
+ public Object get(GeneratedMessage.Builder builder) {
+ return invokeOrDie(getMethodBuilder, builder);
+ }
public void set(final Builder builder, final Object value) {
// Add all the elements individually. This serves two purposes:
// 1) Verifies that each element has the correct type.
@@ -1143,6 +1612,9 @@ public abstract class GeneratedMessage extends AbstractMessage {
final int index) {
return invokeOrDie(getRepeatedMethod, message, index);
}
+ public Object getRepeated(GeneratedMessage.Builder builder, int index) {
+ return invokeOrDie(getRepeatedMethodBuilder, builder, index);
+ }
public void setRepeated(final Builder builder,
final int index, final Object value) {
invokeOrDie(setRepeatedMethod, builder, index, value);
@@ -1154,9 +1626,16 @@ public abstract class GeneratedMessage extends AbstractMessage {
throw new UnsupportedOperationException(
"hasField() called on a singular field.");
}
+ public boolean has(GeneratedMessage.Builder builder) {
+ throw new UnsupportedOperationException(
+ "hasField() called on a singular field.");
+ }
public int getRepeatedCount(final GeneratedMessage message) {
return (Integer) invokeOrDie(getCountMethod, message);
}
+ public int getRepeatedCount(GeneratedMessage.Builder builder) {
+ return (Integer) invokeOrDie(getCountMethodBuilder, builder);
+ }
public void clear(final Builder builder) {
invokeOrDie(clearMethod, builder);
}
@@ -1189,6 +1668,12 @@ public abstract class GeneratedMessage extends AbstractMessage {
public Object get(final GeneratedMessage message) {
return invokeOrDie(getValueDescriptorMethod, super.get(message));
}
+
+ @Override
+ public Object get(final GeneratedMessage.Builder builder) {
+ return invokeOrDie(getValueDescriptorMethod, super.get(builder));
+ }
+
@Override
public void set(final Builder builder, final Object value) {
super.set(builder, invokeOrDie(valueOfMethod, null, value));
@@ -1221,6 +1706,17 @@ public abstract class GeneratedMessage extends AbstractMessage {
}
return Collections.unmodifiableList(newList);
}
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Object get(final GeneratedMessage.Builder builder) {
+ final List newList = new ArrayList();
+ for (final Object element : (List) super.get(builder)) {
+ newList.add(invokeOrDie(getValueDescriptorMethod, element));
+ }
+ return Collections.unmodifiableList(newList);
+ }
+
@Override
public Object getRepeated(final GeneratedMessage message,
final int index) {
@@ -1228,6 +1724,12 @@ public abstract class GeneratedMessage extends AbstractMessage {
super.getRepeated(message, index));
}
@Override
+ public Object getRepeated(final GeneratedMessage.Builder builder,
+ final int index) {
+ return invokeOrDie(getValueDescriptorMethod,
+ super.getRepeated(builder, index));
+ }
+ @Override
public void setRepeated(final Builder builder,
final int index, final Object value) {
super.setRepeated(builder, index, invokeOrDie(valueOfMethod, null,
@@ -1318,4 +1820,14 @@ public abstract class GeneratedMessage extends AbstractMessage {
}
}
}
+
+ /**
+ * Replaces this object in the output stream with a serialized form.
+ * Part of Java's serialization magic. Generated sub-classes must override
+ * this method by calling <code>return super.writeReplace();</code>
+ * @return a SerializedForm of this message
+ */
+ protected Object writeReplace() throws ObjectStreamException {
+ return new GeneratedMessageLite.SerializedForm(this);
+ }
}
diff --git a/java/src/main/java/com/google/protobuf/GeneratedMessageLite.java b/java/src/main/java/com/google/protobuf/GeneratedMessageLite.java
index 9a799b44..30a75715 100644
--- a/java/src/main/java/com/google/protobuf/GeneratedMessageLite.java
+++ b/java/src/main/java/com/google/protobuf/GeneratedMessageLite.java
@@ -31,6 +31,10 @@
package com.google.protobuf;
import java.io.IOException;
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
@@ -41,8 +45,14 @@ import java.util.Map;
*
* @author kenton@google.com Kenton Varda
*/
-public abstract class GeneratedMessageLite extends AbstractMessageLite {
- protected GeneratedMessageLite() {}
+public abstract class GeneratedMessageLite extends AbstractMessageLite
+ implements Serializable {
+
+ protected GeneratedMessageLite() {
+ }
+
+ protected GeneratedMessageLite(Builder builder) {
+ }
@SuppressWarnings("unchecked")
public abstract static class Builder<MessageType extends GeneratedMessageLite,
@@ -50,6 +60,11 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite {
extends AbstractMessageLite.Builder<BuilderType> {
protected Builder() {}
+ //@Override (Java 1.6 override semantics, but we must support 1.5)
+ public BuilderType clear() {
+ return (BuilderType) this;
+ }
+
// This is implemented here only to work around an apparent bug in the
// Java compiler and/or build system. See bug #1898463. The mere presence
// of this dummy clone() implementation makes it go away.
@@ -66,12 +81,6 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite {
public abstract MessageType getDefaultInstanceForType();
/**
- * Get the message being built. We don't just pass this to the
- * constructor because it becomes null when build() is called.
- */
- protected abstract MessageType internalGetResult();
-
- /**
* Called by subclasses to parse an unknown field.
* @return {@code true} unless the tag is an end-group tag.
*/
@@ -87,14 +96,45 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite {
// Extensions-related stuff
/**
+ * Lite equivalent of {@link com.google.protobuf.GeneratedMessage.ExtendableMessageOrBuilder}.
+ */
+ public interface ExtendableMessageOrBuilder<
+ MessageType extends ExtendableMessage> extends MessageLiteOrBuilder {
+
+ /** Check if a singular extension is present. */
+ <Type> boolean hasExtension(
+ GeneratedExtension<MessageType, Type> extension);
+
+ /** Get the number of elements in a repeated extension. */
+ <Type> int getExtensionCount(
+ GeneratedExtension<MessageType, List<Type>> extension);
+
+ /** Get the value of an extension. */
+ <Type> Type getExtension(GeneratedExtension<MessageType, Type> extension);
+
+ /** Get one element of a repeated extension. */
+ <Type> Type getExtension(
+ GeneratedExtension<MessageType, List<Type>> extension,
+ int index);
+ }
+
+ /**
* Lite equivalent of {@link GeneratedMessage.ExtendableMessage}.
*/
public abstract static class ExtendableMessage<
MessageType extends ExtendableMessage<MessageType>>
- extends GeneratedMessageLite {
- protected ExtendableMessage() {}
- private final FieldSet<ExtensionDescriptor> extensions =
- FieldSet.newFieldSet();
+ extends GeneratedMessageLite
+ implements ExtendableMessageOrBuilder<MessageType> {
+
+ private final FieldSet<ExtensionDescriptor> extensions;
+
+ protected ExtendableMessage() {
+ this.extensions = FieldSet.newFieldSet();
+ }
+
+ protected ExtendableMessage(ExtendableBuilder<MessageType, ?> builder) {
+ this.extensions = builder.buildExtensions();
+ }
private void verifyExtensionContainingType(
final GeneratedExtension<MessageType, ?> extension) {
@@ -108,13 +148,15 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite {
}
/** Check if a singular extension is present. */
- public final boolean hasExtension(
- final GeneratedExtension<MessageType, ?> extension) {
+ //@Override (Java 1.6 override semantics, but we must support 1.5)
+ public final <Type> boolean hasExtension(
+ final GeneratedExtension<MessageType, Type> extension) {
verifyExtensionContainingType(extension);
return extensions.hasField(extension.descriptor);
}
/** Get the number of elements in a repeated extension. */
+ //@Override (Java 1.6 override semantics, but we must support 1.5)
public final <Type> int getExtensionCount(
final GeneratedExtension<MessageType, List<Type>> extension) {
verifyExtensionContainingType(extension);
@@ -122,6 +164,7 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite {
}
/** Get the value of an extension. */
+ //@Override (Java 1.6 override semantics, but we must support 1.5)
@SuppressWarnings("unchecked")
public final <Type> Type getExtension(
final GeneratedExtension<MessageType, Type> extension) {
@@ -135,6 +178,7 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite {
}
/** Get one element of a repeated extension. */
+ //@Override (Java 1.6 override semantics, but we must support 1.5)
@SuppressWarnings("unchecked")
public final <Type> Type getExtension(
final GeneratedExtension<MessageType, List<Type>> extension,
@@ -214,53 +258,104 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite {
public abstract static class ExtendableBuilder<
MessageType extends ExtendableMessage<MessageType>,
BuilderType extends ExtendableBuilder<MessageType, BuilderType>>
- extends Builder<MessageType, BuilderType> {
+ extends Builder<MessageType, BuilderType>
+ implements ExtendableMessageOrBuilder<MessageType> {
protected ExtendableBuilder() {}
- // This is implemented here only to work around an apparent bug in the
- // Java compiler and/or build system. See bug #1898463. The mere presence
- // of this dummy clone() implementation makes it go away.
+ private FieldSet<ExtensionDescriptor> extensions = FieldSet.emptySet();
+ private boolean extensionsIsMutable;
+
@Override
- public BuilderType clone() {
- throw new UnsupportedOperationException(
- "This is supposed to be overridden by subclasses.");
+ public BuilderType clear() {
+ extensions.clear();
+ extensionsIsMutable = false;
+ return super.clear();
}
- @Override
- protected abstract MessageType internalGetResult();
+ private void ensureExtensionsIsMutable() {
+ if (!extensionsIsMutable) {
+ extensions = extensions.clone();
+ extensionsIsMutable = true;
+ }
+ }
- /** Check if a singular extension is present. */
- public final boolean hasExtension(
+ /**
+ * Called by the build code path to create a copy of the extensions for
+ * building the message.
+ */
+ private FieldSet<ExtensionDescriptor> buildExtensions() {
+ extensions.makeImmutable();
+ extensionsIsMutable = false;
+ return extensions;
+ }
+
+ private void verifyExtensionContainingType(
final GeneratedExtension<MessageType, ?> extension) {
- return internalGetResult().hasExtension(extension);
+ if (extension.getContainingTypeDefaultInstance() !=
+ getDefaultInstanceForType()) {
+ // This can only happen if someone uses unchecked operations.
+ throw new IllegalArgumentException(
+ "This extension is for a different message type. Please make " +
+ "sure that you are not suppressing any generics type warnings.");
+ }
+ }
+
+ /** Check if a singular extension is present. */
+ //@Override (Java 1.6 override semantics, but we must support 1.5)
+ public final <Type> boolean hasExtension(
+ final GeneratedExtension<MessageType, Type> extension) {
+ verifyExtensionContainingType(extension);
+ return extensions.hasField(extension.descriptor);
}
/** Get the number of elements in a repeated extension. */
+ //@Override (Java 1.6 override semantics, but we must support 1.5)
public final <Type> int getExtensionCount(
final GeneratedExtension<MessageType, List<Type>> extension) {
- return internalGetResult().getExtensionCount(extension);
+ verifyExtensionContainingType(extension);
+ return extensions.getRepeatedFieldCount(extension.descriptor);
}
/** Get the value of an extension. */
+ //@Override (Java 1.6 override semantics, but we must support 1.5)
+ @SuppressWarnings("unchecked")
public final <Type> Type getExtension(
final GeneratedExtension<MessageType, Type> extension) {
- return internalGetResult().getExtension(extension);
+ verifyExtensionContainingType(extension);
+ final Object value = extensions.getField(extension.descriptor);
+ if (value == null) {
+ return extension.defaultValue;
+ } else {
+ return (Type) value;
+ }
}
/** Get one element of a repeated extension. */
+ @SuppressWarnings("unchecked")
+ //@Override (Java 1.6 override semantics, but we must support 1.5)
public final <Type> Type getExtension(
final GeneratedExtension<MessageType, List<Type>> extension,
final int index) {
- return internalGetResult().getExtension(extension, index);
+ verifyExtensionContainingType(extension);
+ return (Type) extensions.getRepeatedField(extension.descriptor, index);
+ }
+
+ // This is implemented here only to work around an apparent bug in the
+ // Java compiler and/or build system. See bug #1898463. The mere presence
+ // of this dummy clone() implementation makes it go away.
+ @Override
+ public BuilderType clone() {
+ throw new UnsupportedOperationException(
+ "This is supposed to be overridden by subclasses.");
}
/** Set the value of an extension. */
public final <Type> BuilderType setExtension(
final GeneratedExtension<MessageType, Type> extension,
final Type value) {
- final ExtendableMessage<MessageType> message = internalGetResult();
- message.verifyExtensionContainingType(extension);
- message.extensions.setField(extension.descriptor, value);
+ verifyExtensionContainingType(extension);
+ ensureExtensionsIsMutable();
+ extensions.setField(extension.descriptor, value);
return (BuilderType) this;
}
@@ -268,9 +363,9 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite {
public final <Type> BuilderType setExtension(
final GeneratedExtension<MessageType, List<Type>> extension,
final int index, final Type value) {
- final ExtendableMessage<MessageType> message = internalGetResult();
- message.verifyExtensionContainingType(extension);
- message.extensions.setRepeatedField(extension.descriptor, index, value);
+ verifyExtensionContainingType(extension);
+ ensureExtensionsIsMutable();
+ extensions.setRepeatedField(extension.descriptor, index, value);
return (BuilderType) this;
}
@@ -278,21 +373,26 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite {
public final <Type> BuilderType addExtension(
final GeneratedExtension<MessageType, List<Type>> extension,
final Type value) {
- final ExtendableMessage<MessageType> message = internalGetResult();
- message.verifyExtensionContainingType(extension);
- message.extensions.addRepeatedField(extension.descriptor, value);
+ verifyExtensionContainingType(extension);
+ ensureExtensionsIsMutable();
+ extensions.addRepeatedField(extension.descriptor, value);
return (BuilderType) this;
}
/** Clear an extension. */
public final <Type> BuilderType clearExtension(
final GeneratedExtension<MessageType, ?> extension) {
- final ExtendableMessage<MessageType> message = internalGetResult();
- message.verifyExtensionContainingType(extension);
- message.extensions.clearField(extension.descriptor);
+ verifyExtensionContainingType(extension);
+ ensureExtensionsIsMutable();
+ extensions.clearField(extension.descriptor);
return (BuilderType) this;
}
+ /** Called by subclasses to check if all extensions are initialized. */
+ protected boolean extensionsAreInitialized() {
+ return extensions.isInitialized();
+ }
+
/**
* Called by subclasses to parse an unknown field or an extension.
* @return {@code true} unless the tag is an end-group tag.
@@ -302,9 +402,6 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite {
final CodedInputStream input,
final ExtensionRegistryLite extensionRegistry,
final int tag) throws IOException {
- final FieldSet<ExtensionDescriptor> extensions =
- ((ExtendableMessage) internalGetResult()).extensions;
-
final int wireType = WireFormat.getTagWireType(tag);
final int fieldNumber = WireFormat.getTagFieldNumber(tag);
@@ -347,6 +444,7 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite {
// enum, drop it (don't even add it to unknownFields).
return true;
}
+ ensureExtensionsIsMutable();
extensions.addRepeatedField(extension.descriptor, value);
}
} else {
@@ -354,6 +452,7 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite {
final Object value =
FieldSet.readPrimitiveField(input,
extension.descriptor.getLiteType());
+ ensureExtensionsIsMutable();
extensions.addRepeatedField(extension.descriptor, value);
}
}
@@ -400,8 +499,10 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite {
}
if (extension.descriptor.isRepeated()) {
+ ensureExtensionsIsMutable();
extensions.addRepeatedField(extension.descriptor, value);
} else {
+ ensureExtensionsIsMutable();
extensions.setField(extension.descriptor, value);
}
}
@@ -410,8 +511,8 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite {
}
protected final void mergeExtensionFields(final MessageType other) {
- ((ExtendableMessage) internalGetResult()).extensions.mergeFrom(
- ((ExtendableMessage) other).extensions);
+ ensureExtensionsIsMutable();
+ extensions.mergeFrom(((ExtendableMessage) other).extensions);
}
}
@@ -420,8 +521,40 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite {
/** For use by generated code only. */
public static <ContainingType extends MessageLite, Type>
GeneratedExtension<ContainingType, Type>
- newGeneratedExtension() {
- return new GeneratedExtension<ContainingType, Type>();
+ newSingularGeneratedExtension(
+ final ContainingType containingTypeDefaultInstance,
+ final Type defaultValue,
+ final MessageLite messageDefaultInstance,
+ final Internal.EnumLiteMap<?> enumTypeMap,
+ final int number,
+ final WireFormat.FieldType type) {
+ return new GeneratedExtension<ContainingType, Type>(
+ containingTypeDefaultInstance,
+ defaultValue,
+ messageDefaultInstance,
+ new ExtensionDescriptor(enumTypeMap, number, type,
+ false /* isRepeated */,
+ false /* isPacked */));
+ }
+
+ /** For use by generated code only. */
+ public static <ContainingType extends MessageLite, Type>
+ GeneratedExtension<ContainingType, Type>
+ newRepeatedGeneratedExtension(
+ final ContainingType containingTypeDefaultInstance,
+ final MessageLite messageDefaultInstance,
+ final Internal.EnumLiteMap<?> enumTypeMap,
+ final int number,
+ final WireFormat.FieldType type,
+ final boolean isPacked) {
+ @SuppressWarnings("unchecked") // Subclasses ensure Type is a List
+ Type emptyList = (Type) Collections.emptyList();
+ return new GeneratedExtension<ContainingType, Type>(
+ containingTypeDefaultInstance,
+ emptyList,
+ messageDefaultInstance,
+ new ExtensionDescriptor(
+ enumTypeMap, number, type, true /* isRepeated */, isPacked));
}
private static final class ExtensionDescriptor
@@ -489,60 +622,33 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite {
*/
public static final class GeneratedExtension<
ContainingType extends MessageLite, Type> {
- // We can't always initialize a GeneratedExtension when we first construct
- // it due to initialization order difficulties (namely, the default
- // instances may not have been constructed yet). So, we construct an
- // uninitialized GeneratedExtension once, then call internalInit() on it
- // later. Generated code will always call internalInit() on all extensions
- // as part of the static initialization code, and internalInit() throws an
- // exception if called more than once, so this method is useless to users.
- private GeneratedExtension() {}
-
- private void internalInit(
+
+ private GeneratedExtension(
final ContainingType containingTypeDefaultInstance,
final Type defaultValue,
final MessageLite messageDefaultInstance,
final ExtensionDescriptor descriptor) {
+ // Defensive checks to verify the correct initialization order of
+ // GeneratedExtensions and their related GeneratedMessages.
+ if (containingTypeDefaultInstance == null) {
+ throw new IllegalArgumentException(
+ "Null containingTypeDefaultInstance");
+ }
+ if (descriptor.getLiteType() == WireFormat.FieldType.MESSAGE &&
+ messageDefaultInstance == null) {
+ throw new IllegalArgumentException(
+ "Null messageDefaultInstance");
+ }
this.containingTypeDefaultInstance = containingTypeDefaultInstance;
this.defaultValue = defaultValue;
this.messageDefaultInstance = messageDefaultInstance;
this.descriptor = descriptor;
}
- /** For use by generated code only. */
- public void internalInitSingular(
- final ContainingType containingTypeDefaultInstance,
- final Type defaultValue,
- final MessageLite messageDefaultInstance,
- final Internal.EnumLiteMap<?> enumTypeMap,
- final int number,
- final WireFormat.FieldType type) {
- internalInit(
- containingTypeDefaultInstance, defaultValue, messageDefaultInstance,
- new ExtensionDescriptor(enumTypeMap, number, type,
- false /* isRepeated */, false /* isPacked */));
- }
-
- /** For use by generated code only. */
- @SuppressWarnings("unchecked")
- public void internalInitRepeated(
- final ContainingType containingTypeDefaultInstance,
- final MessageLite messageDefaultInstance,
- final Internal.EnumLiteMap<?> enumTypeMap,
- final int number,
- final WireFormat.FieldType type,
- final boolean isPacked) {
- internalInit(
- containingTypeDefaultInstance, (Type) Collections.emptyList(),
- messageDefaultInstance,
- new ExtensionDescriptor(
- enumTypeMap, number, type, true /* isRepeated */, isPacked));
- }
-
- private ContainingType containingTypeDefaultInstance;
- private Type defaultValue;
- private MessageLite messageDefaultInstance;
- private ExtensionDescriptor descriptor;
+ private final ContainingType containingTypeDefaultInstance;
+ private final Type defaultValue;
+ private final MessageLite messageDefaultInstance;
+ private final ExtensionDescriptor descriptor;
/**
* Default instance of the type being extended, used to identify that type.
@@ -564,4 +670,61 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite {
return messageDefaultInstance;
}
}
+
+ /**
+ * A serialized (serializable) form of the generated message. Stores the
+ * message as a class name and a byte array.
+ */
+ static final class SerializedForm implements Serializable {
+ private static final long serialVersionUID = 0L;
+
+ private String messageClassName;
+ private byte[] asBytes;
+
+ /**
+ * Creates the serialized form by calling {@link com.google.protobuf.MessageLite#toByteArray}.
+ * @param regularForm the message to serialize
+ */
+ SerializedForm(MessageLite regularForm) {
+ messageClassName = regularForm.getClass().getName();
+ asBytes = regularForm.toByteArray();
+ }
+
+ /**
+ * When read from an ObjectInputStream, this method converts this object
+ * back to the regular form. Part of Java's serialization magic.
+ * @return a GeneratedMessage of the type that was serialized
+ */
+ @SuppressWarnings("unchecked")
+ protected Object readResolve() throws ObjectStreamException {
+ try {
+ Class messageClass = Class.forName(messageClassName);
+ Method newBuilder = messageClass.getMethod("newBuilder");
+ MessageLite.Builder builder =
+ (MessageLite.Builder) newBuilder.invoke(null);
+ builder.mergeFrom(asBytes);
+ return builder.buildPartial();
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException("Unable to find proto buffer class", e);
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException("Unable to find newBuilder method", e);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException("Unable to call newBuilder method", e);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException("Error calling newBuilder", e.getCause());
+ } catch (InvalidProtocolBufferException e) {
+ throw new RuntimeException("Unable to understand proto buffer", e);
+ }
+ }
+ }
+
+ /**
+ * Replaces this object in the output stream with a serialized form.
+ * Part of Java's serialization magic. Generated sub-classes must override
+ * this method by calling <code>return super.writeReplace();</code>
+ * @return a SerializedForm of this message
+ */
+ protected Object writeReplace() throws ObjectStreamException {
+ return new SerializedForm(this);
+ }
}
diff --git a/java/src/main/java/com/google/protobuf/Internal.java b/java/src/main/java/com/google/protobuf/Internal.java
index 965465e1..05eab57a 100644
--- a/java/src/main/java/com/google/protobuf/Internal.java
+++ b/java/src/main/java/com/google/protobuf/Internal.java
@@ -100,6 +100,91 @@ public class Internal {
}
/**
+ * Helper called by generated code to determine if a byte array is a valid
+ * UTF-8 encoded string such that the original bytes can be converted to
+ * a String object and then back to a byte array round tripping the bytes
+ * without loss.
+ * <p>
+ * This is inspired by UTF_8.java in sun.nio.cs.
+ *
+ * @param byteString the string to check
+ * @return whether the byte array is round trippable
+ */
+ public static boolean isValidUtf8(ByteString byteString) {
+ int index = 0;
+ int size = byteString.size();
+ // To avoid the masking, we could change this to use bytes;
+ // Then X > 0xC2 gets turned into X < -0xC2; X < 0x80
+ // gets turned into X >= 0, etc.
+
+ while (index < size) {
+ int byte1 = byteString.byteAt(index++) & 0xFF;
+ if (byte1 < 0x80) {
+ // fast loop for single bytes
+ continue;
+
+ // we know from this point on that we have 2-4 byte forms
+ } else if (byte1 < 0xC2 || byte1 > 0xF4) {
+ // catch illegal first bytes: < C2 or > F4
+ return false;
+ }
+ if (index >= size) {
+ // fail if we run out of bytes
+ return false;
+ }
+ int byte2 = byteString.byteAt(index++) & 0xFF;
+ if (byte2 < 0x80 || byte2 > 0xBF) {
+ // general trail-byte test
+ return false;
+ }
+ if (byte1 <= 0xDF) {
+ // two-byte form; general trail-byte test is sufficient
+ continue;
+ }
+
+ // we know from this point on that we have 3 or 4 byte forms
+ if (index >= size) {
+ // fail if we run out of bytes
+ return false;
+ }
+ int byte3 = byteString.byteAt(index++) & 0xFF;
+ if (byte3 < 0x80 || byte3 > 0xBF) {
+ // general trail-byte test
+ return false;
+ }
+ if (byte1 <= 0xEF) {
+ // three-byte form. Vastly more frequent than four-byte forms
+ // The following has an extra test, but not worth restructuring
+ if (byte1 == 0xE0 && byte2 < 0xA0 ||
+ byte1 == 0xED && byte2 > 0x9F) {
+ // check special cases of byte2
+ return false;
+ }
+
+ } else {
+ // four-byte form
+
+ if (index >= size) {
+ // fail if we run out of bytes
+ return false;
+ }
+ int byte4 = byteString.byteAt(index++) & 0xFF;
+ if (byte4 < 0x80 || byte4 > 0xBF) {
+ // general trail-byte test
+ return false;
+ }
+ // The following has an extra test, but not worth restructuring
+ if (byte1 == 0xF0 && byte2 < 0x90 ||
+ byte1 == 0xF4 && byte2 > 0x8F) {
+ // check special cases of byte2
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
* Interface for an enum value or value descriptor, to be used in FieldSet.
* The lite library stores enum values directly in FieldSets but the full
* library stores EnumValueDescriptors in order to better support reflection.
diff --git a/java/src/main/java/com/google/protobuf/LazyStringArrayList.java b/java/src/main/java/com/google/protobuf/LazyStringArrayList.java
new file mode 100644
index 00000000..1683a640
--- /dev/null
+++ b/java/src/main/java/com/google/protobuf/LazyStringArrayList.java
@@ -0,0 +1,155 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// http://code.google.com/p/protobuf/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import java.util.List;
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.RandomAccess;
+import java.util.Collection;
+
+/**
+ * An implementation of {@link LazyStringList} that wraps an ArrayList. Each
+ * element is either a ByteString or a String. It caches the last one requested
+ * which is most likely the one needed next. This minimizes memory usage while
+ * satisfying the most common use cases.
+ * <p>
+ * <strong>Note that this implementation is not synchronized.</strong>
+ * If multiple threads access an <tt>ArrayList</tt> instance concurrently,
+ * and at least one of the threads modifies the list structurally, it
+ * <i>must</i> be synchronized externally. (A structural modification is
+ * any operation that adds or deletes one or more elements, or explicitly
+ * resizes the backing array; merely setting the value of an element is not
+ * a structural modification.) This is typically accomplished by
+ * synchronizing on some object that naturally encapsulates the list.
+ * <p>
+ * If the implementation is accessed via concurrent reads, this is thread safe.
+ * Conversions are done in a thread safe manner. It's possible that the
+ * conversion may happen more than once if two threads attempt to access the
+ * same element and the modifications were not visible to each other, but this
+ * will not result in any corruption of the list or change in behavior other
+ * than performance.
+ *
+ * @author jonp@google.com (Jon Perlow)
+ */
+public class LazyStringArrayList extends AbstractList<String>
+ implements LazyStringList, RandomAccess {
+
+ public final static LazyStringList EMPTY = new UnmodifiableLazyStringList(
+ new LazyStringArrayList());
+
+ private final List<Object> list;
+
+ public LazyStringArrayList() {
+ list = new ArrayList<Object>();
+ }
+
+ public LazyStringArrayList(List<String> from) {
+ list = new ArrayList<Object>(from);
+ }
+
+ @Override
+ public String get(int index) {
+ Object o = list.get(index);
+ if (o instanceof String) {
+ return (String) o;
+ } else {
+ ByteString bs = (ByteString) o;
+ String s = bs.toStringUtf8();
+ if (Internal.isValidUtf8(bs)) {
+ list.set(index, s);
+ }
+ return s;
+ }
+ }
+
+ @Override
+ public int size() {
+ return list.size();
+ }
+
+ @Override
+ public String set(int index, String s) {
+ Object o = list.set(index, s);
+ return asString(o);
+ }
+
+ @Override
+ public void add(int index, String element) {
+ list.add(index, element);
+ modCount++;
+ }
+
+ @Override
+ public boolean addAll(int index, Collection<? extends String> c) {
+ boolean ret = list.addAll(index, c);
+ modCount++;
+ return ret;
+ }
+
+ @Override
+ public String remove(int index) {
+ Object o = list.remove(index);
+ modCount++;
+ return asString(o);
+ }
+
+ public void clear() {
+ list.clear();
+ modCount++;
+ }
+
+ // @Override
+ public void add(ByteString element) {
+ list.add(element);
+ modCount++;
+ }
+
+ // @Override
+ public ByteString getByteString(int index) {
+ Object o = list.get(index);
+ if (o instanceof String) {
+ ByteString b = ByteString.copyFromUtf8((String) o);
+ list.set(index, b);
+ return b;
+ } else {
+ return (ByteString) o;
+ }
+ }
+
+ private String asString(Object o) {
+ if (o instanceof String) {
+ return (String) o;
+ } else {
+ return ((ByteString) o).toStringUtf8();
+ }
+ }
+}
diff --git a/java/src/main/java/com/google/protobuf/LazyStringList.java b/java/src/main/java/com/google/protobuf/LazyStringList.java
new file mode 100644
index 00000000..97139ca6
--- /dev/null
+++ b/java/src/main/java/com/google/protobuf/LazyStringList.java
@@ -0,0 +1,72 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// http://code.google.com/p/protobuf/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import java.util.List;
+
+/**
+ * An interface extending List&lt;String&gt; that also provides access to the
+ * items of the list as UTF8-encoded ByteString objects. This is used by the
+ * protocol buffer implementation to support lazily converting bytes parsed
+ * over the wire to String objects until needed and also increases the
+ * efficiency of serialization if the String was never requested as the
+ * ByteString is already cached.
+ * <p>
+ * This only adds additional methods that are required for the use in the
+ * protocol buffer code in order to be able successfuly round trip byte arrays
+ * through parsing and serialization without conversion to strings. It's not
+ * attempting to support the functionality of say List&ltByteString&gt, hence
+ * why only these two very specific methods are added.
+ *
+ * @author jonp@google.com (Jon Perlow)
+ */
+public interface LazyStringList extends List<String> {
+
+ /**
+ * Returns the element at the specified position in this list as a ByteString.
+ *
+ * @param index index of the element to return
+ * @return the element at the specified position in this list
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * (<tt>index &lt; 0 || index &gt;= size()</tt>)
+ */
+ ByteString getByteString(int index);
+
+ /**
+ * Appends the specified element to the end of this list (optional
+ * operation).
+ *
+ * @param element element to be appended to this list
+ * @throws UnsupportedOperationException if the <tt>add</tt> operation
+ * is not supported by this list
+ */
+ void add(ByteString element);
+}
diff --git a/java/src/main/java/com/google/protobuf/Message.java b/java/src/main/java/com/google/protobuf/Message.java
index 8c29e212..67c4148e 100644
--- a/java/src/main/java/com/google/protobuf/Message.java
+++ b/java/src/main/java/com/google/protobuf/Message.java
@@ -48,69 +48,7 @@ import java.util.Map;
*
* @author kenton@google.com Kenton Varda
*/
-public interface Message extends MessageLite {
- /**
- * 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();
-
- // (From MessageLite, re-declared here only for return type covariance.)
- 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();
+public interface Message extends MessageLite, MessageOrBuilder {
// -----------------------------------------------------------------
// Comparison and hashing
@@ -119,7 +57,8 @@ public interface Message extends MessageLite {
* 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.
+ * all of its fields. Subclasses must implement this; inheriting
+ * {@code Object.equals()} is incorrect.
*
* @param other object to be compared for equality with this message
* @return <tt>true</tt> if the specified object is equal to this message
@@ -129,8 +68,9 @@ public interface Message extends MessageLite {
/**
* 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.
+ * should mix the message's type (object identity of the decsriptor) with its
+ * contents (known and unknown field values). Subclasses must implement this;
+ * inheriting {@code Object.hashCode()} is incorrect.
*
* @return the hash code value for this message
* @see Map#hashCode()
@@ -158,7 +98,7 @@ public interface Message extends MessageLite {
/**
* Abstract interface implemented by Protocol Message builders.
*/
- interface Builder extends MessageLite.Builder {
+ interface Builder extends MessageLite.Builder, MessageOrBuilder {
// (From MessageLite.Builder, re-declared here only for return type
// covariance.)
Builder clear();
@@ -197,17 +137,6 @@ public interface Message extends MessageLite {
*/
Descriptors.Descriptor getDescriptorForType();
- // (From MessageLite.Builder, re-declared here only for return type
- // covariance.)
- 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(),
@@ -215,12 +144,6 @@ public interface Message extends MessageLite {
*/
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
@@ -235,16 +158,6 @@ public interface Message extends MessageLite {
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
@@ -262,9 +175,6 @@ public interface Message extends MessageLite {
*/
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);
diff --git a/java/src/main/java/com/google/protobuf/MessageLite.java b/java/src/main/java/com/google/protobuf/MessageLite.java
index cf7f39e2..31b8256e 100644
--- a/java/src/main/java/com/google/protobuf/MessageLite.java
+++ b/java/src/main/java/com/google/protobuf/MessageLite.java
@@ -64,22 +64,8 @@ import java.io.OutputStream;
*
* @author kenton@google.com Kenton Varda
*/
-public interface MessageLite {
- /**
- * 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 MessageLite} interface
- * whereas {@code getDefaultInstance()} is a static method of a specific
- * class. They return the same thing.
- */
- MessageLite getDefaultInstanceForType();
+public interface MessageLite extends MessageLiteOrBuilder {
- /**
- * 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
@@ -153,7 +139,7 @@ public interface MessageLite {
/**
* Abstract interface implemented by Protocol Message builders.
*/
- interface Builder extends Cloneable {
+ interface Builder extends MessageLiteOrBuilder, Cloneable {
/** Resets all fields to their default values. */
Builder clear();
@@ -187,12 +173,6 @@ public interface MessageLite {
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(MessageLite)}.
*
@@ -230,12 +210,6 @@ public interface MessageLite {
ExtensionRegistryLite extensionRegistry)
throws IOException;
- /**
- * Get the message's type's default instance.
- * See {@link MessageLite#getDefaultInstanceForType()}.
- */
- MessageLite getDefaultInstanceForType();
-
// ---------------------------------------------------------------
// Convenience methods.
@@ -243,6 +217,8 @@ public interface MessageLite {
* Parse {@code data} as a message of this type and merge it with the
* message being built. This is just a small wrapper around
* {@link #mergeFrom(CodedInputStream)}.
+ *
+ * @return this
*/
Builder mergeFrom(ByteString data) throws InvalidProtocolBufferException;
@@ -250,6 +226,8 @@ public interface MessageLite {
* 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)}.
+ *
+ * @return this
*/
Builder mergeFrom(ByteString data,
ExtensionRegistryLite extensionRegistry)
@@ -259,6 +237,8 @@ public interface MessageLite {
* Parse {@code data} as a message of this type and merge it with the
* message being built. This is just a small wrapper around
* {@link #mergeFrom(CodedInputStream)}.
+ *
+ * @return this
*/
Builder mergeFrom(byte[] data) throws InvalidProtocolBufferException;
@@ -266,6 +246,8 @@ public interface MessageLite {
* Parse {@code data} as a message of this type and merge it with the
* message being built. This is just a small wrapper around
* {@link #mergeFrom(CodedInputStream)}.
+ *
+ * @return this
*/
Builder mergeFrom(byte[] data, int off, int len)
throws InvalidProtocolBufferException;
@@ -274,6 +256,8 @@ public interface MessageLite {
* 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)}.
+ *
+ * @return this
*/
Builder mergeFrom(byte[] data,
ExtensionRegistryLite extensionRegistry)
@@ -283,6 +267,8 @@ public interface MessageLite {
* 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)}.
+ *
+ * @return this
*/
Builder mergeFrom(byte[] data, int off, int len,
ExtensionRegistryLite extensionRegistry)
@@ -299,6 +285,8 @@ public interface MessageLite {
* and {@link #mergeDelimitedFrom(InputStream)} to read it.
* <p>
* Despite usually reading the entire input, this does not close the stream.
+ *
+ * @return this
*/
Builder mergeFrom(InputStream input) throws IOException;
@@ -306,6 +294,8 @@ public interface MessageLite {
* 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)}.
+ *
+ * @return this
*/
Builder mergeFrom(InputStream input,
ExtensionRegistryLite extensionRegistry)
diff --git a/java/src/main/java/com/google/protobuf/MessageLiteOrBuilder.java b/java/src/main/java/com/google/protobuf/MessageLiteOrBuilder.java
new file mode 100644
index 00000000..a2a7448a
--- /dev/null
+++ b/java/src/main/java/com/google/protobuf/MessageLiteOrBuilder.java
@@ -0,0 +1,56 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// http://code.google.com/p/protobuf/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+/**
+ * Base interface for methods common to {@link MessageLite}
+ * and {@link MessageLite.Builder} to provide type equivalency.
+ *
+ * @author jonp@google.com (Jon Perlow)
+ */
+public interface MessageLiteOrBuilder {
+ /**
+ * Get an instance of the type with 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 MessageLite} interface
+ * whereas {@code getDefaultInstance()} is a static method of a specific
+ * class. They return the same thing.
+ */
+ MessageLite getDefaultInstanceForType();
+
+ /**
+ * Returns true if all required fields in the message and all embedded
+ * messages are set, false otherwise.
+ */
+ boolean isInitialized();
+
+}
diff --git a/java/src/main/java/com/google/protobuf/MessageOrBuilder.java b/java/src/main/java/com/google/protobuf/MessageOrBuilder.java
new file mode 100644
index 00000000..0132e7ca
--- /dev/null
+++ b/java/src/main/java/com/google/protobuf/MessageOrBuilder.java
@@ -0,0 +1,110 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// http://code.google.com/p/protobuf/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import java.util.Map;
+
+/**
+ * Base interface for methods common to {@link Message} and
+ * {@link Message.Builder} to provide type equivalency.
+ *
+ * @author jonp@google.com (Jon Perlow)
+ */
+public interface MessageOrBuilder extends MessageLiteOrBuilder {
+
+ // (From MessageLite, re-declared here only for return type covariance.)
+ //@Override (Java 1.6 override semantics, but we must support 1.5)
+ Message getDefaultInstanceForType();
+
+ /**
+ * Get the message's type's descriptor. This differs from the
+ * {@code getDescriptor()} method of generated message classes in that
+ * this method is an abstract method of the {@code Message} interface
+ * whereas {@code getDescriptor()} is a static method of a specific class.
+ * They return the same thing.
+ */
+ Descriptors.Descriptor getDescriptorForType();
+
+ /**
+ * Returns a collection of all the fields in this message which are set
+ * and their corresponding values. A singular ("required" or "optional")
+ * field is set iff hasField() returns true for that field. A "repeated"
+ * field is set iff 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.
+ * <br>
+ * If this is for a builder, the returned map may or may not reflect future
+ * changes to the builder. Either way, the returned map is itself
+ * unmodifiable.
+ */
+ Map<Descriptors.FieldDescriptor, Object> getAllFields();
+
+ /**
+ * Returns true if the given 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();
+}
diff --git a/java/src/main/java/com/google/protobuf/RepeatedFieldBuilder.java b/java/src/main/java/com/google/protobuf/RepeatedFieldBuilder.java
new file mode 100644
index 00000000..0772eaca
--- /dev/null
+++ b/java/src/main/java/com/google/protobuf/RepeatedFieldBuilder.java
@@ -0,0 +1,696 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// http://code.google.com/p/protobuf/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * <code>RepeatedFieldBuilder</code> implements a structure that a protocol
+ * message uses to hold a repeated field of other protocol messages. It supports
+ * the classical use case of adding immutable {@link Message}'s to the
+ * repeated field and is highly optimized around this (no extra memory
+ * allocations and sharing of immutable arrays).
+ * <br>
+ * It also supports the additional use case of adding a {@link Message.Builder}
+ * to the repeated field and deferring conversion of that <code>Builder</code>
+ * to an immutable <code>Message</code>. In this way, it's possible to maintain
+ * a tree of <code>Builder</code>'s that acts as a fully read/write data
+ * structure.
+ * <br>
+ * Logically, one can think of a tree of builders as converting the entire tree
+ * to messages when build is called on the root or when any method is called
+ * that desires a Message instead of a Builder. In terms of the implementation,
+ * the <code>SingleFieldBuilder</code> and <code>RepeatedFieldBuilder</code>
+ * classes cache messages that were created so that messages only need to be
+ * created when some change occured in its builder or a builder for one of its
+ * descendants.
+ *
+ * @param <MType> the type of message for the field
+ * @param <BType> the type of builder for the field
+ * @param <IType> the common interface for the message and the builder
+ *
+ * @author jonp@google.com (Jon Perlow)
+ */
+public class RepeatedFieldBuilder
+ <MType extends GeneratedMessage,
+ BType extends GeneratedMessage.Builder,
+ IType extends MessageOrBuilder>
+ implements GeneratedMessage.BuilderParent {
+
+ // Parent to send changes to.
+ private GeneratedMessage.BuilderParent parent;
+
+ // List of messages. Never null. It may be immutable, in which case
+ // isMessagesListImmutable will be true. See note below.
+ private List<MType> messages;
+
+ // Whether messages is an mutable array that can be modified.
+ private boolean isMessagesListMutable;
+
+ // List of builders. May be null, in which case, no nested builders were
+ // created. If not null, entries represent the builder for that index.
+ private List<SingleFieldBuilder<MType, BType, IType>> builders;
+
+ // Here are the invariants for messages and builders:
+ // 1. messages is never null and its count corresponds to the number of items
+ // in the repeated field.
+ // 2. If builders is non-null, messages and builders MUST always
+ // contain the same number of items.
+ // 3. Entries in either array can be null, but for any index, there MUST be
+ // either a Message in messages or a builder in builders.
+ // 4. If the builder at an index is non-null, the builder is
+ // authoritative. This is the case where a Builder was set on the index.
+ // Any message in the messages array MUST be ignored.
+ // t. If the builder at an index is null, the message in the messages
+ // list is authoritative. This is the case where a Message (not a Builder)
+ // was set directly for an index.
+
+ // Indicates that we've built a message and so we are now obligated
+ // to dispatch dirty invalidations. See GeneratedMessage.BuilderListener.
+ private boolean isClean;
+
+ // A view of this builder that exposes a List interface of messages. This is
+ // initialized on demand. This is fully backed by this object and all changes
+ // are reflected in it. Access to any item converts it to a message if it
+ // was a builder.
+ private MessageExternalList<MType, BType, IType> externalMessageList;
+
+ // A view of this builder that exposes a List interface of builders. This is
+ // initialized on demand. This is fully backed by this object and all changes
+ // are reflected in it. Access to any item converts it to a builder if it
+ // was a message.
+ private BuilderExternalList<MType, BType, IType> externalBuilderList;
+
+ // A view of this builder that exposes a List interface of the interface
+ // implemented by messages and builders. This is initialized on demand. This
+ // is fully backed by this object and all changes are reflected in it.
+ // Access to any item returns either a builder or message depending on
+ // what is most efficient.
+ private MessageOrBuilderExternalList<MType, BType, IType>
+ externalMessageOrBuilderList;
+
+ /**
+ * Constructs a new builder with an empty list of messages.
+ *
+ * @param messages the current list of messages
+ * @param isMessagesListMutable Whether the messages list is mutable
+ * @param parent a listener to notify of changes
+ * @param isClean whether the builder is initially marked clean
+ */
+ public RepeatedFieldBuilder(
+ List<MType> messages,
+ boolean isMessagesListMutable,
+ GeneratedMessage.BuilderParent parent,
+ boolean isClean) {
+ this.messages = messages;
+ this.isMessagesListMutable = isMessagesListMutable;
+ this.parent = parent;
+ this.isClean = isClean;
+ }
+
+ public void dispose() {
+ // Null out parent so we stop sending it invalidations.
+ parent = null;
+ }
+
+ /**
+ * Ensures that the list of messages is mutable so it can be updated. If it's
+ * immutable, a copy is made.
+ */
+ private void ensureMutableMessageList() {
+ if (!isMessagesListMutable) {
+ messages = new ArrayList<MType>(messages);
+ isMessagesListMutable = true;
+ }
+ }
+
+ /**
+ * Ensures that the list of builders is not null. If it's null, the list is
+ * created and initialized to be the same size as the messages list with
+ * null entries.
+ */
+ private void ensureBuilders() {
+ if (this.builders == null) {
+ this.builders =
+ new ArrayList<SingleFieldBuilder<MType, BType, IType>>(
+ messages.size());
+ for (int i = 0; i < messages.size(); i++) {
+ builders.add(null);
+ }
+ }
+ }
+
+ /**
+ * Gets the count of items in the list.
+ *
+ * @return the count of items in the list.
+ */
+ public int getCount() {
+ return messages.size();
+ }
+
+ /**
+ * Gets whether the list is empty.
+ *
+ * @return whether the list is empty
+ */
+ public boolean isEmpty() {
+ return messages.isEmpty();
+ }
+
+ /**
+ * Get the message at the specified index. If the message is currently stored
+ * as a <code>Builder</code>, it is converted to a <code>Message</code> by
+ * calling {@link Message.Builder#buildPartial} on it.
+ *
+ * @param index the index of the message to get
+ * @return the message for the specified index
+ */
+ public MType getMessage(int index) {
+ return getMessage(index, false);
+ }
+
+ /**
+ * Get the message at the specified index. If the message is currently stored
+ * as a <code>Builder</code>, it is converted to a <code>Message</code> by
+ * calling {@link Message.Builder#buildPartial} on it.
+ *
+ * @param index the index of the message to get
+ * @param forBuild this is being called for build so we want to make sure
+ * we SingleFieldBuilder.build to send dirty invalidations
+ * @return the message for the specified index
+ */
+ private MType getMessage(int index, boolean forBuild) {
+ if (this.builders == null) {
+ // We don't have any builders -- return the current Message.
+ // This is the case where no builder was created, so we MUST have a
+ // Message.
+ return messages.get(index);
+ }
+
+ SingleFieldBuilder<MType, BType, IType> builder = builders.get(index);
+ if (builder == null) {
+ // We don't have a builder -- return the current message.
+ // This is the case where no builder was created for the entry at index,
+ // so we MUST have a message.
+ return messages.get(index);
+
+ } else {
+ return forBuild ? builder.build() : builder.getMessage();
+ }
+ }
+
+ /**
+ * Gets a builder for the specified index. If no builder has been created for
+ * that index, a builder is created on demand by calling
+ * {@link Message#toBuilder}.
+ *
+ * @param index the index of the message to get
+ * @return The builder for that index
+ */
+ public BType getBuilder(int index) {
+ ensureBuilders();
+ SingleFieldBuilder<MType, BType, IType> builder = builders.get(index);
+ if (builder == null) {
+ MType message = messages.get(index);
+ builder = new SingleFieldBuilder<MType, BType, IType>(
+ message, this, isClean);
+ builders.set(index, builder);
+ }
+ return builder.getBuilder();
+ }
+
+ /**
+ * Gets the base class interface for the specified index. This may either be
+ * a builder or a message. It will return whatever is more efficient.
+ *
+ * @param index the index of the message to get
+ * @return the message or builder for the index as the base class interface
+ */
+ @SuppressWarnings("unchecked")
+ public IType getMessageOrBuilder(int index) {
+ if (this.builders == null) {
+ // We don't have any builders -- return the current Message.
+ // This is the case where no builder was created, so we MUST have a
+ // Message.
+ return (IType) messages.get(index);
+ }
+
+ SingleFieldBuilder<MType, BType, IType> builder = builders.get(index);
+ if (builder == null) {
+ // We don't have a builder -- return the current message.
+ // This is the case where no builder was created for the entry at index,
+ // so we MUST have a message.
+ return (IType) messages.get(index);
+
+ } else {
+ return builder.getMessageOrBuilder();
+ }
+ }
+
+ /**
+ * Sets a message at the specified index replacing the existing item at
+ * that index.
+ *
+ * @param index the index to set.
+ * @param message the message to set
+ * @return the builder
+ */
+ public RepeatedFieldBuilder<MType, BType, IType> setMessage(
+ int index, MType message) {
+ if (message == null) {
+ throw new NullPointerException();
+ }
+ ensureMutableMessageList();
+ messages.set(index, message);
+ if (builders != null) {
+ SingleFieldBuilder<MType, BType, IType> entry =
+ builders.set(index, null);
+ if (entry != null) {
+ entry.dispose();
+ }
+ }
+ onChanged();
+ incrementModCounts();
+ return this;
+ }
+
+ /**
+ * Appends the specified element to the end of this list.
+ *
+ * @param message the message to add
+ * @return the builder
+ */
+ public RepeatedFieldBuilder<MType, BType, IType> addMessage(
+ MType message) {
+ if (message == null) {
+ throw new NullPointerException();
+ }
+ ensureMutableMessageList();
+ messages.add(message);
+ if (builders != null) {
+ builders.add(null);
+ }
+ onChanged();
+ incrementModCounts();
+ return this;
+ }
+
+ /**
+ * Inserts the specified message at the specified position in this list.
+ * Shifts the element currently at that position (if any) and any subsequent
+ * elements to the right (adds one to their indices).
+ *
+ * @param index the index at which to insert the message
+ * @param message the message to add
+ * @return the builder
+ */
+ public RepeatedFieldBuilder<MType, BType, IType> addMessage(
+ int index, MType message) {
+ if (message == null) {
+ throw new NullPointerException();
+ }
+ ensureMutableMessageList();
+ messages.add(index, message);
+ if (builders != null) {
+ builders.add(index, null);
+ }
+ onChanged();
+ incrementModCounts();
+ return this;
+ }
+
+ /**
+ * Appends all of the messages in the specified collection to the end of
+ * this list, in the order that they are returned by the specified
+ * collection's iterator.
+ *
+ * @param values the messages to add
+ * @return the builder
+ */
+ public RepeatedFieldBuilder<MType, BType, IType> addAllMessages(
+ Iterable<? extends MType> values) {
+ for (final MType value : values) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+ }
+ if (values instanceof Collection) {
+ @SuppressWarnings("unchecked") final
+ Collection<MType> collection = (Collection<MType>) values;
+ if (collection.size() == 0) {
+ return this;
+ }
+ ensureMutableMessageList();
+ for (MType value : values) {
+ addMessage(value);
+ }
+ } else {
+ ensureMutableMessageList();
+ for (MType value : values) {
+ addMessage(value);
+ }
+ }
+ onChanged();
+ incrementModCounts();
+ return this;
+ }
+
+ /**
+ * Appends a new builder to the end of this list and returns the builder.
+ *
+ * @param message the message to add which is the basis of the builder
+ * @return the new builder
+ */
+ public BType addBuilder(MType message) {
+ ensureMutableMessageList();
+ ensureBuilders();
+ SingleFieldBuilder<MType, BType, IType> builder =
+ new SingleFieldBuilder<MType, BType, IType>(
+ message, this, isClean);
+ messages.add(null);
+ builders.add(builder);
+ onChanged();
+ incrementModCounts();
+ return builder.getBuilder();
+ }
+
+ /**
+ * Inserts a new builder at the specified position in this list.
+ * Shifts the element currently at that position (if any) and any subsequent
+ * elements to the right (adds one to their indices).
+ *
+ * @param index the index at which to insert the builder
+ * @param message the message to add which is the basis of the builder
+ * @return the builder
+ */
+ public BType addBuilder(int index, MType message) {
+ ensureMutableMessageList();
+ ensureBuilders();
+ SingleFieldBuilder<MType, BType, IType> builder =
+ new SingleFieldBuilder<MType, BType, IType>(
+ message, this, isClean);
+ messages.add(index, null);
+ builders.add(index, builder);
+ onChanged();
+ incrementModCounts();
+ return builder.getBuilder();
+ }
+
+ /**
+ * Removes the element at the specified position in this list. Shifts any
+ * subsequent elements to the left (subtracts one from their indices).
+ * Returns the element that was removed from the list.
+ *
+ * @param index the index at which to remove the message
+ */
+ public void remove(int index) {
+ ensureMutableMessageList();
+ messages.remove(index);
+ if (builders != null) {
+ SingleFieldBuilder<MType, BType, IType> entry =
+ builders.remove(index);
+ if (entry != null) {
+ entry.dispose();
+ }
+ }
+ onChanged();
+ incrementModCounts();
+ }
+
+ /**
+ * Removes all of the elements from this list.
+ * The list will be empty after this call returns.
+ */
+ public void clear() {
+ messages = Collections.emptyList();
+ isMessagesListMutable = false;
+ if (builders != null) {
+ for (SingleFieldBuilder<MType, BType, IType> entry :
+ builders) {
+ if (entry != null) {
+ entry.dispose();
+ }
+ }
+ builders = null;
+ }
+ onChanged();
+ incrementModCounts();
+ }
+
+ /**
+ * Builds the list of messages from the builder and returns them.
+ *
+ * @return an immutable list of messages
+ */
+ public List<MType> build() {
+ // Now that build has been called, we are required to dispatch
+ // invalidations.
+ isClean = true;
+
+ if (!isMessagesListMutable && builders == null) {
+ // We still have an immutable list and we never created a builder.
+ return messages;
+ }
+
+ boolean allMessagesInSync = true;
+ if (!isMessagesListMutable) {
+ // We still have an immutable list. Let's see if any of them are out
+ // of sync with their builders.
+ for (int i = 0; i < messages.size(); i++) {
+ Message message = messages.get(i);
+ SingleFieldBuilder<MType, BType, IType> builder = builders.get(i);
+ if (builder != null) {
+ if (builder.build() != message) {
+ allMessagesInSync = false;
+ break;
+ }
+ }
+ }
+ if (allMessagesInSync) {
+ // Immutable list is still in sync.
+ return messages;
+ }
+ }
+
+ // Need to make sure messages is up to date
+ ensureMutableMessageList();
+ for (int i = 0; i < messages.size(); i++) {
+ messages.set(i, getMessage(i, true));
+ }
+
+ // We're going to return our list as immutable so we mark that we can
+ // no longer update it.
+ messages = Collections.unmodifiableList(messages);
+ isMessagesListMutable = false;
+ return messages;
+ }
+
+ /**
+ * Gets a view of the builder as a list of messages. The returned list is live
+ * and will reflect any changes to the underlying builder.
+ *
+ * @return the messages in the list
+ */
+ public List<MType> getMessageList() {
+ if (externalMessageList == null) {
+ externalMessageList =
+ new MessageExternalList<MType, BType, IType>(this);
+ }
+ return externalMessageList;
+ }
+
+ /**
+ * Gets a view of the builder as a list of builders. This returned list is
+ * live and will reflect any changes to the underlying builder.
+ *
+ * @return the builders in the list
+ */
+ public List<BType> getBuilderList() {
+ if (externalBuilderList == null) {
+ externalBuilderList =
+ new BuilderExternalList<MType, BType, IType>(this);
+ }
+ return externalBuilderList;
+ }
+
+ /**
+ * Gets a view of the builder as a list of MessageOrBuilders. This returned
+ * list is live and will reflect any changes to the underlying builder.
+ *
+ * @return the builders in the list
+ */
+ public List<IType> getMessageOrBuilderList() {
+ if (externalMessageOrBuilderList == null) {
+ externalMessageOrBuilderList =
+ new MessageOrBuilderExternalList<MType, BType, IType>(this);
+ }
+ return externalMessageOrBuilderList;
+ }
+
+ /**
+ * Called when a the builder or one of its nested children has changed
+ * and any parent should be notified of its invalidation.
+ */
+ private void onChanged() {
+ if (isClean && parent != null) {
+ parent.markDirty();
+
+ // Don't keep dispatching invalidations until build is called again.
+ isClean = false;
+ }
+ }
+
+ @Override
+ public void markDirty() {
+ onChanged();
+ }
+
+ /**
+ * Increments the mod counts so that an ConcurrentModificationException can
+ * be thrown if calling code tries to modify the builder while its iterating
+ * the list.
+ */
+ private void incrementModCounts() {
+ if (externalMessageList != null) {
+ externalMessageList.incrementModCount();
+ }
+ if (externalBuilderList != null) {
+ externalBuilderList.incrementModCount();
+ }
+ if (externalMessageOrBuilderList != null) {
+ externalMessageOrBuilderList.incrementModCount();
+ }
+ }
+
+ /**
+ * Provides a live view of the builder as a list of messages.
+ *
+ * @param <MType> the type of message for the field
+ * @param <BType> the type of builder for the field
+ * @param <IType> the common interface for the message and the builder
+ */
+ private static class MessageExternalList<
+ MType extends GeneratedMessage,
+ BType extends GeneratedMessage.Builder,
+ IType extends MessageOrBuilder>
+ extends AbstractList<MType> implements List<MType> {
+
+ RepeatedFieldBuilder<MType, BType, IType> builder;
+
+ MessageExternalList(
+ RepeatedFieldBuilder<MType, BType, IType> builder) {
+ this.builder = builder;
+ }
+
+ public int size() {
+ return this.builder.getCount();
+ }
+
+ public MType get(int index) {
+ return builder.getMessage(index);
+ }
+
+ void incrementModCount() {
+ modCount++;
+ }
+ }
+
+ /**
+ * Provides a live view of the builder as a list of builders.
+ *
+ * @param <MType> the type of message for the field
+ * @param <BType> the type of builder for the field
+ * @param <IType> the common interface for the message and the builder
+ */
+ private static class BuilderExternalList<
+ MType extends GeneratedMessage,
+ BType extends GeneratedMessage.Builder,
+ IType extends MessageOrBuilder>
+ extends AbstractList<BType> implements List<BType> {
+
+ RepeatedFieldBuilder<MType, BType, IType> builder;
+
+ BuilderExternalList(
+ RepeatedFieldBuilder<MType, BType, IType> builder) {
+ this.builder = builder;
+ }
+
+ public int size() {
+ return this.builder.getCount();
+ }
+
+ public BType get(int index) {
+ return builder.getBuilder(index);
+ }
+
+ void incrementModCount() {
+ modCount++;
+ }
+ }
+
+ /**
+ * Provides a live view of the builder as a list of builders.
+ *
+ * @param <MType> the type of message for the field
+ * @param <BType> the type of builder for the field
+ * @param <IType> the common interface for the message and the builder
+ */
+ private static class MessageOrBuilderExternalList<
+ MType extends GeneratedMessage,
+ BType extends GeneratedMessage.Builder,
+ IType extends MessageOrBuilder>
+ extends AbstractList<IType> implements List<IType> {
+
+ RepeatedFieldBuilder<MType, BType, IType> builder;
+
+ MessageOrBuilderExternalList(
+ RepeatedFieldBuilder<MType, BType, IType> builder) {
+ this.builder = builder;
+ }
+
+ public int size() {
+ return this.builder.getCount();
+ }
+
+ public IType get(int index) {
+ return builder.getMessageOrBuilder(index);
+ }
+
+ void incrementModCount() {
+ modCount++;
+ }
+ }
+}
diff --git a/java/src/main/java/com/google/protobuf/ServiceException.java b/java/src/main/java/com/google/protobuf/ServiceException.java
index c043a775..cde669d6 100644
--- a/java/src/main/java/com/google/protobuf/ServiceException.java
+++ b/java/src/main/java/com/google/protobuf/ServiceException.java
@@ -35,10 +35,18 @@ package com.google.protobuf;
*
* @author cpovirk@google.com (Chris Povirk)
*/
-public final class ServiceException extends Exception {
+public class ServiceException extends Exception {
private static final long serialVersionUID = -1219262335729891920L;
public ServiceException(final String message) {
super(message);
}
+
+ public ServiceException(final Throwable cause) {
+ super(cause);
+ }
+
+ public ServiceException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
}
diff --git a/java/src/main/java/com/google/protobuf/SingleFieldBuilder.java b/java/src/main/java/com/google/protobuf/SingleFieldBuilder.java
new file mode 100644
index 00000000..a92042e8
--- /dev/null
+++ b/java/src/main/java/com/google/protobuf/SingleFieldBuilder.java
@@ -0,0 +1,241 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// http://code.google.com/p/protobuf/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+/**
+ * <code>SingleFieldBuilder</code> implements a structure that a protocol
+ * message uses to hold a single field of another protocol message. It supports
+ * the classical use case of setting an immutable {@link Message} as the value
+ * of the field and is highly optimized around this.
+ * <br>
+ * It also supports the additional use case of setting a {@link Message.Builder}
+ * as the field and deferring conversion of that <code>Builder</code>
+ * to an immutable <code>Message</code>. In this way, it's possible to maintain
+ * a tree of <code>Builder</code>'s that acts as a fully read/write data
+ * structure.
+ * <br>
+ * Logically, one can think of a tree of builders as converting the entire tree
+ * to messages when build is called on the root or when any method is called
+ * that desires a Message instead of a Builder. In terms of the implementation,
+ * the <code>SingleFieldBuilder</code> and <code>RepeatedFieldBuilder</code>
+ * classes cache messages that were created so that messages only need to be
+ * created when some change occured in its builder or a builder for one of its
+ * descendants.
+ *
+ * @param <MType> the type of message for the field
+ * @param <BType> the type of builder for the field
+ * @param <IType> the common interface for the message and the builder
+ *
+ * @author jonp@google.com (Jon Perlow)
+ */
+public class SingleFieldBuilder
+ <MType extends GeneratedMessage,
+ BType extends GeneratedMessage.Builder,
+ IType extends MessageOrBuilder>
+ implements GeneratedMessage.BuilderParent {
+
+ // Parent to send changes to.
+ private GeneratedMessage.BuilderParent parent;
+
+ // Invariant: one of builder or message fields must be non-null.
+
+ // If set, this is the case where we are backed by a builder. In this case,
+ // message field represents a cached message for the builder (or null if
+ // there is no cached message).
+ private BType builder;
+
+ // If builder is non-null, this represents a cached message from the builder.
+ // If builder is null, this is the authoritative message for the field.
+ private MType message;
+
+ // Indicates that we've built a message and so we are now obligated
+ // to dispatch dirty invalidations. See GeneratedMessage.BuilderListener.
+ private boolean isClean;
+
+ public SingleFieldBuilder(
+ MType message,
+ GeneratedMessage.BuilderParent parent,
+ boolean isClean) {
+ if (message == null) {
+ throw new NullPointerException();
+ }
+ this.message = message;
+ this.parent = parent;
+ this.isClean = isClean;
+ }
+
+ public void dispose() {
+ // Null out parent so we stop sending it invalidations.
+ parent = null;
+ }
+
+ /**
+ * Get the message for the field. If the message is currently stored
+ * as a <code>Builder</code>, it is converted to a <code>Message</code> by
+ * calling {@link Message.Builder#buildPartial} on it. If no message has
+ * been set, returns the default instance of the message.
+ *
+ * @return the message for the field
+ */
+ @SuppressWarnings("unchecked")
+ public MType getMessage() {
+ if (message == null) {
+ // If message is null, the invariant is that we must be have a builder.
+ message = (MType) builder.buildPartial();
+ }
+ return message;
+ }
+
+ /**
+ * Builds the message and returns it.
+ *
+ * @return the message
+ */
+ public MType build() {
+ // Now that build has been called, we are required to dispatch
+ // invalidations.
+ isClean = true;
+ return getMessage();
+ }
+
+ /**
+ * Gets a builder for the field. If no builder has been created yet, a
+ * builder is created on demand by calling {@link Message#toBuilder}.
+ *
+ * @return The builder for the field
+ */
+ @SuppressWarnings("unchecked")
+ public BType getBuilder() {
+ if (builder == null) {
+ // builder.mergeFrom() on a fresh builder
+ // does not create any sub-objects with independent clean/dirty states,
+ // therefore setting the builder itself to clean without actually calling
+ // build() cannot break any invariants.
+ builder = (BType) message.newBuilderForType(this);
+ builder.mergeFrom(message); // no-op if message is the default message
+ builder.markClean();
+ }
+ return builder;
+ }
+
+ /**
+ * Gets the base class interface for the field. This may either be a builder
+ * or a message. It will return whatever is more efficient.
+ *
+ * @return the message or builder for the field as the base class interface
+ */
+ @SuppressWarnings("unchecked")
+ public IType getMessageOrBuilder() {
+ if (builder != null) {
+ return (IType) builder;
+ } else {
+ return (IType) message;
+ }
+ }
+
+ /**
+ * Sets a message for the field replacing any existing value.
+ *
+ * @param message the message to set
+ * @return the builder
+ */
+ public SingleFieldBuilder<MType, BType, IType> setMessage(
+ MType message) {
+ if (message == null) {
+ throw new NullPointerException();
+ }
+ this.message = message;
+ if (builder != null) {
+ builder.dispose();
+ builder = null;
+ }
+ onChanged();
+ return this;
+ }
+
+ /**
+ * Merges the field from another field.
+ *
+ * @param value the value to merge from
+ * @return the builder
+ */
+ public SingleFieldBuilder<MType, BType, IType> mergeFrom(
+ MType value) {
+ if (builder == null && message == message.getDefaultInstanceForType()) {
+ message = value;
+ } else {
+ getBuilder().mergeFrom(value);
+ }
+ onChanged();
+ return this;
+ }
+
+ /**
+ * Clears the value of the field.
+ *
+ * @return the builder
+ */
+ @SuppressWarnings("unchecked")
+ public SingleFieldBuilder<MType, BType, IType> clear() {
+ message = (MType) (message != null ?
+ message.getDefaultInstanceForType() :
+ builder.getDefaultInstanceForType());
+ if (builder != null) {
+ builder.dispose();
+ builder = null;
+ }
+ onChanged();
+ return this;
+ }
+
+ /**
+ * Called when a the builder or one of its nested children has changed
+ * and any parent should be notified of its invalidation.
+ */
+ private void onChanged() {
+ // If builder is null, this is the case where onChanged is being called
+ // from setMessage or clear.
+ if (builder != null) {
+ message = null;
+ }
+ if (isClean && parent != null) {
+ parent.markDirty();
+
+ // Don't keep dispatching invalidations until build is called again.
+ isClean = false;
+ }
+ }
+
+ @Override
+ public void markDirty() {
+ onChanged();
+ }
+}
diff --git a/java/src/main/java/com/google/protobuf/SmallSortedMap.java b/java/src/main/java/com/google/protobuf/SmallSortedMap.java
new file mode 100644
index 00000000..ccc20163
--- /dev/null
+++ b/java/src/main/java/com/google/protobuf/SmallSortedMap.java
@@ -0,0 +1,618 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// http://code.google.com/p/protobuf/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import java.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.TreeMap;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.SortedMap;
+
+/**
+ * A custom map implementation from FieldDescriptor to Object optimized to
+ * minimize the number of memory allocations for instances with a small number
+ * of mappings. The implementation stores the first {@code k} mappings in an
+ * array for a configurable value of {@code k}, allowing direct access to the
+ * corresponding {@code Entry}s without the need to create an Iterator. The
+ * remaining entries are stored in an overflow map. Iteration over the entries
+ * in the map should be done as follows:
+ *
+ * <pre>
+ * for (int i = 0; i &lt; fieldMap.getNumArrayEntries(); i++) {
+ * process(fieldMap.getArrayEntryAt(i));
+ * }
+ * for (Map.Entry&lt;K, V&gt; entry : fieldMap.getOverflowEntries()) {
+ * process(entry);
+ * }
+ * </pre>
+ *
+ * The resulting iteration is in order of ascending field tag number. The
+ * object returned by {@link #entrySet()} adheres to the same contract but is
+ * less efficient as it necessarily involves creating an object for iteration.
+ * <p>
+ * The tradeoff for this memory efficiency is that the worst case running time
+ * of the {@code put()} operation is {@code O(k + lg n)}, which happens when
+ * entries are added in descending order. {@code k} should be chosen such that
+ * it covers enough common cases without adversely affecting larger maps. In
+ * practice, the worst case scenario does not happen for extensions because
+ * extension fields are serialized and deserialized in order of ascending tag
+ * number, but the worst case scenario can happen for DynamicMessages.
+ * <p>
+ * The running time for all other operations is similar to that of
+ * {@code TreeMap}.
+ * <p>
+ * Instances are not thread-safe until {@link #makeImmutable()} is called,
+ * after which any modifying operation will result in an
+ * {@link UnsupportedOperationException}.
+ *
+ * @author darick@google.com Darick Tong
+ */
+// This class is final for all intents and purposes because the constructor is
+// private. However, the FieldDescriptor-specific logic is encapsulated in
+// a subclass to aid testability of the core logic.
+class SmallSortedMap<K extends Comparable<K>, V> extends AbstractMap<K, V> {
+
+ /**
+ * Creates a new instance for mapping FieldDescriptors to their values.
+ * The {@link #makeImmutable()} implementation will convert the List values
+ * of any repeated fields to unmodifiable lists.
+ *
+ * @param arraySize The size of the entry array containing the
+ * lexicographically smallest mappings.
+ */
+ static <FieldDescriptorType extends
+ FieldSet.FieldDescriptorLite<FieldDescriptorType>>
+ SmallSortedMap<FieldDescriptorType, Object> newFieldMap(int arraySize) {
+ return new SmallSortedMap<FieldDescriptorType, Object>(arraySize) {
+ @Override
+ @SuppressWarnings("unchecked")
+ public void makeImmutable() {
+ if (!isImmutable()) {
+ for (int i = 0; i < getNumArrayEntries(); i++) {
+ final Map.Entry<FieldDescriptorType, Object> entry =
+ getArrayEntryAt(i);
+ if (entry.getKey().isRepeated()) {
+ final List value = (List) entry.getValue();
+ entry.setValue(Collections.unmodifiableList(value));
+ }
+ }
+ for (Map.Entry<FieldDescriptorType, Object> entry :
+ getOverflowEntries()) {
+ if (entry.getKey().isRepeated()) {
+ final List value = (List) entry.getValue();
+ entry.setValue(Collections.unmodifiableList(value));
+ }
+ }
+ }
+ super.makeImmutable();
+ }
+ };
+ }
+
+ /**
+ * Creates a new instance for testing.
+ *
+ * @param arraySize The size of the entry array containing the
+ * lexicographically smallest mappings.
+ */
+ static <K extends Comparable<K>, V> SmallSortedMap<K, V> newInstanceForTest(
+ int arraySize) {
+ return new SmallSortedMap<K, V>(arraySize);
+ }
+
+ private final int maxArraySize;
+ // The "entry array" is actually a List because generic arrays are not
+ // allowed. ArrayList also nicely handles the entry shifting on inserts and
+ // removes.
+ private List<Entry> entryList;
+ private Map<K, V> overflowEntries;
+ private boolean isImmutable;
+ // The EntrySet is a stateless view of the Map. It's initialized the first
+ // time it is requested and reused henceforth.
+ private volatile EntrySet lazyEntrySet;
+
+ /**
+ * @code arraySize Size of the array in which the lexicographically smallest
+ * mappings are stored. (i.e. the {@code k} referred to in the class
+ * documentation).
+ */
+ private SmallSortedMap(int arraySize) {
+ this.maxArraySize = arraySize;
+ this.entryList = Collections.emptyList();
+ this.overflowEntries = Collections.emptyMap();
+ }
+
+ /** Make this map immutable from this point forward. */
+ public void makeImmutable() {
+ if (!isImmutable) {
+ // Note: There's no need to wrap the entryList in an unmodifiableList
+ // because none of the list's accessors are exposed. The iterator() of
+ // overflowEntries, on the other hand, is exposed so it must be made
+ // unmodifiable.
+ overflowEntries = overflowEntries.isEmpty() ?
+ Collections.<K, V>emptyMap() :
+ Collections.unmodifiableMap(overflowEntries);
+ isImmutable = true;
+ }
+ }
+
+ /** @return Whether {@link #makeImmutable()} has been called. */
+ public boolean isImmutable() {
+ return isImmutable;
+ }
+
+ /** @return The number of entries in the entry array. */
+ public int getNumArrayEntries() {
+ return entryList.size();
+ }
+
+ /** @return The array entry at the given {@code index}. */
+ public Map.Entry<K, V> getArrayEntryAt(int index) {
+ return entryList.get(index);
+ }
+
+ /** @return There number of overflow entries. */
+ public int getNumOverflowEntries() {
+ return overflowEntries.size();
+ }
+
+ /** @return An iterable over the overflow entries. */
+ public Iterable<Map.Entry<K, V>> getOverflowEntries() {
+ return overflowEntries.isEmpty() ?
+ EmptySet.<Map.Entry<K, V>>iterable() :
+ overflowEntries.entrySet();
+ }
+
+ @Override
+ public int size() {
+ return entryList.size() + overflowEntries.size();
+ }
+
+ /**
+ * The implementation throws a {@code ClassCastException} if o is not an
+ * object of type {@code K}.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean containsKey(Object o) {
+ @SuppressWarnings("unchecked")
+ final K key = (K) o;
+ return binarySearchInArray(key) >= 0 || overflowEntries.containsKey(key);
+ }
+
+ /**
+ * The implementation throws a {@code ClassCastException} if o is not an
+ * object of type {@code K}.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public V get(Object o) {
+ @SuppressWarnings("unchecked")
+ final K key = (K) o;
+ final int index = binarySearchInArray(key);
+ if (index >= 0) {
+ return entryList.get(index).getValue();
+ }
+ return overflowEntries.get(key);
+ }
+
+ @Override
+ public V put(K key, V value) {
+ checkMutable();
+ final int index = binarySearchInArray(key);
+ if (index >= 0) {
+ // Replace existing array entry.
+ return entryList.get(index).setValue(value);
+ }
+ ensureEntryArrayMutable();
+ final int insertionPoint = -(index + 1);
+ if (insertionPoint >= maxArraySize) {
+ // Put directly in overflow.
+ return getOverflowEntriesMutable().put(key, value);
+ }
+ // Insert new Entry in array.
+ if (entryList.size() == maxArraySize) {
+ // Shift the last array entry into overflow.
+ final Entry lastEntryInArray = entryList.remove(maxArraySize - 1);
+ getOverflowEntriesMutable().put(lastEntryInArray.getKey(),
+ lastEntryInArray.getValue());
+ }
+ entryList.add(insertionPoint, new Entry(key, value));
+ return null;
+ }
+
+ @Override
+ public void clear() {
+ checkMutable();
+ if (!entryList.isEmpty()) {
+ entryList.clear();
+ }
+ if (!overflowEntries.isEmpty()) {
+ overflowEntries.clear();
+ }
+ }
+
+ /**
+ * The implementation throws a {@code ClassCastException} if o is not an
+ * object of type {@code K}.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public V remove(Object o) {
+ checkMutable();
+ @SuppressWarnings("unchecked")
+ final K key = (K) o;
+ final int index = binarySearchInArray(key);
+ if (index >= 0) {
+ return removeArrayEntryAt(index);
+ }
+ // overflowEntries might be Collections.unmodifiableMap(), so only
+ // call remove() if it is non-empty.
+ if (overflowEntries.isEmpty()) {
+ return null;
+ } else {
+ return overflowEntries.remove(key);
+ }
+ }
+
+ private V removeArrayEntryAt(int index) {
+ checkMutable();
+ final V removed = entryList.remove(index).getValue();
+ if (!overflowEntries.isEmpty()) {
+ // Shift the first entry in the overflow to be the last entry in the
+ // array.
+ final Iterator<Map.Entry<K, V>> iterator =
+ getOverflowEntriesMutable().entrySet().iterator();
+ entryList.add(new Entry(iterator.next()));
+ iterator.remove();
+ }
+ return removed;
+ }
+
+ /**
+ * @param key The key to find in the entry array.
+ * @return The returned integer position follows the same semantics as the
+ * value returned by {@link java.util.Arrays#binarySearch()}.
+ */
+ private int binarySearchInArray(K key) {
+ int left = 0;
+ int right = entryList.size() - 1;
+
+ // Optimization: For the common case in which entries are added in
+ // ascending tag order, check the largest element in the array before
+ // doing a full binary search.
+ if (right >= 0) {
+ int cmp = key.compareTo(entryList.get(right).getKey());
+ if (cmp > 0) {
+ return -(right + 2); // Insert point is after "right".
+ } else if (cmp == 0) {
+ return right;
+ }
+ }
+
+ while (left <= right) {
+ int mid = (left + right) / 2;
+ int cmp = key.compareTo(entryList.get(mid).getKey());
+ if (cmp < 0) {
+ right = mid - 1;
+ } else if (cmp > 0) {
+ left = mid + 1;
+ } else {
+ return mid;
+ }
+ }
+ return -(left + 1);
+ }
+
+ /**
+ * Similar to the AbstractMap implementation of {@code keySet()} and
+ * {@code values()}, the entry set is created the first time this method is
+ * called, and returned in response to all subsequent calls.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public Set<Map.Entry<K, V>> entrySet() {
+ if (lazyEntrySet == null) {
+ lazyEntrySet = new EntrySet();
+ }
+ return lazyEntrySet;
+ }
+
+ /**
+ * @throws UnsupportedOperationException if {@link #makeImmutable()} has
+ * has been called.
+ */
+ private void checkMutable() {
+ if (isImmutable) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /**
+ * @return a {@link SortedMap} to which overflow entries mappings can be
+ * added or removed.
+ * @throws UnsupportedOperationException if {@link #makeImmutable()} has been
+ * called.
+ */
+ @SuppressWarnings("unchecked")
+ private SortedMap<K, V> getOverflowEntriesMutable() {
+ checkMutable();
+ if (overflowEntries.isEmpty() && !(overflowEntries instanceof TreeMap)) {
+ overflowEntries = new TreeMap<K, V>();
+ }
+ return (SortedMap<K, V>) overflowEntries;
+ }
+
+ /**
+ * Lazily creates the entry list. Any code that adds to the list must first
+ * call this method.
+ */
+ private void ensureEntryArrayMutable() {
+ checkMutable();
+ if (entryList.isEmpty() && !(entryList instanceof ArrayList)) {
+ entryList = new ArrayList<Entry>(maxArraySize);
+ }
+ }
+
+ /**
+ * Entry implementation that implements Comparable in order to support
+ * binary search witin the entry array. Also checks mutability in
+ * {@link #setValue()}.
+ */
+ private class Entry implements Map.Entry<K, V>, Comparable<Entry> {
+
+ private final K key;
+ private V value;
+
+ Entry(Map.Entry<K, V> copy) {
+ this(copy.getKey(), copy.getValue());
+ }
+
+ Entry(K key, V value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ @Override
+ public K getKey() {
+ return key;
+ }
+
+ @Override
+ public V getValue() {
+ return value;
+ }
+
+ @Override
+ public int compareTo(Entry other) {
+ return getKey().compareTo(other.getKey());
+ }
+
+ @Override
+ public V setValue(V newValue) {
+ checkMutable();
+ final V oldValue = this.value;
+ this.value = newValue;
+ return oldValue;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof Map.Entry)) {
+ return false;
+ }
+ @SuppressWarnings("unchecked")
+ Map.Entry<?, ?> other = (Map.Entry<?, ?>) o;
+ return equals(key, other.getKey()) && equals(value, other.getValue());
+ }
+
+ @Override
+ public int hashCode() {
+ return (key == null ? 0 : key.hashCode()) ^
+ (value == null ? 0 : value.hashCode());
+ }
+
+ @Override
+ public String toString() {
+ return key + "=" + value;
+ }
+
+ /** equals() that handles null values. */
+ private boolean equals(Object o1, Object o2) {
+ return o1 == null ? o2 == null : o1.equals(o2);
+ }
+ }
+
+ /**
+ * Stateless view of the entries in the field map.
+ */
+ private class EntrySet extends AbstractSet<Map.Entry<K, V>> {
+
+ @Override
+ public Iterator<Map.Entry<K, V>> iterator() {
+ return new EntryIterator();
+ }
+
+ @Override
+ public int size() {
+ return SmallSortedMap.this.size();
+ }
+
+ /**
+ * Throws a {@link ClassCastException} if o is not of the expected type.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean contains(Object o) {
+ @SuppressWarnings("unchecked")
+ final Map.Entry<K, V> entry = (Map.Entry<K, V>) o;
+ final V existing = get(entry.getKey());
+ final V value = entry.getValue();
+ return existing == value ||
+ (existing != null && existing.equals(value));
+ }
+
+ @Override
+ public boolean add(Map.Entry<K, V> entry) {
+ if (!contains(entry)) {
+ put(entry.getKey(), entry.getValue());
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Throws a {@link ClassCastException} if o is not of the expected type.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean remove(Object o) {
+ @SuppressWarnings("unchecked")
+ final Map.Entry<K, V> entry = (Map.Entry<K, V>) o;
+ if (contains(entry)) {
+ SmallSortedMap.this.remove(entry.getKey());
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void clear() {
+ SmallSortedMap.this.clear();
+ }
+ }
+
+ /**
+ * Iterator implementation that switches from the entry array to the overflow
+ * entries appropriately.
+ */
+ private class EntryIterator implements Iterator<Map.Entry<K, V>> {
+
+ private int pos = -1;
+ private boolean nextCalledBeforeRemove;
+ private Iterator<Map.Entry<K, V>> lazyOverflowIterator;
+
+ @Override
+ public boolean hasNext() {
+ return (pos + 1) < entryList.size() ||
+ getOverflowIterator().hasNext();
+ }
+
+ @Override
+ public Map.Entry<K, V> next() {
+ nextCalledBeforeRemove = true;
+ // Always increment pos so that we know whether the last returned value
+ // was from the array or from overflow.
+ if (++pos < entryList.size()) {
+ return entryList.get(pos);
+ }
+ return getOverflowIterator().next();
+ }
+
+ @Override
+ public void remove() {
+ if (!nextCalledBeforeRemove) {
+ throw new IllegalStateException("remove() was called before next()");
+ }
+ nextCalledBeforeRemove = false;
+ checkMutable();
+
+ if (pos < entryList.size()) {
+ removeArrayEntryAt(pos--);
+ } else {
+ getOverflowIterator().remove();
+ }
+ }
+
+ /**
+ * It is important to create the overflow iterator only after the array
+ * entries have been iterated over because the overflow entry set changes
+ * when the client calls remove() on the array entries, which invalidates
+ * any existing iterators.
+ */
+ private Iterator<Map.Entry<K, V>> getOverflowIterator() {
+ if (lazyOverflowIterator == null) {
+ lazyOverflowIterator = overflowEntries.entrySet().iterator();
+ }
+ return lazyOverflowIterator;
+ }
+ }
+
+ /**
+ * Helper class that holds immutable instances of an Iterable/Iterator that
+ * we return when the overflow entries is empty. This eliminates the creation
+ * of an Iterator object when there is nothing to iterate over.
+ */
+ private static class EmptySet {
+
+ private static final Iterator<Object> ITERATOR = new Iterator<Object>() {
+ @Override
+ public boolean hasNext() {
+ return false;
+ }
+ @Override
+ public Object next() {
+ throw new NoSuchElementException();
+ }
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+
+ private static final Iterable<Object> ITERABLE = new Iterable<Object>() {
+ @Override
+ public Iterator<Object> iterator() {
+ return ITERATOR;
+ }
+ };
+
+ @SuppressWarnings("unchecked")
+ static <T> Iterable<T> iterable() {
+ return (Iterable<T>) ITERABLE;
+ }
+ }
+}
diff --git a/java/src/main/java/com/google/protobuf/TextFormat.java b/java/src/main/java/com/google/protobuf/TextFormat.java
index 7ca2b4bf..d5fbdabf 100644
--- a/java/src/main/java/com/google/protobuf/TextFormat.java
+++ b/java/src/main/java/com/google/protobuf/TextFormat.java
@@ -46,15 +46,17 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
- * Provide ascii text parsing and formatting support for proto2 instances.
+ * Provide text parsing and formatting support for proto2 instances.
* The implementation largely follows google/protobuf/text_format.cc.
*
* @author wenboz@google.com Wenbo Zhu
* @author kenton@google.com Kenton Varda
*/
public final class TextFormat {
- private TextFormat() {
- }
+ private TextFormat() {}
+
+ private static final Printer DEFAULT_PRINTER = new Printer(false);
+ private static final Printer SINGLE_LINE_PRINTER = new Printer(true);
/**
* Outputs a textual representation of the Protocol Message supplied into
@@ -63,16 +65,44 @@ public final class TextFormat {
*/
public static void print(final Message message, final Appendable output)
throws IOException {
- final TextGenerator generator = new TextGenerator(output);
- print(message, generator);
+ DEFAULT_PRINTER.print(message, new TextGenerator(output));
}
/** Outputs a textual representation of {@code fields} to {@code output}. */
public static void print(final UnknownFieldSet fields,
final Appendable output)
throws IOException {
- final TextGenerator generator = new TextGenerator(output);
- printUnknownFields(fields, generator);
+ DEFAULT_PRINTER.printUnknownFields(fields, new TextGenerator(output));
+ }
+
+ /**
+ * Generates a human readable form of this message, useful for debugging and
+ * other purposes, with no newline characters.
+ */
+ public static String shortDebugString(final Message message) {
+ try {
+ final StringBuilder sb = new StringBuilder();
+ SINGLE_LINE_PRINTER.print(message, new TextGenerator(sb));
+ // Single line mode currently might have an extra space at the end.
+ return sb.toString().trim();
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ /**
+ * Generates a human readable form of the unknown fields, useful for debugging
+ * and other purposes, with no newline characters.
+ */
+ public static String shortDebugString(final UnknownFieldSet fields) {
+ try {
+ final StringBuilder sb = new StringBuilder();
+ SINGLE_LINE_PRINTER.printUnknownFields(fields, new TextGenerator(sb));
+ // Single line mode currently might have an extra space at the end.
+ return sb.toString().trim();
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
}
/**
@@ -85,9 +115,7 @@ public final class TextFormat {
print(message, text);
return text.toString();
} catch (IOException e) {
- throw new RuntimeException(
- "Writing to a StringBuilder threw an IOException (should never " +
- "happen).", e);
+ throw new IllegalStateException(e);
}
}
@@ -101,28 +129,15 @@ public final class TextFormat {
print(fields, text);
return text.toString();
} catch (IOException e) {
- throw new RuntimeException(
- "Writing to a StringBuilder threw an IOException (should never " +
- "happen).", e);
+ throw new IllegalStateException(e);
}
}
- private static void print(final Message message,
- final TextGenerator generator)
- throws IOException {
- for (final Map.Entry<FieldDescriptor, Object> field :
- message.getAllFields().entrySet()) {
- printField(field.getKey(), field.getValue(), generator);
- }
- printUnknownFields(message.getUnknownFields(), generator);
- }
-
public static void printField(final FieldDescriptor field,
final Object value,
final Appendable output)
throws IOException {
- final TextGenerator generator = new TextGenerator(output);
- printField(field, value, generator);
+ DEFAULT_PRINTER.printField(field, value, new TextGenerator(output));
}
public static String printFieldToString(final FieldDescriptor field,
@@ -132,157 +147,263 @@ public final class TextFormat {
printField(field, value, text);
return text.toString();
} catch (IOException e) {
- throw new RuntimeException(
- "Writing to a StringBuilder threw an IOException (should never " +
- "happen).", e);
+ throw new IllegalStateException(e);
}
}
- private static void printField(final FieldDescriptor field,
- final Object value,
- final TextGenerator generator)
- throws IOException {
- if (field.isRepeated()) {
- // Repeated field. Print each element.
- for (final Object element : (List<?>) value) {
- printSingleField(field, element, generator);
- }
- } else {
- printSingleField(field, value, generator);
+ /**
+ * Outputs a textual representation of the value of given field value.
+ *
+ * @param field the descriptor of the field
+ * @param value the value of the field
+ * @param output the output to which to append the formatted value
+ * @throws ClassCastException if the value is not appropriate for the
+ * given field descriptor
+ * @throws IOException if there is an exception writing to the output
+ */
+ public static void printFieldValue(final FieldDescriptor field,
+ final Object value,
+ final Appendable output)
+ throws IOException {
+ DEFAULT_PRINTER.printFieldValue(field, value, new TextGenerator(output));
+ }
+
+ /**
+ * Outputs a textual representation of the value of an unknown field.
+ *
+ * @param tag the field's tag number
+ * @param value the value of the field
+ * @param output the output to which to append the formatted value
+ * @throws ClassCastException if the value is not appropriate for the
+ * given field descriptor
+ * @throws IOException if there is an exception writing to the output
+ */
+ public static void printUnknownFieldValue(final int tag,
+ final Object value,
+ final Appendable output)
+ throws IOException {
+ printUnknownFieldValue(tag, value, new TextGenerator(output));
+ }
+
+ private static void printUnknownFieldValue(final int tag,
+ final Object value,
+ final TextGenerator generator)
+ throws IOException {
+ switch (WireFormat.getTagWireType(tag)) {
+ case WireFormat.WIRETYPE_VARINT:
+ generator.print(unsignedToString((Long) value));
+ break;
+ case WireFormat.WIRETYPE_FIXED32:
+ generator.print(
+ String.format((Locale) null, "0x%08x", (Integer) value));
+ break;
+ case WireFormat.WIRETYPE_FIXED64:
+ generator.print(String.format((Locale) null, "0x%016x", (Long) value));
+ break;
+ case WireFormat.WIRETYPE_LENGTH_DELIMITED:
+ generator.print("\"");
+ generator.print(escapeBytes((ByteString) value));
+ generator.print("\"");
+ break;
+ case WireFormat.WIRETYPE_START_GROUP:
+ DEFAULT_PRINTER.printUnknownFields((UnknownFieldSet) value, generator);
+ break;
+ default:
+ throw new IllegalArgumentException("Bad tag: " + tag);
}
}
- private static void printSingleField(final FieldDescriptor field,
- final Object value,
- final TextGenerator generator)
- throws IOException {
- if (field.isExtension()) {
- generator.print("[");
- // We special-case MessageSet elements for compatibility with proto1.
- if (field.getContainingType().getOptions().getMessageSetWireFormat()
- && (field.getType() == FieldDescriptor.Type.MESSAGE)
- && (field.isOptional())
- // object equality
- && (field.getExtensionScope() == field.getMessageType())) {
- generator.print(field.getMessageType().getFullName());
- } else {
- generator.print(field.getFullName());
+ /** Helper class for converting protobufs to text. */
+ private static final class Printer {
+ /** Whether to omit newlines from the output. */
+ final boolean singleLineMode;
+
+ private Printer(final boolean singleLineMode) {
+ this.singleLineMode = singleLineMode;
+ }
+
+ private void print(final Message message, final TextGenerator generator)
+ throws IOException {
+ for (Map.Entry<FieldDescriptor, Object> field
+ : message.getAllFields().entrySet()) {
+ printField(field.getKey(), field.getValue(), generator);
}
- generator.print("]");
- } else {
- if (field.getType() == FieldDescriptor.Type.GROUP) {
- // Groups must be serialized with their original capitalization.
- generator.print(field.getMessageType().getName());
+ printUnknownFields(message.getUnknownFields(), generator);
+ }
+
+ private void printField(final FieldDescriptor field, final Object value,
+ final TextGenerator generator) throws IOException {
+ if (field.isRepeated()) {
+ // Repeated field. Print each element.
+ for (Object element : (List<?>) value) {
+ printSingleField(field, element, generator);
+ }
} else {
- generator.print(field.getName());
+ printSingleField(field, value, generator);
}
}
- if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
- generator.print(" {\n");
- generator.indent();
- } else {
- generator.print(": ");
- }
+ private void printSingleField(final FieldDescriptor field,
+ final Object value,
+ final TextGenerator generator)
+ throws IOException {
+ if (field.isExtension()) {
+ generator.print("[");
+ // We special-case MessageSet elements for compatibility with proto1.
+ if (field.getContainingType().getOptions().getMessageSetWireFormat()
+ && (field.getType() == FieldDescriptor.Type.MESSAGE)
+ && (field.isOptional())
+ // object equality
+ && (field.getExtensionScope() == field.getMessageType())) {
+ generator.print(field.getMessageType().getFullName());
+ } else {
+ generator.print(field.getFullName());
+ }
+ generator.print("]");
+ } else {
+ if (field.getType() == FieldDescriptor.Type.GROUP) {
+ // Groups must be serialized with their original capitalization.
+ generator.print(field.getMessageType().getName());
+ } else {
+ generator.print(field.getName());
+ }
+ }
- printFieldValue(field, value, generator);
+ if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
+ if (singleLineMode) {
+ generator.print(" { ");
+ } else {
+ generator.print(" {\n");
+ generator.indent();
+ }
+ } else {
+ generator.print(": ");
+ }
- if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
- generator.outdent();
- generator.print("}");
+ printFieldValue(field, value, generator);
+
+ if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
+ if (singleLineMode) {
+ generator.print("} ");
+ } else {
+ generator.outdent();
+ generator.print("}\n");
+ }
+ } else {
+ if (singleLineMode) {
+ generator.print(" ");
+ } else {
+ generator.print("\n");
+ }
+ }
}
- generator.print("\n");
- }
- private static void printFieldValue(final FieldDescriptor field,
- final Object value,
- final 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;
+ private void printFieldValue(final FieldDescriptor field,
+ final Object value,
+ final TextGenerator generator)
+ throws IOException {
+ switch (field.getType()) {
+ case INT32:
+ case SINT32:
+ case SFIXED32:
+ generator.print(((Integer) value).toString());
+ break;
- case UINT32:
- case FIXED32:
- generator.print(unsignedToString((Integer) value));
- break;
+ case INT64:
+ case SINT64:
+ case SFIXED64:
+ generator.print(((Long) value).toString());
+ break;
- case UINT64:
- case FIXED64:
- generator.print(unsignedToString((Long) value));
- break;
+ case BOOL:
+ generator.print(((Boolean) value).toString());
+ break;
- case STRING:
- generator.print("\"");
- generator.print(escapeText((String) value));
- generator.print("\"");
- break;
+ case FLOAT:
+ generator.print(((Float) value).toString());
+ break;
- case BYTES:
- generator.print("\"");
- generator.print(escapeBytes((ByteString) value));
- generator.print("\"");
- break;
+ case DOUBLE:
+ generator.print(((Double) value).toString());
+ break;
- case ENUM:
- generator.print(((EnumValueDescriptor) value).getName());
- break;
+ case UINT32:
+ case FIXED32:
+ generator.print(unsignedToString((Integer) value));
+ break;
- case MESSAGE:
- case GROUP:
- print((Message) value, generator);
- break;
- }
- }
+ case UINT64:
+ case FIXED64:
+ generator.print(unsignedToString((Long) value));
+ break;
- private static void printUnknownFields(final UnknownFieldSet unknownFields,
- final TextGenerator generator)
- throws IOException {
- for (final Map.Entry<Integer, UnknownFieldSet.Field> entry :
- unknownFields.asMap().entrySet()) {
- final UnknownFieldSet.Field field = entry.getValue();
+ case STRING:
+ generator.print("\"");
+ generator.print(escapeText((String) value));
+ generator.print("\"");
+ break;
- for (final long value : field.getVarintList()) {
- generator.print(entry.getKey().toString());
- generator.print(": ");
- generator.print(unsignedToString(value));
- generator.print("\n");
+ 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;
}
- for (final int value : field.getFixed32List()) {
- generator.print(entry.getKey().toString());
- generator.print(": ");
- generator.print(String.format((Locale) null, "0x%08x", value));
- generator.print("\n");
+ }
+
+ private void printUnknownFields(final UnknownFieldSet unknownFields,
+ final TextGenerator generator)
+ throws IOException {
+ for (Map.Entry<Integer, UnknownFieldSet.Field> entry :
+ unknownFields.asMap().entrySet()) {
+ final int number = entry.getKey();
+ final UnknownFieldSet.Field field = entry.getValue();
+ printUnknownField(number, WireFormat.WIRETYPE_VARINT,
+ field.getVarintList(), generator);
+ printUnknownField(number, WireFormat.WIRETYPE_FIXED32,
+ field.getFixed32List(), generator);
+ printUnknownField(number, WireFormat.WIRETYPE_FIXED64,
+ field.getFixed64List(), generator);
+ printUnknownField(number, WireFormat.WIRETYPE_LENGTH_DELIMITED,
+ field.getLengthDelimitedList(), generator);
+ for (final UnknownFieldSet value : field.getGroupList()) {
+ generator.print(entry.getKey().toString());
+ if (singleLineMode) {
+ generator.print(" { ");
+ } else {
+ generator.print(" {\n");
+ generator.indent();
+ }
+ printUnknownFields(value, generator);
+ if (singleLineMode) {
+ generator.print("} ");
+ } else {
+ generator.outdent();
+ generator.print("}\n");
+ }
+ }
}
- for (final long value : field.getFixed64List()) {
- generator.print(entry.getKey().toString());
+ }
+
+ private void printUnknownField(final int number,
+ final int wireType,
+ final List<?> values,
+ final TextGenerator generator)
+ throws IOException {
+ for (final Object value : values) {
+ generator.print(String.valueOf(number));
generator.print(": ");
- generator.print(String.format((Locale) null, "0x%016x", value));
- generator.print("\n");
- }
- for (final ByteString value : field.getLengthDelimitedList()) {
- generator.print(entry.getKey().toString());
- generator.print(": \"");
- generator.print(escapeBytes(value));
- generator.print("\"\n");
- }
- for (final UnknownFieldSet value : field.getGroupList()) {
- generator.print(entry.getKey().toString());
- generator.print(" {\n");
- generator.indent();
- printUnknownFields(value, generator);
- generator.outdent();
- generator.print("}\n");
+ printUnknownFieldValue(wireType, value, generator);
+ generator.print(singleLineMode ? " " : "\n");
}
}
}
@@ -312,9 +433,9 @@ public final class TextFormat {
* An inner class for writing text to the output stream.
*/
private static final class TextGenerator {
- private Appendable output;
- private boolean atStartOfLine = true;
+ private final Appendable output;
private final StringBuilder indent = new StringBuilder();
+ private boolean atStartOfLine = true;
private TextGenerator(final Appendable output) {
this.output = output;
@@ -670,10 +791,14 @@ public final class TextFormat {
* Otherwise, throw a {@link ParseException}.
*/
public boolean consumeBoolean() throws ParseException {
- if (currentToken.equals("true")) {
+ if (currentToken.equals("true") ||
+ currentToken.equals("t") ||
+ currentToken.equals("1")) {
nextToken();
return true;
- } else if (currentToken.equals("false")) {
+ } else if (currentToken.equals("false") ||
+ currentToken.equals("f") ||
+ currentToken.equals("0")) {
nextToken();
return false;
} else {
@@ -1063,6 +1188,9 @@ public final class TextFormat {
case '\'': builder.append("\\\'"); break;
case '"' : builder.append("\\\""); break;
default:
+ // Note: Bytes with the high-order bit set should be escaped. Since
+ // bytes are signed, such bytes will compare less than 0x20, hence
+ // the following line is correct.
if (b >= 0x20) {
builder.append((char) b);
} else {
@@ -1082,27 +1210,37 @@ public final class TextFormat {
* {@link #escapeBytes(ByteString)}. Two-digit hex escapes (starting with
* "\x") are also recognized.
*/
- static ByteString unescapeBytes(final CharSequence input)
+ static ByteString unescapeBytes(final CharSequence charString)
throws InvalidEscapeSequenceException {
- final byte[] result = new byte[input.length()];
+ // First convert the Java characater sequence to UTF-8 bytes.
+ ByteString input = ByteString.copyFromUtf8(charString.toString());
+ // Then unescape certain byte sequences introduced by ASCII '\\'. The valid
+ // escapes can all be expressed with ASCII characters, so it is safe to
+ // operate on bytes here.
+ //
+ // Unescaping the input byte array will result in a byte sequence that's no
+ // longer than the input. That's because each escape sequence is between
+ // two and four bytes long and stands for a single byte.
+ final byte[] result = new byte[input.size()];
int pos = 0;
- for (int i = 0; i < input.length(); i++) {
- char c = input.charAt(i);
+ for (int i = 0; i < input.size(); i++) {
+ byte c = input.byteAt(i);
if (c == '\\') {
- if (i + 1 < input.length()) {
+ if (i + 1 < input.size()) {
++i;
- c = input.charAt(i);
+ c = input.byteAt(i);
if (isOctal(c)) {
// Octal escape.
int code = digitValue(c);
- if (i + 1 < input.length() && isOctal(input.charAt(i + 1))) {
+ if (i + 1 < input.size() && isOctal(input.byteAt(i + 1))) {
++i;
- code = code * 8 + digitValue(input.charAt(i));
+ code = code * 8 + digitValue(input.byteAt(i));
}
- if (i + 1 < input.length() && isOctal(input.charAt(i + 1))) {
+ if (i + 1 < input.size() && isOctal(input.byteAt(i + 1))) {
++i;
- code = code * 8 + digitValue(input.charAt(i));
+ code = code * 8 + digitValue(input.byteAt(i));
}
+ // TODO: Check that 0 <= code && code <= 0xFF.
result[pos++] = (byte)code;
} else {
switch (c) {
@@ -1120,31 +1258,31 @@ public final class TextFormat {
case 'x':
// hex escape
int code = 0;
- if (i + 1 < input.length() && isHex(input.charAt(i + 1))) {
+ if (i + 1 < input.size() && isHex(input.byteAt(i + 1))) {
++i;
- code = digitValue(input.charAt(i));
+ code = digitValue(input.byteAt(i));
} else {
throw new InvalidEscapeSequenceException(
- "Invalid escape sequence: '\\x' with no digits");
+ "Invalid escape sequence: '\\x' with no digits");
}
- if (i + 1 < input.length() && isHex(input.charAt(i + 1))) {
+ if (i + 1 < input.size() && isHex(input.byteAt(i + 1))) {
++i;
- code = code * 16 + digitValue(input.charAt(i));
+ code = code * 16 + digitValue(input.byteAt(i));
}
result[pos++] = (byte)code;
break;
default:
throw new InvalidEscapeSequenceException(
- "Invalid escape sequence: '\\" + c + '\'');
+ "Invalid escape sequence: '\\" + (char)c + '\'');
}
}
} else {
throw new InvalidEscapeSequenceException(
- "Invalid escape sequence: '\\' at end of string.");
+ "Invalid escape sequence: '\\' at end of string.");
}
} else {
- result[pos++] = (byte)c;
+ result[pos++] = c;
}
}
@@ -1182,12 +1320,12 @@ public final class TextFormat {
}
/** Is this an octal digit? */
- private static boolean isOctal(final char c) {
+ private static boolean isOctal(final byte c) {
return '0' <= c && c <= '7';
}
/** Is this a hex digit? */
- private static boolean isHex(final char c) {
+ private static boolean isHex(final byte c) {
return ('0' <= c && c <= '9') ||
('a' <= c && c <= 'f') ||
('A' <= c && c <= 'F');
@@ -1198,7 +1336,7 @@ public final class TextFormat {
* numeric value. This is like {@code Character.digit()} but we don't accept
* non-ASCII digits.
*/
- private static int digitValue(final char c) {
+ private static int digitValue(final byte c) {
if ('0' <= c && c <= '9') {
return c - '0';
} else if ('a' <= c && c <= 'z') {
diff --git a/java/src/main/java/com/google/protobuf/UnmodifiableLazyStringList.java b/java/src/main/java/com/google/protobuf/UnmodifiableLazyStringList.java
new file mode 100644
index 00000000..ee8fe190
--- /dev/null
+++ b/java/src/main/java/com/google/protobuf/UnmodifiableLazyStringList.java
@@ -0,0 +1,146 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// http://code.google.com/p/protobuf/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import java.util.AbstractList;
+import java.util.RandomAccess;
+import java.util.ListIterator;
+import java.util.Iterator;
+
+/**
+ * An implementation of {@link LazyStringList} that wraps another
+ * {@link LazyStringList} such that it cannot be modified via the wrapper.
+ *
+ * @author jonp@google.com (Jon Perlow)
+ */
+public class UnmodifiableLazyStringList extends AbstractList<String>
+ implements LazyStringList, RandomAccess {
+
+ private final LazyStringList list;
+
+ public UnmodifiableLazyStringList(LazyStringList list) {
+ this.list = list;
+ }
+
+ @Override
+ public String get(int index) {
+ return list.get(index);
+ }
+
+ @Override
+ public int size() {
+ return list.size();
+ }
+
+ @Override
+ public ByteString getByteString(int index) {
+ return list.getByteString(index);
+ }
+
+ @Override
+ public void add(ByteString element) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ListIterator<String> listIterator(final int index) {
+ return new ListIterator<String>() {
+ ListIterator<String> iter = list.listIterator(index);
+
+ @Override
+ public boolean hasNext() {
+ return iter.hasNext();
+ }
+
+ @Override
+ public String next() {
+ return iter.next();
+ }
+
+ @Override
+ public boolean hasPrevious() {
+ return iter.hasPrevious();
+ }
+
+ @Override
+ public String previous() {
+ return iter.previous();
+ }
+
+ @Override
+ public int nextIndex() {
+ return iter.nextIndex();
+ }
+
+ @Override
+ public int previousIndex() {
+ return iter.previousIndex();
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void set(String o) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void add(String o) {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+
+ @Override
+ public Iterator<String> iterator() {
+ return new Iterator<String>() {
+ Iterator<String> iter = list.iterator();
+
+ @Override
+ public boolean hasNext() {
+ return iter.hasNext();
+ }
+
+ @Override
+ public String next() {
+ return iter.next();
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+}
diff --git a/java/src/main/java/com/google/protobuf/WireFormat.java b/java/src/main/java/com/google/protobuf/WireFormat.java
index c46f7b0a..a30f2a3c 100644
--- a/java/src/main/java/com/google/protobuf/WireFormat.java
+++ b/java/src/main/java/com/google/protobuf/WireFormat.java
@@ -45,12 +45,12 @@ 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;
+ public static final int WIRETYPE_VARINT = 0;
+ public static final int WIRETYPE_FIXED64 = 1;
+ public static final int WIRETYPE_LENGTH_DELIMITED = 2;
+ public static final int WIRETYPE_START_GROUP = 3;
+ public static final int WIRETYPE_END_GROUP = 4;
+ public static final int WIRETYPE_FIXED32 = 5;
static final int TAG_TYPE_BITS = 3;
static final int TAG_TYPE_MASK = (1 << TAG_TYPE_BITS) - 1;
diff --git a/java/src/test/java/com/google/protobuf/AbstractMessageTest.java b/java/src/test/java/com/google/protobuf/AbstractMessageTest.java
index c44d6605..d53ce8d7 100644
--- a/java/src/test/java/com/google/protobuf/AbstractMessageTest.java
+++ b/java/src/test/java/com/google/protobuf/AbstractMessageTest.java
@@ -366,7 +366,7 @@ public class AbstractMessageTest extends TestCase {
// -----------------------------------------------------------------
// Tests for equals and hashCode
-
+
public void testEqualsAndHashCode() throws Exception {
TestAllTypes a = TestUtil.getAllSet();
TestAllTypes b = TestAllTypes.newBuilder().build();
@@ -382,7 +382,7 @@ public class AbstractMessageTest extends TestCase {
checkEqualsIsConsistent(d);
checkEqualsIsConsistent(e);
checkEqualsIsConsistent(f);
-
+
checkNotEqual(a, b);
checkNotEqual(a, c);
checkNotEqual(a, d);
@@ -413,19 +413,20 @@ public class AbstractMessageTest extends TestCase {
checkEqualsIsConsistent(eUnknownFields);
checkEqualsIsConsistent(fUnknownFields);
- // Subseqent reconstitutions should be identical
+ // Subsequent reconstitutions should be identical
UnittestProto.TestEmptyMessage eUnknownFields2 =
UnittestProto.TestEmptyMessage.parseFrom(e.toByteArray());
checkEqualsIsConsistent(eUnknownFields, eUnknownFields2);
}
-
+
+
/**
* Asserts that the given proto has symetric equals and hashCode methods.
*/
private void checkEqualsIsConsistent(Message message) {
// Object should be equal to itself.
assertEquals(message, message);
-
+
// Object should be equal to a dynamic copy of itself.
DynamicMessage dynamic = DynamicMessage.newBuilder(message).build();
checkEqualsIsConsistent(message, dynamic);
@@ -442,7 +443,7 @@ public class AbstractMessageTest extends TestCase {
/**
* 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.
diff --git a/java/src/test/java/com/google/protobuf/CodedInputStreamTest.java b/java/src/test/java/com/google/protobuf/CodedInputStreamTest.java
index 6acd3223..83f7f8da 100644
--- a/java/src/test/java/com/google/protobuf/CodedInputStreamTest.java
+++ b/java/src/test/java/com/google/protobuf/CodedInputStreamTest.java
@@ -325,6 +325,27 @@ public class CodedInputStreamTest extends TestCase {
assertEquals(2, input.readRawByte());
}
+ /**
+ * Test that a bug in skipRawBytes() has been fixed: if the skip skips
+ * past the end of a buffer with a limit that has been set past the end of
+ * that buffer, this should not break things.
+ */
+ public void testSkipRawBytesPastEndOfBufferWithLimit() throws Exception {
+ byte[] rawBytes = new byte[] { 1, 2, 3, 4, 5 };
+ CodedInputStream input = CodedInputStream.newInstance(
+ new SmallBlockInputStream(rawBytes, 3));
+
+ int limit = input.pushLimit(4);
+ // In order to expose the bug we need to read at least one byte to prime the
+ // buffer inside the CodedInputStream.
+ assertEquals(1, input.readRawByte());
+ // Skip to the end of the limit.
+ input.skipRawBytes(3);
+ assertTrue(input.isAtEnd());
+ input.popLimit(limit);
+ assertEquals(5, input.readRawByte());
+ }
+
public void testReadHugeBlob() throws Exception {
// Allocate and initialize a 1MB blob.
byte[] blob = new byte[1 << 20];
diff --git a/java/src/test/java/com/google/protobuf/DeprecatedFieldTest.java b/java/src/test/java/com/google/protobuf/DeprecatedFieldTest.java
new file mode 100644
index 00000000..1f8bb445
--- /dev/null
+++ b/java/src/test/java/com/google/protobuf/DeprecatedFieldTest.java
@@ -0,0 +1,80 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// http://code.google.com/p/protobuf/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import protobuf_unittest.UnittestProto.TestDeprecatedFields;
+
+import junit.framework.TestCase;
+
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Method;
+/**
+ * Test field deprecation
+ *
+ * @author birdo@google.com (Roberto Scaramuzzi)
+ */
+public class DeprecatedFieldTest extends TestCase {
+ private String[] deprecatedGetterNames = {
+ "hasDeprecatedInt32",
+ "getDeprecatedInt32"};
+
+ private String[] deprecatedBuilderGetterNames = {
+ "hasDeprecatedInt32",
+ "getDeprecatedInt32",
+ "clearDeprecatedInt32"};
+
+ private String[] deprecatedBuilderSetterNames = {
+ "setDeprecatedInt32"};
+
+ public void testDeprecatedField() throws Exception {
+ Class<?> deprecatedFields = TestDeprecatedFields.class;
+ Class<?> deprecatedFieldsBuilder = TestDeprecatedFields.Builder.class;
+ for (String name : deprecatedGetterNames) {
+ Method method = deprecatedFields.getMethod(name);
+ assertTrue("Method " + name + " should be deprecated",
+ isDeprecated(method));
+ }
+ for (String name : deprecatedBuilderGetterNames) {
+ Method method = deprecatedFieldsBuilder.getMethod(name);
+ assertTrue("Method " + name + " should be deprecated",
+ isDeprecated(method));
+ }
+ for (String name : deprecatedBuilderSetterNames) {
+ Method method = deprecatedFieldsBuilder.getMethod(name, int.class);
+ assertTrue("Method " + name + " should be deprecated",
+ isDeprecated(method));
+ }
+ }
+
+ private boolean isDeprecated(AnnotatedElement annotated) {
+ return annotated.isAnnotationPresent(Deprecated.class);
+ }
+}
diff --git a/java/src/test/java/com/google/protobuf/DescriptorsTest.java b/java/src/test/java/com/google/protobuf/DescriptorsTest.java
index c77af5d2..65d06e32 100644
--- a/java/src/test/java/com/google/protobuf/DescriptorsTest.java
+++ b/java/src/test/java/com/google/protobuf/DescriptorsTest.java
@@ -44,6 +44,7 @@ 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;
diff --git a/java/src/test/java/com/google/protobuf/ForceFieldBuildersPreRun.java b/java/src/test/java/com/google/protobuf/ForceFieldBuildersPreRun.java
new file mode 100644
index 00000000..108a28e1
--- /dev/null
+++ b/java/src/test/java/com/google/protobuf/ForceFieldBuildersPreRun.java
@@ -0,0 +1,48 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// http://code.google.com/p/protobuf/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+/**
+ * A prerun for a test suite that allows running the full protocol buffer
+ * tests in a mode that disables the optimization for not using
+ * {@link RepeatedFieldBuilder} and {@link SingleFieldBuilder} until they are
+ * requested. This allows us to run all the tests through both code paths
+ * and ensures that both code paths produce identical results.
+ *
+ * @author jonp@google.com (Jon Perlow)
+ */
+public class ForceFieldBuildersPreRun implements Runnable {
+
+ @Override
+ public void run() {
+ GeneratedMessage.enableAlwaysUseFieldBuildersForTesting();
+ }
+}
diff --git a/java/src/test/java/com/google/protobuf/GeneratedMessageTest.java b/java/src/test/java/com/google/protobuf/GeneratedMessageTest.java
index 73c71f31..3675e003 100644
--- a/java/src/test/java/com/google/protobuf/GeneratedMessageTest.java
+++ b/java/src/test/java/com/google/protobuf/GeneratedMessageTest.java
@@ -30,26 +30,43 @@
package com.google.protobuf;
+import com.google.protobuf.UnittestLite.TestAllExtensionsLite;
+import com.google.protobuf.test.UnittestImport;
+import protobuf_unittest.EnumWithNoOuter;
+import protobuf_unittest.MessageWithNoOuter;
+import protobuf_unittest.MultipleFilesTestProto;
+import protobuf_unittest.NestedExtension.MyNestedExtension;
+import protobuf_unittest.NestedExtensionLite.MyNestedExtensionLite;
+import protobuf_unittest.NonNestedExtension;
+import protobuf_unittest.NonNestedExtension.MessageToBeExtended;
+import protobuf_unittest.NonNestedExtension.MyNonNestedExtension;
+import protobuf_unittest.NonNestedExtensionLite;
+import protobuf_unittest.NonNestedExtensionLite.MessageLiteToBeExtended;
+import protobuf_unittest.NonNestedExtensionLite.MyNonNestedExtensionLite;
+import protobuf_unittest.ServiceWithNoOuter;
import protobuf_unittest.UnittestOptimizeFor.TestOptimizedForSize;
import protobuf_unittest.UnittestOptimizeFor.TestOptionalOptimizedForSize;
import protobuf_unittest.UnittestOptimizeFor.TestRequiredOptimizedForSize;
import protobuf_unittest.UnittestProto;
-import protobuf_unittest.UnittestProto.ForeignMessage;
import protobuf_unittest.UnittestProto.ForeignEnum;
-import protobuf_unittest.UnittestProto.TestAllTypes;
+import protobuf_unittest.UnittestProto.ForeignMessage;
+import protobuf_unittest.UnittestProto.ForeignMessageOrBuilder;
import protobuf_unittest.UnittestProto.TestAllExtensions;
+import protobuf_unittest.UnittestProto.TestAllTypes;
+import protobuf_unittest.UnittestProto.TestAllTypesOrBuilder;
import protobuf_unittest.UnittestProto.TestExtremeDefaultValues;
import protobuf_unittest.UnittestProto.TestPackedTypes;
import protobuf_unittest.UnittestProto.TestUnpackedTypes;
-import protobuf_unittest.MultipleFilesTestProto;
-import protobuf_unittest.MessageWithNoOuter;
-import protobuf_unittest.EnumWithNoOuter;
-import protobuf_unittest.ServiceWithNoOuter;
-import com.google.protobuf.UnittestLite;
-import com.google.protobuf.UnittestLite.TestAllExtensionsLite;
import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
/**
* Unit test for generated messages and generated code. See also
@@ -68,32 +85,111 @@ public class GeneratedMessageTest extends TestCase {
TestAllTypes.newBuilder().getDefaultInstanceForType());
}
- public void testAccessors() throws Exception {
+ public void testMessageOrBuilder() throws Exception {
TestAllTypes.Builder builder = TestAllTypes.newBuilder();
TestUtil.setAllFields(builder);
TestAllTypes message = builder.build();
TestUtil.assertAllFieldsSet(message);
}
- public void testDoubleBuildError() throws Exception {
+ public void testUsingBuilderMultipleTimes() throws Exception {
TestAllTypes.Builder builder = TestAllTypes.newBuilder();
- builder.build();
- try {
- builder.build();
- fail("Should have thrown exception.");
- } catch (IllegalStateException e) {
- // Success.
- }
+ // primitive field scalar and repeated
+ builder.setOptionalSfixed64(100);
+ builder.addRepeatedInt32(100);
+ // enum field scalar and repeated
+ builder.setOptionalImportEnum(UnittestImport.ImportEnum.IMPORT_BAR);
+ builder.addRepeatedImportEnum(UnittestImport.ImportEnum.IMPORT_BAR);
+ // proto field scalar and repeated
+ builder.setOptionalForeignMessage(ForeignMessage.newBuilder().setC(1));
+ builder.addRepeatedForeignMessage(ForeignMessage.newBuilder().setC(1));
+
+ TestAllTypes value1 = builder.build();
+
+ assertEquals(100, value1.getOptionalSfixed64());
+ assertEquals(100, value1.getRepeatedInt32(0));
+ assertEquals(UnittestImport.ImportEnum.IMPORT_BAR,
+ value1.getOptionalImportEnum());
+ assertEquals(UnittestImport.ImportEnum.IMPORT_BAR,
+ value1.getRepeatedImportEnum(0));
+ assertEquals(1, value1.getOptionalForeignMessage().getC());
+ assertEquals(1, value1.getRepeatedForeignMessage(0).getC());
+
+ // Make sure that builder didn't update previously created values
+ builder.setOptionalSfixed64(200);
+ builder.setRepeatedInt32(0, 200);
+ builder.setOptionalImportEnum(UnittestImport.ImportEnum.IMPORT_FOO);
+ builder.setRepeatedImportEnum(0, UnittestImport.ImportEnum.IMPORT_FOO);
+ builder.setOptionalForeignMessage(ForeignMessage.newBuilder().setC(2));
+ builder.setRepeatedForeignMessage(0, ForeignMessage.newBuilder().setC(2));
+
+ TestAllTypes value2 = builder.build();
+
+ // Make sure value1 didn't change.
+ assertEquals(100, value1.getOptionalSfixed64());
+ assertEquals(100, value1.getRepeatedInt32(0));
+ assertEquals(UnittestImport.ImportEnum.IMPORT_BAR,
+ value1.getOptionalImportEnum());
+ assertEquals(UnittestImport.ImportEnum.IMPORT_BAR,
+ value1.getRepeatedImportEnum(0));
+ assertEquals(1, value1.getOptionalForeignMessage().getC());
+ assertEquals(1, value1.getRepeatedForeignMessage(0).getC());
+
+ // Make sure value2 is correct
+ assertEquals(200, value2.getOptionalSfixed64());
+ assertEquals(200, value2.getRepeatedInt32(0));
+ assertEquals(UnittestImport.ImportEnum.IMPORT_FOO,
+ value2.getOptionalImportEnum());
+ assertEquals(UnittestImport.ImportEnum.IMPORT_FOO,
+ value2.getRepeatedImportEnum(0));
+ assertEquals(2, value2.getOptionalForeignMessage().getC());
+ assertEquals(2, value2.getRepeatedForeignMessage(0).getC());
+ }
+
+ public void testProtosShareRepeatedArraysIfDidntChange() throws Exception {
+ TestAllTypes.Builder builder = TestAllTypes.newBuilder();
+ builder.addRepeatedInt32(100);
+ builder.addRepeatedImportEnum(UnittestImport.ImportEnum.IMPORT_BAR);
+ builder.addRepeatedForeignMessage(ForeignMessage.getDefaultInstance());
+
+ TestAllTypes value1 = builder.build();
+ TestAllTypes value2 = value1.toBuilder().build();
+
+ assertSame(value1.getRepeatedInt32List(), value2.getRepeatedInt32List());
+ assertSame(value1.getRepeatedImportEnumList(),
+ value2.getRepeatedImportEnumList());
+ assertSame(value1.getRepeatedForeignMessageList(),
+ value2.getRepeatedForeignMessageList());
}
- public void testClearAfterBuildError() throws Exception {
+ public void testRepeatedArraysAreImmutable() throws Exception {
TestAllTypes.Builder builder = TestAllTypes.newBuilder();
- builder.build();
- try {
- builder.clear();
- fail("Should have thrown exception.");
- } catch (IllegalStateException e) {
- // Success.
+ builder.addRepeatedInt32(100);
+ builder.addRepeatedImportEnum(UnittestImport.ImportEnum.IMPORT_BAR);
+ builder.addRepeatedForeignMessage(ForeignMessage.getDefaultInstance());
+ assertIsUnmodifiable(builder.getRepeatedInt32List());
+ assertIsUnmodifiable(builder.getRepeatedImportEnumList());
+ assertIsUnmodifiable(builder.getRepeatedForeignMessageList());
+ assertIsUnmodifiable(builder.getRepeatedFloatList());
+
+
+ TestAllTypes value = builder.build();
+ assertIsUnmodifiable(value.getRepeatedInt32List());
+ assertIsUnmodifiable(value.getRepeatedImportEnumList());
+ assertIsUnmodifiable(value.getRepeatedForeignMessageList());
+ assertIsUnmodifiable(value.getRepeatedFloatList());
+ }
+
+ private void assertIsUnmodifiable(List<?> list) {
+ if (list == Collections.emptyList()) {
+ // OKAY -- Need to check this b/c EmptyList allows you to call clear.
+ } else {
+ try {
+ list.clear();
+ fail("List wasn't immutable");
+ } catch (UnsupportedOperationException e) {
+ // good
+ }
}
}
@@ -316,9 +412,19 @@ public class GeneratedMessageTest extends TestCase {
assertTrue(Float.isNaN(message.getNanFloat()));
}
+ public void testClear() throws Exception {
+ TestAllTypes.Builder builder = TestAllTypes.newBuilder();
+ TestUtil.assertClear(builder);
+ TestUtil.setAllFields(builder);
+ builder.clear();
+ TestUtil.assertClear(builder);
+ }
+
public void testReflectionGetters() throws Exception {
TestAllTypes.Builder builder = TestAllTypes.newBuilder();
TestUtil.setAllFields(builder);
+ reflectionTester.assertAllFieldsSetViaReflection(builder);
+
TestAllTypes message = builder.build();
reflectionTester.assertAllFieldsSetViaReflection(message);
}
@@ -326,6 +432,8 @@ public class GeneratedMessageTest extends TestCase {
public void testReflectionSetters() throws Exception {
TestAllTypes.Builder builder = TestAllTypes.newBuilder();
reflectionTester.setAllFieldsViaReflection(builder);
+ TestUtil.assertAllFieldsSet(builder);
+
TestAllTypes message = builder.build();
TestUtil.assertAllFieldsSet(message);
}
@@ -339,6 +447,8 @@ public class GeneratedMessageTest extends TestCase {
TestAllTypes.Builder builder = TestAllTypes.newBuilder();
reflectionTester.setAllFieldsViaReflection(builder);
reflectionTester.modifyRepeatedFieldsViaReflection(builder);
+ TestUtil.assertRepeatedFieldsModified(builder);
+
TestAllTypes message = builder.build();
TestUtil.assertRepeatedFieldsModified(message);
}
@@ -391,7 +501,7 @@ public class GeneratedMessageTest extends TestCase {
new TestUtil.ReflectionTester(TestAllExtensions.getDescriptor(),
TestUtil.getExtensionRegistry());
- public void testExtensionAccessors() throws Exception {
+ public void testExtensionMessageOrBuilder() throws Exception {
TestAllExtensions.Builder builder = TestAllExtensions.newBuilder();
TestUtil.setAllExtensions(builder);
TestAllExtensions message = builder.build();
@@ -414,6 +524,8 @@ public class GeneratedMessageTest extends TestCase {
public void testExtensionReflectionGetters() throws Exception {
TestAllExtensions.Builder builder = TestAllExtensions.newBuilder();
TestUtil.setAllExtensions(builder);
+ extensionsReflectionTester.assertAllFieldsSetViaReflection(builder);
+
TestAllExtensions message = builder.build();
extensionsReflectionTester.assertAllFieldsSetViaReflection(message);
}
@@ -421,6 +533,8 @@ public class GeneratedMessageTest extends TestCase {
public void testExtensionReflectionSetters() throws Exception {
TestAllExtensions.Builder builder = TestAllExtensions.newBuilder();
extensionsReflectionTester.setAllFieldsViaReflection(builder);
+ TestUtil.assertAllExtensionsSet(builder);
+
TestAllExtensions message = builder.build();
TestUtil.assertAllExtensionsSet(message);
}
@@ -434,6 +548,8 @@ public class GeneratedMessageTest extends TestCase {
TestAllExtensions.Builder builder = TestAllExtensions.newBuilder();
extensionsReflectionTester.setAllFieldsViaReflection(builder);
extensionsReflectionTester.modifyRepeatedFieldsViaReflection(builder);
+ TestUtil.assertRepeatedExtensionsModified(builder);
+
TestAllExtensions message = builder.build();
TestUtil.assertRepeatedExtensionsModified(message);
}
@@ -491,9 +607,11 @@ public class GeneratedMessageTest extends TestCase {
// lite fields directly since they are implemented exactly the same as
// regular fields.
- public void testLiteExtensionAccessors() throws Exception {
+ public void testLiteExtensionMessageOrBuilder() throws Exception {
TestAllExtensionsLite.Builder builder = TestAllExtensionsLite.newBuilder();
TestUtil.setAllExtensions(builder);
+ TestUtil.assertAllExtensionsSet(builder);
+
TestAllExtensionsLite message = builder.build();
TestUtil.assertAllExtensionsSet(message);
}
@@ -502,6 +620,8 @@ public class GeneratedMessageTest extends TestCase {
TestAllExtensionsLite.Builder builder = TestAllExtensionsLite.newBuilder();
TestUtil.setAllExtensions(builder);
TestUtil.modifyRepeatedExtensions(builder);
+ TestUtil.assertRepeatedExtensionsModified(builder);
+
TestAllExtensionsLite message = builder.build();
TestUtil.assertRepeatedExtensionsModified(message);
}
@@ -609,6 +729,7 @@ public class GeneratedMessageTest extends TestCase {
TestAllTypes.Builder builder = TestAllTypes.newBuilder();
TestUtil.setAllFields(builder);
TestAllTypes message = builder.build();
+ TestUtil.assertAllFieldsSet(message);
TestUtil.assertAllFieldsSet(message.toBuilder().build());
}
@@ -646,4 +767,207 @@ public class GeneratedMessageTest extends TestCase {
assertTrue(message.getA() != null);
assertTrue(message.getA() == message);
}
+
+ public void testSerialize() throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ TestAllTypes.Builder builder = TestAllTypes.newBuilder();
+ TestUtil.setAllFields(builder);
+ TestAllTypes expected = builder.build();
+ ObjectOutputStream out = new ObjectOutputStream(baos);
+ out.writeObject(expected);
+ out.close();
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ ObjectInputStream in = new ObjectInputStream(bais);
+ TestAllTypes actual = (TestAllTypes) in.readObject();
+ assertEquals(expected, actual);
+ }
+
+ public void testSerializePartial() throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ TestAllTypes.Builder builder = TestAllTypes.newBuilder();
+ TestAllTypes expected = builder.buildPartial();
+ ObjectOutputStream out = new ObjectOutputStream(baos);
+ out.writeObject(expected);
+ out.close();
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ ObjectInputStream in = new ObjectInputStream(bais);
+ TestAllTypes actual = (TestAllTypes) in.readObject();
+ assertEquals(expected, actual);
+ }
+
+ public void testEnumValues() {
+ assertEquals(
+ TestAllTypes.NestedEnum.BAR.getNumber(),
+ TestAllTypes.NestedEnum.BAR_VALUE);
+ assertEquals(
+ TestAllTypes.NestedEnum.BAZ.getNumber(),
+ TestAllTypes.NestedEnum.BAZ_VALUE);
+ assertEquals(
+ TestAllTypes.NestedEnum.FOO.getNumber(),
+ TestAllTypes.NestedEnum.FOO_VALUE);
+ }
+
+ public void testNonNestedExtensionInitialization() {
+ assertTrue(NonNestedExtension.nonNestedExtension
+ .getMessageDefaultInstance() instanceof MyNonNestedExtension);
+ assertEquals("nonNestedExtension",
+ NonNestedExtension.nonNestedExtension.getDescriptor().getName());
+ }
+
+ public void testNestedExtensionInitialization() {
+ assertTrue(MyNestedExtension.recursiveExtension.getMessageDefaultInstance()
+ instanceof MessageToBeExtended);
+ assertEquals("recursiveExtension",
+ MyNestedExtension.recursiveExtension.getDescriptor().getName());
+ }
+
+ public void testNonNestedExtensionLiteInitialization() {
+ assertTrue(NonNestedExtensionLite.nonNestedExtensionLite
+ .getMessageDefaultInstance() instanceof MyNonNestedExtensionLite);
+ }
+
+ public void testNestedExtensionLiteInitialization() {
+ assertTrue(MyNestedExtensionLite.recursiveExtensionLite
+ .getMessageDefaultInstance() instanceof MessageLiteToBeExtended);
+ }
+
+ public void testInvalidations() throws Exception {
+ GeneratedMessage.enableAlwaysUseFieldBuildersForTesting();
+ TestAllTypes.NestedMessage nestedMessage1 =
+ TestAllTypes.NestedMessage.newBuilder().build();
+ TestAllTypes.NestedMessage nestedMessage2 =
+ TestAllTypes.NestedMessage.newBuilder().build();
+
+ // Set all three flavors (enum, primitive, message and singular/repeated)
+ // and verify no invalidations fired
+ TestUtil.MockBuilderParent mockParent = new TestUtil.MockBuilderParent();
+
+ TestAllTypes.Builder builder = (TestAllTypes.Builder)
+ ((GeneratedMessage) TestAllTypes.getDefaultInstance()).
+ newBuilderForType(mockParent);
+ builder.setOptionalInt32(1);
+ builder.setOptionalNestedEnum(TestAllTypes.NestedEnum.BAR);
+ builder.setOptionalNestedMessage(nestedMessage1);
+ builder.addRepeatedInt32(1);
+ builder.addRepeatedNestedEnum(TestAllTypes.NestedEnum.BAR);
+ builder.addRepeatedNestedMessage(nestedMessage1);
+ assertEquals(0, mockParent.getInvalidationCount());
+
+ // Now tell it we want changes and make sure it's only fired once
+ // And do this for each flavor
+
+ // primitive single
+ builder.buildPartial();
+ builder.setOptionalInt32(2);
+ builder.setOptionalInt32(3);
+ assertEquals(1, mockParent.getInvalidationCount());
+
+ // enum single
+ builder.buildPartial();
+ builder.setOptionalNestedEnum(TestAllTypes.NestedEnum.BAZ);
+ builder.setOptionalNestedEnum(TestAllTypes.NestedEnum.BAR);
+ assertEquals(2, mockParent.getInvalidationCount());
+
+ // message single
+ builder.buildPartial();
+ builder.setOptionalNestedMessage(nestedMessage2);
+ builder.setOptionalNestedMessage(nestedMessage1);
+ assertEquals(3, mockParent.getInvalidationCount());
+
+ // primitive repated
+ builder.buildPartial();
+ builder.addRepeatedInt32(2);
+ builder.addRepeatedInt32(3);
+ assertEquals(4, mockParent.getInvalidationCount());
+
+ // enum repeated
+ builder.buildPartial();
+ builder.addRepeatedNestedEnum(TestAllTypes.NestedEnum.BAZ);
+ builder.addRepeatedNestedEnum(TestAllTypes.NestedEnum.BAZ);
+ assertEquals(5, mockParent.getInvalidationCount());
+
+ // message repeated
+ builder.buildPartial();
+ builder.addRepeatedNestedMessage(nestedMessage2);
+ builder.addRepeatedNestedMessage(nestedMessage1);
+ assertEquals(6, mockParent.getInvalidationCount());
+
+ }
+
+ public void testInvalidations_Extensions() throws Exception {
+ TestUtil.MockBuilderParent mockParent = new TestUtil.MockBuilderParent();
+
+ TestAllExtensions.Builder builder = (TestAllExtensions.Builder)
+ ((GeneratedMessage) TestAllExtensions.getDefaultInstance()).
+ newBuilderForType(mockParent);
+
+ builder.addExtension(UnittestProto.repeatedInt32Extension, 1);
+ builder.setExtension(UnittestProto.repeatedInt32Extension, 0, 2);
+ builder.clearExtension(UnittestProto.repeatedInt32Extension);
+ assertEquals(0, mockParent.getInvalidationCount());
+
+ // Now tell it we want changes and make sure it's only fired once
+ builder.buildPartial();
+ builder.addExtension(UnittestProto.repeatedInt32Extension, 2);
+ builder.addExtension(UnittestProto.repeatedInt32Extension, 3);
+ assertEquals(1, mockParent.getInvalidationCount());
+
+ builder.buildPartial();
+ builder.setExtension(UnittestProto.repeatedInt32Extension, 0, 4);
+ builder.setExtension(UnittestProto.repeatedInt32Extension, 1, 5);
+ assertEquals(2, mockParent.getInvalidationCount());
+
+ builder.buildPartial();
+ builder.clearExtension(UnittestProto.repeatedInt32Extension);
+ builder.clearExtension(UnittestProto.repeatedInt32Extension);
+ assertEquals(3, mockParent.getInvalidationCount());
+ }
+
+ public void testBaseMessageOrBuilder() {
+ // Mostly just makes sure the base interface exists and has some methods.
+ TestAllTypes.Builder builder = TestAllTypes.newBuilder();
+ TestAllTypes message = builder.buildPartial();
+ TestAllTypesOrBuilder builderAsInterface = (TestAllTypesOrBuilder) builder;
+ TestAllTypesOrBuilder messageAsInterface = (TestAllTypesOrBuilder) message;
+
+ assertEquals(
+ messageAsInterface.getDefaultBool(),
+ messageAsInterface.getDefaultBool());
+ assertEquals(
+ messageAsInterface.getOptionalDouble(),
+ messageAsInterface.getOptionalDouble());
+ }
+
+ public void testMessageOrBuilderGetters() {
+ TestAllTypes.Builder builder = TestAllTypes.newBuilder();
+
+ // single fields
+ assertSame(ForeignMessage.getDefaultInstance(),
+ builder.getOptionalForeignMessageOrBuilder());
+ ForeignMessage.Builder subBuilder =
+ builder.getOptionalForeignMessageBuilder();
+ assertSame(subBuilder, builder.getOptionalForeignMessageOrBuilder());
+
+ // repeated fields
+ ForeignMessage m0 = ForeignMessage.newBuilder().buildPartial();
+ ForeignMessage m1 = ForeignMessage.newBuilder().buildPartial();
+ ForeignMessage m2 = ForeignMessage.newBuilder().buildPartial();
+ builder.addRepeatedForeignMessage(m0);
+ builder.addRepeatedForeignMessage(m1);
+ builder.addRepeatedForeignMessage(m2);
+ assertSame(m0, builder.getRepeatedForeignMessageOrBuilder(0));
+ assertSame(m1, builder.getRepeatedForeignMessageOrBuilder(1));
+ assertSame(m2, builder.getRepeatedForeignMessageOrBuilder(2));
+ ForeignMessage.Builder b0 = builder.getRepeatedForeignMessageBuilder(0);
+ ForeignMessage.Builder b1 = builder.getRepeatedForeignMessageBuilder(1);
+ assertSame(b0, builder.getRepeatedForeignMessageOrBuilder(0));
+ assertSame(b1, builder.getRepeatedForeignMessageOrBuilder(1));
+ assertSame(m2, builder.getRepeatedForeignMessageOrBuilder(2));
+
+ List<? extends ForeignMessageOrBuilder> messageOrBuilderList =
+ builder.getRepeatedForeignMessageOrBuilderList();
+ assertSame(b0, messageOrBuilderList.get(0));
+ assertSame(b1, messageOrBuilderList.get(1));
+ assertSame(m2, messageOrBuilderList.get(2));
+ }
}
diff --git a/java/src/test/java/com/google/protobuf/LazyStringArrayListTest.java b/java/src/test/java/com/google/protobuf/LazyStringArrayListTest.java
new file mode 100644
index 00000000..4dcdc74d
--- /dev/null
+++ b/java/src/test/java/com/google/protobuf/LazyStringArrayListTest.java
@@ -0,0 +1,118 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// http://code.google.com/p/protobuf/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for {@link LazyStringArrayList}.
+ *
+ * @author jonp@google.com (Jon Perlow)
+ */
+public class LazyStringArrayListTest extends TestCase {
+
+ private static String STRING_A = "A";
+ private static String STRING_B = "B";
+ private static String STRING_C = "C";
+
+ private static ByteString BYTE_STRING_A = ByteString.copyFromUtf8("A");
+ private static ByteString BYTE_STRING_B = ByteString.copyFromUtf8("B");
+ private static ByteString BYTE_STRING_C = ByteString.copyFromUtf8("C");
+
+ public void testJustStrings() {
+ LazyStringArrayList list = new LazyStringArrayList();
+ list.add(STRING_A);
+ list.add(STRING_B);
+ list.add(STRING_C);
+
+ assertEquals(3, list.size());
+ assertSame(STRING_A, list.get(0));
+ assertSame(STRING_B, list.get(1));
+ assertSame(STRING_C, list.get(2));
+
+ list.set(1, STRING_C);
+ assertSame(STRING_C, list.get(1));
+
+ list.remove(1);
+ assertSame(STRING_A, list.get(0));
+ assertSame(STRING_C, list.get(1));
+ }
+
+ public void testJustByteString() {
+ LazyStringArrayList list = new LazyStringArrayList();
+ list.add(BYTE_STRING_A);
+ list.add(BYTE_STRING_B);
+ list.add(BYTE_STRING_C);
+
+ assertEquals(3, list.size());
+ assertSame(BYTE_STRING_A, list.getByteString(0));
+ assertSame(BYTE_STRING_B, list.getByteString(1));
+ assertSame(BYTE_STRING_C, list.getByteString(2));
+
+ list.remove(1);
+ assertSame(BYTE_STRING_A, list.getByteString(0));
+ assertSame(BYTE_STRING_C, list.getByteString(1));
+ }
+
+ public void testConversionBackAndForth() {
+ LazyStringArrayList list = new LazyStringArrayList();
+ list.add(STRING_A);
+ list.add(BYTE_STRING_B);
+ list.add(BYTE_STRING_C);
+
+ // String a should be the same because it was originally a string
+ assertSame(STRING_A, list.get(0));
+
+ // String b and c should be different because the string has to be computed
+ // from the ByteString
+ String bPrime = list.get(1);
+ assertNotSame(STRING_B, bPrime);
+ assertEquals(STRING_B, bPrime);
+ String cPrime = list.get(2);
+ assertNotSame(STRING_C, cPrime);
+ assertEquals(STRING_C, cPrime);
+
+ // String c and c should stay the same once cached.
+ assertSame(bPrime, list.get(1));
+ assertSame(cPrime, list.get(2));
+
+ // ByteString needs to be computed from string for both a and b
+ ByteString aPrimeByteString = list.getByteString(0);
+ assertEquals(BYTE_STRING_A, aPrimeByteString);
+ ByteString bPrimeByteString = list.getByteString(1);
+ assertNotSame(BYTE_STRING_B, bPrimeByteString);
+ assertEquals(BYTE_STRING_B, list.getByteString(1));
+
+ // Once cached, ByteString should stay cached.
+ assertSame(aPrimeByteString, list.getByteString(0));
+ assertSame(bPrimeByteString, list.getByteString(1));
+ }
+}
diff --git a/java/src/test/java/com/google/protobuf/LazyStringEndToEndTest.java b/java/src/test/java/com/google/protobuf/LazyStringEndToEndTest.java
new file mode 100644
index 00000000..e6870b51
--- /dev/null
+++ b/java/src/test/java/com/google/protobuf/LazyStringEndToEndTest.java
@@ -0,0 +1,115 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// http://code.google.com/p/protobuf/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+
+import protobuf_unittest.UnittestProto;
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+
+/**
+ * Tests to make sure the lazy conversion of UTF8-encoded byte arrays to
+ * strings works correctly.
+ *
+ * @author jonp@google.com (Jon Perlow)
+ */
+public class LazyStringEndToEndTest extends TestCase {
+
+ private static ByteString TEST_ALL_TYPES_SERIALIZED_WITH_ILLEGAL_UTF8 =
+ ByteString.copyFrom(new byte[] {
+ 114, 4, -1, 0, -1, 0, -30, 2, 4, -1,
+ 0, -1, 0, -30, 2, 4, -1, 0, -1, 0, });
+
+ /**
+ * Tests that an invalid UTF8 string will roundtrip through a parse
+ * and serialization.
+ */
+ public void testParseAndSerialize() throws InvalidProtocolBufferException {
+ UnittestProto.TestAllTypes tV2 = UnittestProto.TestAllTypes.parseFrom(
+ TEST_ALL_TYPES_SERIALIZED_WITH_ILLEGAL_UTF8);
+ ByteString bytes = tV2.toByteString();
+ assertEquals(TEST_ALL_TYPES_SERIALIZED_WITH_ILLEGAL_UTF8, bytes);
+
+ tV2.getOptionalString();
+ bytes = tV2.toByteString();
+ assertEquals(TEST_ALL_TYPES_SERIALIZED_WITH_ILLEGAL_UTF8, bytes);
+ }
+
+ public void testParseAndWrite() throws IOException {
+ UnittestProto.TestAllTypes tV2 = UnittestProto.TestAllTypes.parseFrom(
+ TEST_ALL_TYPES_SERIALIZED_WITH_ILLEGAL_UTF8);
+ byte[] sink = new byte[TEST_ALL_TYPES_SERIALIZED_WITH_ILLEGAL_UTF8.size()];
+ CodedOutputStream outputStream = CodedOutputStream.newInstance(sink);
+ tV2.writeTo(outputStream);
+ outputStream.flush();
+ assertEquals(
+ TEST_ALL_TYPES_SERIALIZED_WITH_ILLEGAL_UTF8,
+ ByteString.copyFrom(sink));
+ }
+
+ public void testCaching() {
+ String a = "a";
+ String b = "b";
+ String c = "c";
+ UnittestProto.TestAllTypes proto = UnittestProto.TestAllTypes.newBuilder()
+ .setOptionalString(a)
+ .addRepeatedString(b)
+ .addRepeatedString(c)
+ .build();
+
+ // String should be the one we passed it.
+ assertSame(a, proto.getOptionalString());
+ assertSame(b, proto.getRepeatedString(0));
+ assertSame(c, proto.getRepeatedString(1));
+
+
+ // There's no way to directly observe that the ByteString is cached
+ // correctly on serialization, but we can observe that it had to recompute
+ // the string after serialization.
+ proto.toByteString();
+ String aPrime = proto.getOptionalString();
+ assertNotSame(a, aPrime);
+ assertEquals(a, aPrime);
+ String bPrime = proto.getRepeatedString(0);
+ assertNotSame(b, bPrime);
+ assertEquals(b, bPrime);
+ String cPrime = proto.getRepeatedString(1);
+ assertNotSame(c, cPrime);
+ assertEquals(c, cPrime);
+
+ // And now the string should stay cached.
+ assertSame(aPrime, proto.getOptionalString());
+ assertSame(bPrime, proto.getRepeatedString(0));
+ assertSame(cPrime, proto.getRepeatedString(1));
+ }
+}
diff --git a/java/src/test/java/com/google/protobuf/LiteTest.java b/java/src/test/java/com/google/protobuf/LiteTest.java
index 728bad9d..4e1003d9 100644
--- a/java/src/test/java/com/google/protobuf/LiteTest.java
+++ b/java/src/test/java/com/google/protobuf/LiteTest.java
@@ -37,6 +37,11 @@ import com.google.protobuf.UnittestLite.TestNestedExtensionLite;
import junit.framework.TestCase;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
/**
* Test lite runtime.
*
@@ -113,4 +118,28 @@ public class LiteTest extends TestCase {
assertEquals(7, message2.getExtension(
UnittestLite.optionalNestedMessageExtensionLite).getBb());
}
+
+ public void testSerialize() throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ TestAllTypesLite expected =
+ TestAllTypesLite.newBuilder()
+ .setOptionalInt32(123)
+ .addRepeatedString("hello")
+ .setOptionalNestedMessage(
+ TestAllTypesLite.NestedMessage.newBuilder().setBb(7))
+ .build();
+ ObjectOutputStream out = new ObjectOutputStream(baos);
+ out.writeObject(expected);
+ out.close();
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ ObjectInputStream in = new ObjectInputStream(bais);
+ TestAllTypesLite actual = (TestAllTypesLite) in.readObject();
+ assertEquals(expected.getOptionalInt32(), actual.getOptionalInt32());
+ assertEquals(expected.getRepeatedStringCount(),
+ actual.getRepeatedStringCount());
+ assertEquals(expected.getRepeatedString(0),
+ actual.getRepeatedString(0));
+ assertEquals(expected.getOptionalNestedMessage().getBb(),
+ actual.getOptionalNestedMessage().getBb());
+ }
}
diff --git a/java/src/test/java/com/google/protobuf/NestedBuildersTest.java b/java/src/test/java/com/google/protobuf/NestedBuildersTest.java
new file mode 100644
index 00000000..f5375801
--- /dev/null
+++ b/java/src/test/java/com/google/protobuf/NestedBuildersTest.java
@@ -0,0 +1,185 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// http://code.google.com/p/protobuf/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import protobuf_unittest.Vehicle;
+import protobuf_unittest.Wheel;
+
+import junit.framework.TestCase;
+
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Test cases that exercise end-to-end use cases involving
+ * {@link SingleFieldBuilder} and {@link RepeatedFieldBuilder}.
+ *
+ * @author jonp@google.com (Jon Perlow)
+ */
+public class NestedBuildersTest extends TestCase {
+
+ public void testMessagesAndBuilders() {
+ Vehicle.Builder vehicleBuilder = Vehicle.newBuilder();
+ vehicleBuilder.addWheelBuilder()
+ .setRadius(4)
+ .setWidth(1);
+ vehicleBuilder.addWheelBuilder()
+ .setRadius(4)
+ .setWidth(2);
+ vehicleBuilder.addWheelBuilder()
+ .setRadius(4)
+ .setWidth(3);
+ vehicleBuilder.addWheelBuilder()
+ .setRadius(4)
+ .setWidth(4);
+ vehicleBuilder.getEngineBuilder()
+ .setLiters(10);
+
+ Vehicle vehicle = vehicleBuilder.build();
+ assertEquals(4, vehicle.getWheelCount());
+ for (int i = 0; i < 4; i++) {
+ Wheel wheel = vehicle.getWheel(i);
+ assertEquals(4, wheel.getRadius());
+ assertEquals(i + 1, wheel.getWidth());
+ }
+ assertEquals(10, vehicle.getEngine().getLiters());
+
+ for (int i = 0; i < 4; i++) {
+ vehicleBuilder.getWheelBuilder(i)
+ .setRadius(5)
+ .setWidth(i + 10);
+ }
+ vehicleBuilder.getEngineBuilder().setLiters(20);
+
+ vehicle = vehicleBuilder.build();
+ for (int i = 0; i < 4; i++) {
+ Wheel wheel = vehicle.getWheel(i);
+ assertEquals(5, wheel.getRadius());
+ assertEquals(i + 10, wheel.getWidth());
+ }
+ assertEquals(20, vehicle.getEngine().getLiters());
+ assertTrue(vehicle.hasEngine());
+ }
+
+ public void testMessagesAreCached() {
+ Vehicle.Builder vehicleBuilder = Vehicle.newBuilder();
+ vehicleBuilder.addWheelBuilder()
+ .setRadius(1)
+ .setWidth(2);
+ vehicleBuilder.addWheelBuilder()
+ .setRadius(3)
+ .setWidth(4);
+ vehicleBuilder.addWheelBuilder()
+ .setRadius(5)
+ .setWidth(6);
+ vehicleBuilder.addWheelBuilder()
+ .setRadius(7)
+ .setWidth(8);
+
+ // Make sure messages are cached.
+ List<Wheel> wheels = new ArrayList<Wheel>(vehicleBuilder.getWheelList());
+ for (int i = 0; i < wheels.size(); i++) {
+ assertSame(wheels.get(i), vehicleBuilder.getWheel(i));
+ }
+
+ // Now get builders and check they didn't change.
+ for (int i = 0; i < wheels.size(); i++) {
+ vehicleBuilder.getWheel(i);
+ }
+ for (int i = 0; i < wheels.size(); i++) {
+ assertSame(wheels.get(i), vehicleBuilder.getWheel(i));
+ }
+
+ // Change just one
+ vehicleBuilder.getWheelBuilder(3)
+ .setRadius(20).setWidth(20);
+
+ // Now get wheels and check that only that one changed
+ for (int i = 0; i < wheels.size(); i++) {
+ if (i < 3) {
+ assertSame(wheels.get(i), vehicleBuilder.getWheel(i));
+ } else {
+ assertNotSame(wheels.get(i), vehicleBuilder.getWheel(i));
+ }
+ }
+ }
+
+ public void testRemove_WithNestedBuilders() {
+ Vehicle.Builder vehicleBuilder = Vehicle.newBuilder();
+ vehicleBuilder.addWheelBuilder()
+ .setRadius(1)
+ .setWidth(1);
+ vehicleBuilder.addWheelBuilder()
+ .setRadius(2)
+ .setWidth(2);
+ vehicleBuilder.removeWheel(0);
+
+ assertEquals(1, vehicleBuilder.getWheelCount());
+ assertEquals(2, vehicleBuilder.getWheel(0).getRadius());
+ }
+
+ public void testRemove_WithNestedMessages() {
+ Vehicle.Builder vehicleBuilder = Vehicle.newBuilder();
+ vehicleBuilder.addWheel(Wheel.newBuilder()
+ .setRadius(1)
+ .setWidth(1));
+ vehicleBuilder.addWheel(Wheel.newBuilder()
+ .setRadius(2)
+ .setWidth(2));
+ vehicleBuilder.removeWheel(0);
+
+ assertEquals(1, vehicleBuilder.getWheelCount());
+ assertEquals(2, vehicleBuilder.getWheel(0).getRadius());
+ }
+
+ public void testMerge() {
+ Vehicle vehicle1 = Vehicle.newBuilder()
+ .addWheel(Wheel.newBuilder().setRadius(1).build())
+ .addWheel(Wheel.newBuilder().setRadius(2).build())
+ .build();
+
+ Vehicle vehicle2 = Vehicle.newBuilder()
+ .mergeFrom(vehicle1)
+ .build();
+ // List should be the same -- no allocation
+ assertSame(vehicle1.getWheelList(), vehicle2.getWheelList());
+
+ Vehicle vehicle3 = vehicle1.toBuilder().build();
+ assertSame(vehicle1.getWheelList(), vehicle3.getWheelList());
+ }
+
+ public void testGettingBuilderMarksFieldAsHaving() {
+ Vehicle.Builder vehicleBuilder = Vehicle.newBuilder();
+ vehicleBuilder.getEngineBuilder();
+ Vehicle vehicle = vehicleBuilder.buildPartial();
+ assertTrue(vehicle.hasEngine());
+ }
+}
diff --git a/java/src/test/java/com/google/protobuf/RepeatedFieldBuilderTest.java b/java/src/test/java/com/google/protobuf/RepeatedFieldBuilderTest.java
new file mode 100644
index 00000000..cdcdcb25
--- /dev/null
+++ b/java/src/test/java/com/google/protobuf/RepeatedFieldBuilderTest.java
@@ -0,0 +1,190 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// http://code.google.com/p/protobuf/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import protobuf_unittest.UnittestProto.TestAllTypes;
+import protobuf_unittest.UnittestProto.TestAllTypesOrBuilder;
+
+import junit.framework.TestCase;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Tests for {@link RepeatedFieldBuilder}. This tests basic functionality.
+ * More extensive testing is provided via other tests that exercise the
+ * builder.
+ *
+ * @author jonp@google.com (Jon Perlow)
+ */
+public class RepeatedFieldBuilderTest extends TestCase {
+
+ public void testBasicUse() {
+ TestUtil.MockBuilderParent mockParent = new TestUtil.MockBuilderParent();
+ RepeatedFieldBuilder<TestAllTypes, TestAllTypes.Builder,
+ TestAllTypesOrBuilder> builder = newRepeatedFieldBuilder(mockParent);
+ builder.addMessage(TestAllTypes.newBuilder().setOptionalInt32(0).build());
+ builder.addMessage(TestAllTypes.newBuilder().setOptionalInt32(1).build());
+ assertEquals(0, builder.getMessage(0).getOptionalInt32());
+ assertEquals(1, builder.getMessage(1).getOptionalInt32());
+
+ List<TestAllTypes> list = builder.build();
+ assertEquals(2, list.size());
+ assertEquals(0, list.get(0).getOptionalInt32());
+ assertEquals(1, list.get(1).getOptionalInt32());
+ assertIsUnmodifiable(list);
+
+ // Make sure it doesn't change.
+ List<TestAllTypes> list2 = builder.build();
+ assertSame(list, list2);
+ assertEquals(0, mockParent.getInvalidationCount());
+ }
+
+ public void testGoingBackAndForth() {
+ TestUtil.MockBuilderParent mockParent = new TestUtil.MockBuilderParent();
+ RepeatedFieldBuilder<TestAllTypes, TestAllTypes.Builder,
+ TestAllTypesOrBuilder> builder = newRepeatedFieldBuilder(mockParent);
+ builder.addMessage(TestAllTypes.newBuilder().setOptionalInt32(0).build());
+ builder.addMessage(TestAllTypes.newBuilder().setOptionalInt32(1).build());
+ assertEquals(0, builder.getMessage(0).getOptionalInt32());
+ assertEquals(1, builder.getMessage(1).getOptionalInt32());
+
+ // Convert to list
+ List<TestAllTypes> list = builder.build();
+ assertEquals(2, list.size());
+ assertEquals(0, list.get(0).getOptionalInt32());
+ assertEquals(1, list.get(1).getOptionalInt32());
+ assertIsUnmodifiable(list);
+
+ // Update 0th item
+ assertEquals(0, mockParent.getInvalidationCount());
+ builder.getBuilder(0).setOptionalString("foo");
+ assertEquals(1, mockParent.getInvalidationCount());
+ list = builder.build();
+ assertEquals(2, list.size());
+ assertEquals(0, list.get(0).getOptionalInt32());
+ assertEquals("foo", list.get(0).getOptionalString());
+ assertEquals(1, list.get(1).getOptionalInt32());
+ assertIsUnmodifiable(list);
+ assertEquals(1, mockParent.getInvalidationCount());
+ }
+
+ public void testVariousMethods() {
+ TestUtil.MockBuilderParent mockParent = new TestUtil.MockBuilderParent();
+ RepeatedFieldBuilder<TestAllTypes, TestAllTypes.Builder,
+ TestAllTypesOrBuilder> builder = newRepeatedFieldBuilder(mockParent);
+ builder.addMessage(TestAllTypes.newBuilder().setOptionalInt32(1).build());
+ builder.addMessage(TestAllTypes.newBuilder().setOptionalInt32(2).build());
+ builder.addBuilder(0, TestAllTypes.getDefaultInstance())
+ .setOptionalInt32(0);
+ builder.addBuilder(TestAllTypes.getDefaultInstance()).setOptionalInt32(3);
+
+ assertEquals(0, builder.getMessage(0).getOptionalInt32());
+ assertEquals(1, builder.getMessage(1).getOptionalInt32());
+ assertEquals(2, builder.getMessage(2).getOptionalInt32());
+ assertEquals(3, builder.getMessage(3).getOptionalInt32());
+
+ assertEquals(0, mockParent.getInvalidationCount());
+ List<TestAllTypes> messages = builder.build();
+ assertEquals(4, messages.size());
+ assertSame(messages, builder.build()); // expect same list
+
+ // Remove a message.
+ builder.remove(2);
+ assertEquals(1, mockParent.getInvalidationCount());
+ assertEquals(3, builder.getCount());
+ assertEquals(0, builder.getMessage(0).getOptionalInt32());
+ assertEquals(1, builder.getMessage(1).getOptionalInt32());
+ assertEquals(3, builder.getMessage(2).getOptionalInt32());
+
+ // Remove a builder.
+ builder.remove(0);
+ assertEquals(1, mockParent.getInvalidationCount());
+ assertEquals(2, builder.getCount());
+ assertEquals(1, builder.getMessage(0).getOptionalInt32());
+ assertEquals(3, builder.getMessage(1).getOptionalInt32());
+
+ // Test clear.
+ builder.clear();
+ assertEquals(1, mockParent.getInvalidationCount());
+ assertEquals(0, builder.getCount());
+ assertTrue(builder.isEmpty());
+ }
+
+ public void testLists() {
+ TestUtil.MockBuilderParent mockParent = new TestUtil.MockBuilderParent();
+ RepeatedFieldBuilder<TestAllTypes, TestAllTypes.Builder,
+ TestAllTypesOrBuilder> builder = newRepeatedFieldBuilder(mockParent);
+ builder.addMessage(TestAllTypes.newBuilder().setOptionalInt32(1).build());
+ builder.addMessage(0,
+ TestAllTypes.newBuilder().setOptionalInt32(0).build());
+ assertEquals(0, builder.getMessage(0).getOptionalInt32());
+ assertEquals(1, builder.getMessage(1).getOptionalInt32());
+
+ // Use list of builders.
+ List<TestAllTypes.Builder> builders = builder.getBuilderList();
+ assertEquals(0, builders.get(0).getOptionalInt32());
+ assertEquals(1, builders.get(1).getOptionalInt32());
+ builders.get(0).setOptionalInt32(10);
+ builders.get(1).setOptionalInt32(11);
+
+ // Use list of protos
+ List<TestAllTypes> protos = builder.getMessageList();
+ assertEquals(10, protos.get(0).getOptionalInt32());
+ assertEquals(11, protos.get(1).getOptionalInt32());
+
+ // Add an item to the builders and verify it's updated in both
+ builder.addMessage(TestAllTypes.newBuilder().setOptionalInt32(12).build());
+ assertEquals(3, builders.size());
+ assertEquals(3, protos.size());
+ }
+
+ private void assertIsUnmodifiable(List<?> list) {
+ if (list == Collections.emptyList()) {
+ // OKAY -- Need to check this b/c EmptyList allows you to call clear.
+ } else {
+ try {
+ list.clear();
+ fail("List wasn't immutable");
+ } catch (UnsupportedOperationException e) {
+ // good
+ }
+ }
+ }
+
+ private RepeatedFieldBuilder<TestAllTypes, TestAllTypes.Builder,
+ TestAllTypesOrBuilder>
+ newRepeatedFieldBuilder(GeneratedMessage.BuilderParent parent) {
+ return new RepeatedFieldBuilder<TestAllTypes, TestAllTypes.Builder,
+ TestAllTypesOrBuilder>(Collections.<TestAllTypes>emptyList(), false,
+ parent, false);
+ }
+}
diff --git a/java/src/test/java/com/google/protobuf/ServiceTest.java b/java/src/test/java/com/google/protobuf/ServiceTest.java
index 802eb0e0..4be84f5b 100644
--- a/java/src/test/java/com/google/protobuf/ServiceTest.java
+++ b/java/src/test/java/com/google/protobuf/ServiceTest.java
@@ -244,6 +244,14 @@ public class ServiceTest extends TestCase {
// make assumptions, so I'm just going to accept any character as the
// separator.
assertTrue(fullName.startsWith(outerName));
+
+ if (!Service.class.isAssignableFrom(innerClass) &&
+ !Message.class.isAssignableFrom(innerClass) &&
+ !ProtocolMessageEnum.class.isAssignableFrom(innerClass)) {
+ // Ignore any classes not generated by the base code generator.
+ continue;
+ }
+
innerClassNames.add(fullName.substring(outerName.length() + 1));
}
diff --git a/java/src/test/java/com/google/protobuf/SingleFieldBuilderTest.java b/java/src/test/java/com/google/protobuf/SingleFieldBuilderTest.java
new file mode 100644
index 00000000..5c2f7e75
--- /dev/null
+++ b/java/src/test/java/com/google/protobuf/SingleFieldBuilderTest.java
@@ -0,0 +1,155 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// http://code.google.com/p/protobuf/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import protobuf_unittest.UnittestProto.TestAllTypes;
+import protobuf_unittest.UnittestProto.TestAllTypesOrBuilder;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for {@link SingleFieldBuilder}. This tests basic functionality.
+ * More extensive testing is provided via other tests that exercise the
+ * builder.
+ *
+ * @author jonp@google.com (Jon Perlow)
+ */
+public class SingleFieldBuilderTest extends TestCase {
+
+ public void testBasicUseAndInvalidations() {
+ TestUtil.MockBuilderParent mockParent = new TestUtil.MockBuilderParent();
+ SingleFieldBuilder<TestAllTypes, TestAllTypes.Builder,
+ TestAllTypesOrBuilder> builder =
+ new SingleFieldBuilder<TestAllTypes, TestAllTypes.Builder,
+ TestAllTypesOrBuilder>(
+ TestAllTypes.getDefaultInstance(),
+ mockParent,
+ false);
+ assertSame(TestAllTypes.getDefaultInstance(), builder.getMessage());
+ assertEquals(TestAllTypes.getDefaultInstance(),
+ builder.getBuilder().buildPartial());
+ assertEquals(0, mockParent.getInvalidationCount());
+
+ builder.getBuilder().setOptionalInt32(10);
+ assertEquals(0, mockParent.getInvalidationCount());
+ TestAllTypes message = builder.build();
+ assertEquals(10, message.getOptionalInt32());
+
+ // Test that we receive invalidations now that build has been called.
+ assertEquals(0, mockParent.getInvalidationCount());
+ builder.getBuilder().setOptionalInt32(20);
+ assertEquals(1, mockParent.getInvalidationCount());
+
+ // Test that we don't keep getting invalidations on every change
+ builder.getBuilder().setOptionalInt32(30);
+ assertEquals(1, mockParent.getInvalidationCount());
+
+ }
+
+ public void testSetMessage() {
+ TestUtil.MockBuilderParent mockParent = new TestUtil.MockBuilderParent();
+ SingleFieldBuilder<TestAllTypes, TestAllTypes.Builder,
+ TestAllTypesOrBuilder> builder =
+ new SingleFieldBuilder<TestAllTypes, TestAllTypes.Builder,
+ TestAllTypesOrBuilder>(
+ TestAllTypes.getDefaultInstance(),
+ mockParent,
+ false);
+ builder.setMessage(TestAllTypes.newBuilder().setOptionalInt32(0).build());
+ assertEquals(0, builder.getMessage().getOptionalInt32());
+
+ // Update message using the builder
+ builder.getBuilder().setOptionalInt32(1);
+ assertEquals(0, mockParent.getInvalidationCount());
+ assertEquals(1, builder.getBuilder().getOptionalInt32());
+ assertEquals(1, builder.getMessage().getOptionalInt32());
+ builder.build();
+ builder.getBuilder().setOptionalInt32(2);
+ assertEquals(2, builder.getBuilder().getOptionalInt32());
+ assertEquals(2, builder.getMessage().getOptionalInt32());
+
+ // Make sure message stays cached
+ assertSame(builder.getMessage(), builder.getMessage());
+ }
+
+ public void testClear() {
+ TestUtil.MockBuilderParent mockParent = new TestUtil.MockBuilderParent();
+ SingleFieldBuilder<TestAllTypes, TestAllTypes.Builder,
+ TestAllTypesOrBuilder> builder =
+ new SingleFieldBuilder<TestAllTypes, TestAllTypes.Builder,
+ TestAllTypesOrBuilder>(
+ TestAllTypes.getDefaultInstance(),
+ mockParent,
+ false);
+ builder.setMessage(TestAllTypes.newBuilder().setOptionalInt32(0).build());
+ assertNotSame(TestAllTypes.getDefaultInstance(), builder.getMessage());
+ builder.clear();
+ assertSame(TestAllTypes.getDefaultInstance(), builder.getMessage());
+
+ builder.getBuilder().setOptionalInt32(1);
+ assertNotSame(TestAllTypes.getDefaultInstance(), builder.getMessage());
+ builder.clear();
+ assertSame(TestAllTypes.getDefaultInstance(), builder.getMessage());
+ }
+
+ public void testMerge() {
+ TestUtil.MockBuilderParent mockParent = new TestUtil.MockBuilderParent();
+ SingleFieldBuilder<TestAllTypes, TestAllTypes.Builder,
+ TestAllTypesOrBuilder> builder =
+ new SingleFieldBuilder<TestAllTypes, TestAllTypes.Builder,
+ TestAllTypesOrBuilder>(
+ TestAllTypes.getDefaultInstance(),
+ mockParent,
+ false);
+
+ // Merge into default field.
+ builder.mergeFrom(TestAllTypes.getDefaultInstance());
+ assertSame(TestAllTypes.getDefaultInstance(), builder.getMessage());
+
+ // Merge into non-default field on existing builder.
+ builder.getBuilder().setOptionalInt32(2);
+ builder.mergeFrom(TestAllTypes.newBuilder()
+ .setOptionalDouble(4.0)
+ .buildPartial());
+ assertEquals(2, builder.getMessage().getOptionalInt32());
+ assertEquals(4.0, builder.getMessage().getOptionalDouble());
+
+ // Merge into non-default field on existing message
+ builder.setMessage(TestAllTypes.newBuilder()
+ .setOptionalInt32(10)
+ .buildPartial());
+ builder.mergeFrom(TestAllTypes.newBuilder()
+ .setOptionalDouble(5.0)
+ .buildPartial());
+ assertEquals(10, builder.getMessage().getOptionalInt32());
+ assertEquals(5.0, builder.getMessage().getOptionalDouble());
+ }
+}
diff --git a/java/src/test/java/com/google/protobuf/SmallSortedMapTest.java b/java/src/test/java/com/google/protobuf/SmallSortedMapTest.java
new file mode 100644
index 00000000..922115fc
--- /dev/null
+++ b/java/src/test/java/com/google/protobuf/SmallSortedMapTest.java
@@ -0,0 +1,378 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// http://code.google.com/p/protobuf/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import junit.framework.TestCase;
+
+import java.util.AbstractMap.SimpleEntry;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * @author darick@google.com Darick Tong
+ */
+public class SmallSortedMapTest extends TestCase {
+
+ public void testPutAndGetArrayEntriesOnly() {
+ runPutAndGetTest(3);
+ }
+
+ public void testPutAndGetOverflowEntries() {
+ runPutAndGetTest(6);
+ }
+
+ private void runPutAndGetTest(int numElements) {
+ // Test with even and odd arraySize
+ SmallSortedMap<Integer, Integer> map1 =
+ SmallSortedMap.newInstanceForTest(3);
+ SmallSortedMap<Integer, Integer> map2 =
+ SmallSortedMap.newInstanceForTest(4);
+ SmallSortedMap<Integer, Integer> map3 =
+ SmallSortedMap.newInstanceForTest(3);
+ SmallSortedMap<Integer, Integer> map4 =
+ SmallSortedMap.newInstanceForTest(4);
+
+ // Test with puts in ascending order.
+ for (int i = 0; i < numElements; i++) {
+ assertNull(map1.put(i, i + 1));
+ assertNull(map2.put(i, i + 1));
+ }
+ // Test with puts in descending order.
+ for (int i = numElements - 1; i >= 0; i--) {
+ assertNull(map3.put(i, i + 1));
+ assertNull(map4.put(i, i + 1));
+ }
+
+ assertEquals(Math.min(3, numElements), map1.getNumArrayEntries());
+ assertEquals(Math.min(4, numElements), map2.getNumArrayEntries());
+ assertEquals(Math.min(3, numElements), map3.getNumArrayEntries());
+ assertEquals(Math.min(4, numElements), map4.getNumArrayEntries());
+
+ List<SmallSortedMap<Integer, Integer>> allMaps =
+ new ArrayList<SmallSortedMap<Integer, Integer>>();
+ allMaps.add(map1);
+ allMaps.add(map2);
+ allMaps.add(map3);
+ allMaps.add(map4);
+
+ for (SmallSortedMap<Integer, Integer> map : allMaps) {
+ assertEquals(numElements, map.size());
+ for (int i = 0; i < numElements; i++) {
+ assertEquals(new Integer(i + 1), map.get(i));
+ }
+ }
+
+ assertEquals(map1, map2);
+ assertEquals(map2, map3);
+ assertEquals(map3, map4);
+ }
+
+ public void testReplacingPut() {
+ SmallSortedMap<Integer, Integer> map = SmallSortedMap.newInstanceForTest(3);
+ for (int i = 0; i < 6; i++) {
+ assertNull(map.put(i, i + 1));
+ assertNull(map.remove(i + 1));
+ }
+ for (int i = 0; i < 6; i++) {
+ assertEquals(new Integer(i + 1), map.put(i, i + 2));
+ }
+ }
+
+ public void testRemove() {
+ SmallSortedMap<Integer, Integer> map = SmallSortedMap.newInstanceForTest(3);
+ for (int i = 0; i < 6; i++) {
+ assertNull(map.put(i, i + 1));
+ assertNull(map.remove(i + 1));
+ }
+
+ assertEquals(3, map.getNumArrayEntries());
+ assertEquals(3, map.getNumOverflowEntries());
+ assertEquals(6, map.size());
+ assertEquals(makeSortedKeySet(0, 1, 2, 3, 4, 5), map.keySet());
+
+ assertEquals(new Integer(2), map.remove(1));
+ assertEquals(3, map.getNumArrayEntries());
+ assertEquals(2, map.getNumOverflowEntries());
+ assertEquals(5, map.size());
+ assertEquals(makeSortedKeySet(0, 2, 3, 4, 5), map.keySet());
+
+ assertEquals(new Integer(5), map.remove(4));
+ assertEquals(3, map.getNumArrayEntries());
+ assertEquals(1, map.getNumOverflowEntries());
+ assertEquals(4, map.size());
+ assertEquals(makeSortedKeySet(0, 2, 3, 5), map.keySet());
+
+ assertEquals(new Integer(4), map.remove(3));
+ assertEquals(3, map.getNumArrayEntries());
+ assertEquals(0, map.getNumOverflowEntries());
+ assertEquals(3, map.size());
+ assertEquals(makeSortedKeySet(0, 2, 5), map.keySet());
+
+ assertNull(map.remove(3));
+ assertEquals(3, map.getNumArrayEntries());
+ assertEquals(0, map.getNumOverflowEntries());
+ assertEquals(3, map.size());
+
+ assertEquals(new Integer(1), map.remove(0));
+ assertEquals(2, map.getNumArrayEntries());
+ assertEquals(0, map.getNumOverflowEntries());
+ assertEquals(2, map.size());
+ }
+
+ public void testClear() {
+ SmallSortedMap<Integer, Integer> map = SmallSortedMap.newInstanceForTest(3);
+ for (int i = 0; i < 6; i++) {
+ assertNull(map.put(i, i + 1));
+ }
+ map.clear();
+ assertEquals(0, map.getNumArrayEntries());
+ assertEquals(0, map.getNumOverflowEntries());
+ assertEquals(0, map.size());
+ }
+
+ public void testGetArrayEntryAndOverflowEntries() {
+ SmallSortedMap<Integer, Integer> map = SmallSortedMap.newInstanceForTest(3);
+ for (int i = 0; i < 6; i++) {
+ assertNull(map.put(i, i + 1));
+ }
+ assertEquals(3, map.getNumArrayEntries());
+ for (int i = 0; i < 3; i++) {
+ Map.Entry<Integer, Integer> entry = map.getArrayEntryAt(i);
+ assertEquals(new Integer(i), entry.getKey());
+ assertEquals(new Integer(i + 1), entry.getValue());
+ }
+ Iterator<Map.Entry<Integer, Integer>> it =
+ map.getOverflowEntries().iterator();
+ for (int i = 3; i < 6; i++) {
+ assertTrue(it.hasNext());
+ Map.Entry<Integer, Integer> entry = it.next();
+ assertEquals(new Integer(i), entry.getKey());
+ assertEquals(new Integer(i + 1), entry.getValue());
+ }
+ assertFalse(it.hasNext());
+ }
+
+ public void testEntrySetContains() {
+ SmallSortedMap<Integer, Integer> map = SmallSortedMap.newInstanceForTest(3);
+ for (int i = 0; i < 6; i++) {
+ assertNull(map.put(i, i + 1));
+ }
+ Set<Map.Entry<Integer, Integer>> entrySet = map.entrySet();
+ for (int i = 0; i < 6; i++) {
+ assertTrue(
+ entrySet.contains(new SimpleEntry<Integer, Integer>(i, i + 1)));
+ assertFalse(
+ entrySet.contains(new SimpleEntry<Integer, Integer>(i, i)));
+ }
+ }
+
+ public void testEntrySetAdd() {
+ SmallSortedMap<Integer, Integer> map = SmallSortedMap.newInstanceForTest(3);
+ Set<Map.Entry<Integer, Integer>> entrySet = map.entrySet();
+ for (int i = 0; i < 6; i++) {
+ Map.Entry<Integer, Integer> entry =
+ new SimpleEntry<Integer, Integer>(i, i + 1);
+ assertTrue(entrySet.add(entry));
+ assertFalse(entrySet.add(entry));
+ }
+ for (int i = 0; i < 6; i++) {
+ assertEquals(new Integer(i + 1), map.get(i));
+ }
+ assertEquals(3, map.getNumArrayEntries());
+ assertEquals(3, map.getNumOverflowEntries());
+ assertEquals(6, map.size());
+ }
+
+ public void testEntrySetRemove() {
+ SmallSortedMap<Integer, Integer> map = SmallSortedMap.newInstanceForTest(3);
+ Set<Map.Entry<Integer, Integer>> entrySet = map.entrySet();
+ for (int i = 0; i < 6; i++) {
+ assertNull(map.put(i, i + 1));
+ }
+ for (int i = 0; i < 6; i++) {
+ Map.Entry<Integer, Integer> entry =
+ new SimpleEntry<Integer, Integer>(i, i + 1);
+ assertTrue(entrySet.remove(entry));
+ assertFalse(entrySet.remove(entry));
+ }
+ assertTrue(map.isEmpty());
+ assertEquals(0, map.getNumArrayEntries());
+ assertEquals(0, map.getNumOverflowEntries());
+ assertEquals(0, map.size());
+ }
+
+ public void testEntrySetClear() {
+ SmallSortedMap<Integer, Integer> map = SmallSortedMap.newInstanceForTest(3);
+ for (int i = 0; i < 6; i++) {
+ assertNull(map.put(i, i + 1));
+ }
+ map.entrySet().clear();
+ assertTrue(map.isEmpty());
+ assertEquals(0, map.getNumArrayEntries());
+ assertEquals(0, map.getNumOverflowEntries());
+ assertEquals(0, map.size());
+ }
+
+ public void testEntrySetIteratorNext() {
+ SmallSortedMap<Integer, Integer> map = SmallSortedMap.newInstanceForTest(3);
+ for (int i = 0; i < 6; i++) {
+ assertNull(map.put(i, i + 1));
+ }
+ Iterator<Map.Entry<Integer, Integer>> it = map.entrySet().iterator();
+ for (int i = 0; i < 6; i++) {
+ assertTrue(it.hasNext());
+ Map.Entry<Integer, Integer> entry = it.next();
+ assertEquals(new Integer(i), entry.getKey());
+ assertEquals(new Integer(i + 1), entry.getValue());
+ }
+ assertFalse(it.hasNext());
+ }
+
+ public void testEntrySetIteratorRemove() {
+ SmallSortedMap<Integer, Integer> map = SmallSortedMap.newInstanceForTest(3);
+ for (int i = 0; i < 6; i++) {
+ assertNull(map.put(i, i + 1));
+ }
+ Iterator<Map.Entry<Integer, Integer>> it = map.entrySet().iterator();
+ for (int i = 0; i < 6; i++) {
+ assertTrue(map.containsKey(i));
+ it.next();
+ it.remove();
+ assertFalse(map.containsKey(i));
+ assertEquals(6 - i - 1, map.size());
+ }
+ }
+
+ public void testMapEntryModification() {
+ SmallSortedMap<Integer, Integer> map = SmallSortedMap.newInstanceForTest(3);
+ for (int i = 0; i < 6; i++) {
+ assertNull(map.put(i, i + 1));
+ }
+ Iterator<Map.Entry<Integer, Integer>> it = map.entrySet().iterator();
+ for (int i = 0; i < 6; i++) {
+ Map.Entry<Integer, Integer> entry = it.next();
+ entry.setValue(i + 23);
+ }
+ for (int i = 0; i < 6; i++) {
+ assertEquals(new Integer(i + 23), map.get(i));
+ }
+ }
+
+ public void testMakeImmutable() {
+ SmallSortedMap<Integer, Integer> map = SmallSortedMap.newInstanceForTest(3);
+ for (int i = 0; i < 6; i++) {
+ assertNull(map.put(i, i + 1));
+ }
+ map.makeImmutable();
+ assertEquals(new Integer(1), map.get(0));
+ assertEquals(6, map.size());
+
+ try {
+ map.put(23, 23);
+ fail("Expected UnsupportedOperationException");
+ } catch (UnsupportedOperationException expected) {
+ }
+
+ Map<Integer, Integer> other = new HashMap<Integer, Integer>();
+ other.put(23, 23);
+ try {
+ map.putAll(other);
+ fail("Expected UnsupportedOperationException");
+ } catch (UnsupportedOperationException expected) {
+ }
+
+ try {
+ map.remove(0);
+ fail("Expected UnsupportedOperationException");
+ } catch (UnsupportedOperationException expected) {
+ }
+
+ try {
+ map.clear();
+ fail("Expected UnsupportedOperationException");
+ } catch (UnsupportedOperationException expected) {
+ }
+
+ Set<Map.Entry<Integer, Integer>> entrySet = map.entrySet();
+ try {
+ entrySet.clear();
+ fail("Expected UnsupportedOperationException");
+ } catch (UnsupportedOperationException expected) {
+ }
+
+ Iterator<Map.Entry<Integer, Integer>> it = entrySet.iterator();
+ while (it.hasNext()) {
+ Map.Entry<Integer, Integer> entry = it.next();
+ try {
+ entry.setValue(0);
+ fail("Expected UnsupportedOperationException");
+ } catch (UnsupportedOperationException expected) {
+ }
+ try {
+ it.remove();
+ fail("Expected UnsupportedOperationException");
+ } catch (UnsupportedOperationException expected) {
+ }
+ }
+
+ Set<Integer> keySet = map.keySet();
+ try {
+ keySet.clear();
+ fail("Expected UnsupportedOperationException");
+ } catch (UnsupportedOperationException expected) {
+ }
+
+ Iterator<Integer> keys = keySet.iterator();
+ while (keys.hasNext()) {
+ Integer key = keys.next();
+ try {
+ keySet.remove(key);
+ fail("Expected UnsupportedOperationException");
+ } catch (UnsupportedOperationException expected) {
+ }
+ try {
+ keys.remove();
+ fail("Expected UnsupportedOperationException");
+ } catch (UnsupportedOperationException expected) {
+ }
+ }
+ }
+
+ private Set<Integer> makeSortedKeySet(Integer... keys) {
+ return new TreeSet<Integer>(Arrays.<Integer>asList(keys));
+ }
+}
diff --git a/java/src/test/java/com/google/protobuf/TestBadIdentifiers.java b/java/src/test/java/com/google/protobuf/TestBadIdentifiers.java
new file mode 100644
index 00000000..6feec4e6
--- /dev/null
+++ b/java/src/test/java/com/google/protobuf/TestBadIdentifiers.java
@@ -0,0 +1,49 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// http://code.google.com/p/protobuf/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests that proto2 api generation doesn't cause compile errors when
+ * compiling protocol buffers that have names that would otherwise conflict
+ * if not fully qualified (like @Deprecated and @Override).
+ *
+ * @author jonp@google.com (Jon Perlow)
+ */
+public class TestBadIdentifiers extends TestCase {
+
+ public void testCompilation() {
+ // If this compiles, it means the generation was correct.
+ TestBadIdentifiersProto.Deprecated.newBuilder();
+ TestBadIdentifiersProto.Override.newBuilder();
+ }
+}
diff --git a/java/src/test/java/com/google/protobuf/TestUtil.java b/java/src/test/java/com/google/protobuf/TestUtil.java
index 2b8b2af8..7c458d75 100644
--- a/java/src/test/java/com/google/protobuf/TestUtil.java
+++ b/java/src/test/java/com/google/protobuf/TestUtil.java
@@ -214,7 +214,9 @@ import static com.google.protobuf.UnittestLite.packedBoolExtensionLite;
import static com.google.protobuf.UnittestLite.packedEnumExtensionLite;
import protobuf_unittest.UnittestProto.TestAllExtensions;
+import protobuf_unittest.UnittestProto.TestAllExtensionsOrBuilder;
import protobuf_unittest.UnittestProto.TestAllTypes;
+import protobuf_unittest.UnittestProto.TestAllTypesOrBuilder;
import protobuf_unittest.UnittestProto.TestPackedExtensions;
import protobuf_unittest.UnittestProto.TestPackedTypes;
import protobuf_unittest.UnittestProto.TestUnpackedTypes;
@@ -225,6 +227,7 @@ import com.google.protobuf.test.UnittestImport.ImportEnum;
import com.google.protobuf.UnittestLite.TestAllTypesLite;
import com.google.protobuf.UnittestLite.TestAllExtensionsLite;
+import com.google.protobuf.UnittestLite.TestAllExtensionsLiteOrBuilder;
import com.google.protobuf.UnittestLite.TestPackedExtensionsLite;
import com.google.protobuf.UnittestLite.ForeignMessageLite;
import com.google.protobuf.UnittestLite.ForeignEnumLite;
@@ -244,9 +247,12 @@ import java.io.RandomAccessFile;
* set all fields of a message, serialize it, parse it, and check that all
* fields are set.
*
+ * <p>This code is not to be used outside of {@code com.google.protobuf} and
+ * subpackages.
+ *
* @author kenton@google.com Kenton Varda
*/
-class TestUtil {
+public final class TestUtil {
private TestUtil() {}
/** Helper to convert a String to ByteString. */
@@ -485,7 +491,7 @@ class TestUtil {
* 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) {
+ public static void assertAllFieldsSet(TestAllTypesOrBuilder message) {
Assert.assertTrue(message.hasOptionalInt32 ());
Assert.assertTrue(message.hasOptionalInt64 ());
Assert.assertTrue(message.hasOptionalUint32 ());
@@ -682,13 +688,12 @@ class TestUtil {
}
// -------------------------------------------------------------------
-
/**
* 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) {
+ public static void assertClear(TestAllTypesOrBuilder message) {
// hasBlah() should initially be false for all optional fields.
Assert.assertFalse(message.hasOptionalInt32 ());
Assert.assertFalse(message.hasOptionalInt64 ());
@@ -838,7 +843,8 @@ class TestUtil {
* {@code message} are set to the values assigned by {@code setAllFields}
* followed by {@code modifyRepeatedFields}.
*/
- public static void assertRepeatedFieldsModified(TestAllTypes message) {
+ public static void assertRepeatedFieldsModified(
+ TestAllTypesOrBuilder message) {
// ModifyRepeatedFields only sets the second repeated element of each
// field. In addition to verifying this, we also verify that the first
// element and size were *not* modified.
@@ -1352,7 +1358,8 @@ class TestUtil {
* 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) {
+ public static void assertAllExtensionsSet(
+ TestAllExtensionsOrBuilder message) {
Assert.assertTrue(message.hasExtension(optionalInt32Extension ));
Assert.assertTrue(message.hasExtension(optionalInt64Extension ));
Assert.assertTrue(message.hasExtension(optionalUint32Extension ));
@@ -1567,7 +1574,7 @@ class TestUtil {
* {@code message} are cleared, and that getting the extensions returns their
* default values.
*/
- public static void assertExtensionsClear(TestAllExtensions message) {
+ public static void assertExtensionsClear(TestAllExtensionsOrBuilder message) {
// hasBlah() should initially be false for all optional fields.
Assert.assertFalse(message.hasExtension(optionalInt32Extension ));
Assert.assertFalse(message.hasExtension(optionalInt64Extension ));
@@ -1752,7 +1759,7 @@ class TestUtil {
* followed by {@code modifyRepeatedExtensions}.
*/
public static void assertRepeatedExtensionsModified(
- TestAllExtensions message) {
+ TestAllExtensionsOrBuilder message) {
// ModifyRepeatedFields only sets the second repeated element of each
// field. In addition to verifying this, we also verify that the first
// element and size were *not* modified.
@@ -2106,7 +2113,8 @@ class TestUtil {
* 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(TestAllExtensionsLite message) {
+ public static void assertAllExtensionsSet(
+ TestAllExtensionsLiteOrBuilder message) {
Assert.assertTrue(message.hasExtension(optionalInt32ExtensionLite ));
Assert.assertTrue(message.hasExtension(optionalInt64ExtensionLite ));
Assert.assertTrue(message.hasExtension(optionalUint32ExtensionLite ));
@@ -2321,7 +2329,8 @@ class TestUtil {
* {@code message} are cleared, and that getting the extensions returns their
* default values.
*/
- public static void assertExtensionsClear(TestAllExtensionsLite message) {
+ public static void assertExtensionsClear(
+ TestAllExtensionsLiteOrBuilder message) {
// hasBlah() should initially be false for all optional fields.
Assert.assertFalse(message.hasExtension(optionalInt32ExtensionLite ));
Assert.assertFalse(message.hasExtension(optionalInt64ExtensionLite ));
@@ -2478,7 +2487,7 @@ class TestUtil {
* followed by {@code modifyRepeatedExtensions}.
*/
public static void assertRepeatedExtensionsModified(
- TestAllExtensionsLite message) {
+ TestAllExtensionsLiteOrBuilder message) {
// ModifyRepeatedFields only sets the second repeated element of each
// field. In addition to verifying this, we also verify that the first
// element and size were *not* modified.
@@ -3015,7 +3024,7 @@ class TestUtil {
* {@code message} are set to the values assigned by {@code setAllFields},
* using the {@link Message} reflection interface.
*/
- public void assertAllFieldsSetViaReflection(Message message) {
+ public void assertAllFieldsSetViaReflection(MessageOrBuilder message) {
Assert.assertTrue(message.hasField(f("optional_int32" )));
Assert.assertTrue(message.hasField(f("optional_int64" )));
Assert.assertTrue(message.hasField(f("optional_uint32" )));
@@ -3248,7 +3257,7 @@ class TestUtil {
* {@code message} are cleared, and that getting the fields returns their
* default values, using the {@link Message} reflection interface.
*/
- public void assertClearViaReflection(Message message) {
+ public void assertClearViaReflection(MessageOrBuilder message) {
// has_blah() should initially be false for all optional fields.
Assert.assertFalse(message.hasField(f("optional_int32" )));
Assert.assertFalse(message.hasField(f("optional_int64" )));
@@ -3405,9 +3414,11 @@ class TestUtil {
Assert.assertEquals("123", message.getField(f("default_cord")));
}
+
// ---------------------------------------------------------------
- public void assertRepeatedFieldsModifiedViaReflection(Message message) {
+ public void assertRepeatedFieldsModifiedViaReflection(
+ MessageOrBuilder message) {
// ModifyRepeatedFields only sets the second repeated element of each
// field. In addition to verifying this, we also verify that the first
// element and size were *not* modified.
@@ -3543,7 +3554,7 @@ class TestUtil {
message.addRepeatedField(f("packed_enum" ), foreignBaz);
}
- public void assertPackedFieldsSetViaReflection(Message message) {
+ public void assertPackedFieldsSetViaReflection(MessageOrBuilder message) {
Assert.assertEquals(2, message.getRepeatedFieldCount(f("packed_int32" )));
Assert.assertEquals(2, message.getRepeatedFieldCount(f("packed_int64" )));
Assert.assertEquals(2, message.getRepeatedFieldCount(f("packed_uint32" )));
@@ -3699,7 +3710,7 @@ class TestUtil {
/**
* @param filePath The path relative to
- * {@link com.google.testing.util.TestUtil#getDefaultSrcDir}.
+ * {@link #getTestDataDir}.
*/
public static String readTextFromFile(String filePath) {
return readBytesFromFile(filePath).toStringUtf8();
@@ -3728,8 +3739,8 @@ class TestUtil {
}
/**
- * @param filePath The path relative to
- * {@link com.google.testing.util.TestUtil#getDefaultSrcDir}.
+ * @param filename The path relative to
+ * {@link #getTestDataDir}.
*/
public static ByteString readBytesFromFile(String filename) {
File fullPath = new File(getTestDataDir(), filename);
@@ -3749,7 +3760,7 @@ class TestUtil {
/**
* 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
+ * {@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++.
*/
@@ -3764,7 +3775,7 @@ class TestUtil {
/**
* Get the bytes of the "golden packed fields message". This is a serialized
* TestPackedTypes with all fields set as they would be by
- * {@link setPackedFields(TestPackedTypes.Builder)}, but it is loaded from a
+ * {@link #setPackedFields(TestPackedTypes.Builder)}, but it is loaded from a
* file on disk rather than generated dynamically. The file is actually
* generated by C++ code, so testing against it verifies compatibility with
* C++.
@@ -3777,4 +3788,24 @@ class TestUtil {
return goldenPackedFieldsMessage;
}
private static ByteString goldenPackedFieldsMessage = null;
+
+ /**
+ * Mock implementation of {@link GeneratedMessage.BuilderParent} for testing.
+ *
+ * @author jonp@google.com (Jon Perlow)
+ */
+ public static class MockBuilderParent
+ implements GeneratedMessage.BuilderParent {
+
+ private int invalidations;
+
+ @Override
+ public void markDirty() {
+ invalidations++;
+ }
+
+ public int getInvalidationCount() {
+ return invalidations;
+ }
+ }
}
diff --git a/java/src/test/java/com/google/protobuf/TextFormatTest.java b/java/src/test/java/com/google/protobuf/TextFormatTest.java
index ad48157a..47690d1f 100644
--- a/java/src/test/java/com/google/protobuf/TextFormatTest.java
+++ b/java/src/test/java/com/google/protobuf/TextFormatTest.java
@@ -31,14 +31,14 @@
package com.google.protobuf;
import com.google.protobuf.Descriptors.FieldDescriptor;
-import protobuf_unittest.UnittestProto.OneString;
-import protobuf_unittest.UnittestProto.TestAllTypes;
-import protobuf_unittest.UnittestProto.TestAllExtensions;
-import protobuf_unittest.UnittestProto.TestEmptyMessage;
-import protobuf_unittest.UnittestProto.TestAllTypes.NestedMessage;
import protobuf_unittest.UnittestMset.TestMessageSet;
import protobuf_unittest.UnittestMset.TestMessageSetExtension1;
import protobuf_unittest.UnittestMset.TestMessageSetExtension2;
+import protobuf_unittest.UnittestProto.OneString;
+import protobuf_unittest.UnittestProto.TestAllExtensions;
+import protobuf_unittest.UnittestProto.TestAllTypes;
+import protobuf_unittest.UnittestProto.TestAllTypes.NestedMessage;
+import protobuf_unittest.UnittestProto.TestEmptyMessage;
import junit.framework.TestCase;
@@ -133,40 +133,44 @@ public class TextFormatTest extends TestCase {
assertEquals(allExtensionsSetText, javaText);
}
+ // Creates an example unknown field set.
+ private UnknownFieldSet makeUnknownFieldSet() {
+ return UnknownFieldSet.newBuilder()
+ .addField(5,
+ UnknownFieldSet.Field.newBuilder()
+ .addVarint(1)
+ .addFixed32(2)
+ .addFixed64(3)
+ .addLengthDelimited(ByteString.copyFromUtf8("4"))
+ .addGroup(
+ UnknownFieldSet.newBuilder()
+ .addField(10,
+ UnknownFieldSet.Field.newBuilder()
+ .addVarint(5)
+ .build())
+ .build())
+ .build())
+ .addField(8,
+ UnknownFieldSet.Field.newBuilder()
+ .addVarint(1)
+ .addVarint(2)
+ .addVarint(3)
+ .build())
+ .addField(15,
+ UnknownFieldSet.Field.newBuilder()
+ .addVarint(0xABCDEF1234567890L)
+ .addFixed32(0xABCD1234)
+ .addFixed64(0xABCDEF1234567890L)
+ .build())
+ .build();
+ }
+
public void testPrintUnknownFields() throws Exception {
// Test printing of unknown fields in a message.
TestEmptyMessage message =
TestEmptyMessage.newBuilder()
- .setUnknownFields(
- 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())
+ .setUnknownFields(makeUnknownFieldSet())
.build();
assertEquals(
@@ -409,6 +413,9 @@ public class TextFormatTest extends TestCase {
"1:16: Expected \"true\" or \"false\".",
"optional_bool: maybe");
assertParseError(
+ "1:16: Expected \"true\" or \"false\".",
+ "optional_bool: 2");
+ assertParseError(
"1:18: Expected string.",
"optional_string: 123");
assertParseError(
@@ -485,6 +492,11 @@ public class TextFormatTest extends TestCase {
assertEquals(bytes(0xe1, 0x88, 0xb4),
TextFormat.unescapeBytes("\\xe1\\x88\\xb4"));
+ // Handling of strings with unescaped Unicode characters > 255.
+ final String zh = "\u9999\u6e2f\u4e0a\u6d77\ud84f\udf80\u8c50\u9280\u884c";
+ ByteString zhByteString = ByteString.copyFromUtf8(zh);
+ assertEquals(zhByteString, TextFormat.unescapeBytes(zh));
+
// Errors.
try {
TextFormat.unescapeText("\\x");
@@ -626,6 +638,13 @@ public class TextFormatTest extends TestCase {
}
}
+ public void testParseString() throws Exception {
+ final String zh = "\u9999\u6e2f\u4e0a\u6d77\ud84f\udf80\u8c50\u9280\u884c";
+ TestAllTypes.Builder builder = TestAllTypes.newBuilder();
+ TextFormat.merge("optional_string: \"" + zh + "\"", builder);
+ assertEquals(zh, builder.getOptionalString());
+ }
+
public void testParseLongString() throws Exception {
String longText =
"123456789012345678901234567890123456789012345678901234567890" +
@@ -654,9 +673,80 @@ public class TextFormatTest extends TestCase {
assertEquals(longText, builder.getOptionalString());
}
+ public void testParseBoolean() throws Exception {
+ String goodText =
+ "repeated_bool: t repeated_bool : 0\n" +
+ "repeated_bool :f repeated_bool:1";
+ String goodTextCanonical =
+ "repeated_bool: true\n" +
+ "repeated_bool: false\n" +
+ "repeated_bool: false\n" +
+ "repeated_bool: true\n";
+ TestAllTypes.Builder builder = TestAllTypes.newBuilder();
+ TextFormat.merge(goodText, builder);
+ assertEquals(goodTextCanonical, builder.build().toString());
+
+ try {
+ TestAllTypes.Builder badBuilder = TestAllTypes.newBuilder();
+ TextFormat.merge("optional_bool:2", badBuilder);
+ fail("Should have thrown an exception.");
+ } catch (TextFormat.ParseException e) {
+ // success
+ }
+ try {
+ TestAllTypes.Builder badBuilder = TestAllTypes.newBuilder();
+ TextFormat.merge("optional_bool: foo", badBuilder);
+ fail("Should have thrown an exception.");
+ } catch (TextFormat.ParseException e) {
+ // success
+ }
+ }
+
public void testParseAdjacentStringLiterals() throws Exception {
TestAllTypes.Builder builder = TestAllTypes.newBuilder();
TextFormat.merge("optional_string: \"foo\" 'corge' \"grault\"", builder);
assertEquals("foocorgegrault", builder.getOptionalString());
}
+
+ public void testPrintFieldValue() throws Exception {
+ assertPrintFieldValue("\"Hello\"", "Hello", "repeated_string");
+ assertPrintFieldValue("123.0", 123f, "repeated_float");
+ assertPrintFieldValue("123.0", 123d, "repeated_double");
+ assertPrintFieldValue("123", 123, "repeated_int32");
+ assertPrintFieldValue("123", 123L, "repeated_int64");
+ assertPrintFieldValue("true", true, "repeated_bool");
+ assertPrintFieldValue("4294967295", 0xFFFFFFFF, "repeated_uint32");
+ assertPrintFieldValue("18446744073709551615", 0xFFFFFFFFFFFFFFFFL,
+ "repeated_uint64");
+ assertPrintFieldValue("\"\\001\\002\\003\"",
+ ByteString.copyFrom(new byte[] {1, 2, 3}), "repeated_bytes");
+ }
+
+ private void assertPrintFieldValue(String expect, Object value,
+ String fieldName) throws Exception {
+ TestAllTypes.Builder builder = TestAllTypes.newBuilder();
+ StringBuilder sb = new StringBuilder();
+ TextFormat.printFieldValue(
+ TestAllTypes.getDescriptor().findFieldByName(fieldName),
+ value, sb);
+ assertEquals(expect, sb.toString());
+ }
+
+ public void testShortDebugString() {
+ assertEquals("optional_nested_message { bb: 42 } repeated_int32: 1"
+ + " repeated_uint32: 2",
+ TextFormat.shortDebugString(TestAllTypes.newBuilder()
+ .addRepeatedInt32(1)
+ .addRepeatedUint32(2)
+ .setOptionalNestedMessage(
+ NestedMessage.newBuilder().setBb(42).build())
+ .build()));
+ }
+
+ public void testShortDebugString_unknown() {
+ assertEquals("5: 1 5: 0x00000002 5: 0x0000000000000003 5: \"4\" 5 { 10: 5 }"
+ + " 8: 1 8: 2 8: 3 15: 12379813812177893520 15: 0xabcd1234 15:"
+ + " 0xabcdef1234567890",
+ TextFormat.shortDebugString(makeUnknownFieldSet()));
+ }
}
diff --git a/java/src/test/java/com/google/protobuf/UnmodifiableLazyStringListTest.java b/java/src/test/java/com/google/protobuf/UnmodifiableLazyStringListTest.java
new file mode 100644
index 00000000..ed5d069e
--- /dev/null
+++ b/java/src/test/java/com/google/protobuf/UnmodifiableLazyStringListTest.java
@@ -0,0 +1,152 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// http://code.google.com/p/protobuf/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import junit.framework.TestCase;
+
+import java.util.Iterator;
+import java.util.ListIterator;
+
+/**
+ * Tests for {@link UnmodifiableLazyStringList}.
+ *
+ * @author jonp@google.com (Jon Perlow)
+ */
+public class UnmodifiableLazyStringListTest extends TestCase {
+
+ private static String STRING_A = "A";
+ private static String STRING_B = "B";
+ private static String STRING_C = "C";
+
+ private static ByteString BYTE_STRING_A = ByteString.copyFromUtf8("A");
+ private static ByteString BYTE_STRING_B = ByteString.copyFromUtf8("B");
+ private static ByteString BYTE_STRING_C = ByteString.copyFromUtf8("C");
+
+ public void testReadOnlyMethods() {
+ LazyStringArrayList rawList = createSampleList();
+ UnmodifiableLazyStringList list = new UnmodifiableLazyStringList(rawList);
+ assertEquals(3, list.size());
+ assertSame(STRING_A, list.get(0));
+ assertSame(STRING_B, list.get(1));
+ assertSame(STRING_C, list.get(2));
+ assertEquals(BYTE_STRING_A, list.getByteString(0));
+ assertEquals(BYTE_STRING_B, list.getByteString(1));
+ assertEquals(BYTE_STRING_C, list.getByteString(2));
+ }
+
+ public void testModifyMethods() {
+ LazyStringArrayList rawList = createSampleList();
+ UnmodifiableLazyStringList list = new UnmodifiableLazyStringList(rawList);
+
+ try {
+ list.remove(0);
+ fail();
+ } catch (UnsupportedOperationException e) {
+ // expected
+ }
+ assertEquals(3, list.size());
+
+ try {
+ list.add(STRING_B);
+ fail();
+ } catch (UnsupportedOperationException e) {
+ // expected
+ }
+ assertEquals(3, list.size());
+
+ try {
+ list.set(1, STRING_B);
+ fail();
+ } catch (UnsupportedOperationException e) {
+ // expected
+ }
+ }
+
+ public void testIterator() {
+ LazyStringArrayList rawList = createSampleList();
+ UnmodifiableLazyStringList list = new UnmodifiableLazyStringList(rawList);
+
+ Iterator<String> iter = list.iterator();
+ int count = 0;
+ while (iter.hasNext()) {
+ iter.next();
+ count++;
+ try {
+ iter.remove();
+ fail();
+ } catch (UnsupportedOperationException e) {
+ // expected
+ }
+ }
+ assertEquals(3, count);
+
+ }
+
+ public void testListIterator() {
+ LazyStringArrayList rawList = createSampleList();
+ UnmodifiableLazyStringList list = new UnmodifiableLazyStringList(rawList);
+
+ ListIterator<String> iter = list.listIterator();
+ int count = 0;
+ while (iter.hasNext()) {
+ iter.next();
+ count++;
+ try {
+ iter.remove();
+ fail();
+ } catch (UnsupportedOperationException e) {
+ // expected
+ }
+ try {
+ iter.set("bar");
+ fail();
+ } catch (UnsupportedOperationException e) {
+ // expected
+ }
+ try {
+ iter.add("bar");
+ fail();
+ } catch (UnsupportedOperationException e) {
+ // expected
+ }
+ }
+ assertEquals(3, count);
+
+ }
+
+ private LazyStringArrayList createSampleList() {
+ LazyStringArrayList rawList = new LazyStringArrayList();
+ rawList.add(STRING_A);
+ rawList.add(STRING_B);
+ rawList.add(STRING_C);
+ return rawList;
+ }
+}
diff --git a/java/src/test/java/com/google/protobuf/multiple_files_test.proto b/java/src/test/java/com/google/protobuf/multiple_files_test.proto
index 060f159a..9a040145 100644
--- a/java/src/test/java/com/google/protobuf/multiple_files_test.proto
+++ b/java/src/test/java/com/google/protobuf/multiple_files_test.proto
@@ -33,6 +33,10 @@
// A proto file which tests the java_multiple_files option.
+// Some generic_services option(s) added automatically.
+// See: http://go/proto2-generic-services-default
+option java_generic_services = true; // auto-added
+
import "google/protobuf/unittest.proto";
package protobuf_unittest;
diff --git a/java/src/test/java/com/google/protobuf/nested_builders_test.proto b/java/src/test/java/com/google/protobuf/nested_builders_test.proto
new file mode 100644
index 00000000..abffb9d2
--- /dev/null
+++ b/java/src/test/java/com/google/protobuf/nested_builders_test.proto
@@ -0,0 +1,53 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// http://code.google.com/p/protobuf/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Author: jonp@google.com (Jon Perlow)
+//
+
+package protobuf_unittest;
+
+option java_multiple_files = true;
+option java_outer_classname = "NestedBuilders";
+
+
+message Vehicle {
+ optional Engine engine = 1;
+ repeated Wheel wheel = 2;
+}
+
+message Engine {
+ optional int32 cylinder = 1;
+ optional int32 liters = 2;
+}
+
+message Wheel {
+ optional int32 radius = 1;
+ optional int32 width = 2;
+}
diff --git a/java/src/test/java/com/google/protobuf/nested_extension.proto b/java/src/test/java/com/google/protobuf/nested_extension.proto
new file mode 100644
index 00000000..9fe5d560
--- /dev/null
+++ b/java/src/test/java/com/google/protobuf/nested_extension.proto
@@ -0,0 +1,45 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// http://code.google.com/p/protobuf/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Author: Darick Tong (darick@google.com)
+//
+// A proto file with nested extensions. Note that this must be defined in
+// a separate file to properly test the initialization of the outer class.
+
+
+import "com/google/protobuf/non_nested_extension.proto";
+
+package protobuf_unittest;
+
+message MyNestedExtension {
+ extend MessageToBeExtended {
+ optional MessageToBeExtended recursiveExtension = 2;
+ }
+}
diff --git a/java/src/test/java/com/google/protobuf/nested_extension_lite.proto b/java/src/test/java/com/google/protobuf/nested_extension_lite.proto
new file mode 100644
index 00000000..16ee46e5
--- /dev/null
+++ b/java/src/test/java/com/google/protobuf/nested_extension_lite.proto
@@ -0,0 +1,48 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// http://code.google.com/p/protobuf/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Author: Darick Tong (darick@google.com)
+//
+// A proto file with nested extensions for a MessageLite messages. Note that
+// this must be defined in a separate file to properly test the initialization
+// of the outer class.
+
+
+package protobuf_unittest;
+
+option optimize_for = LITE_RUNTIME;
+
+import "com/google/protobuf/non_nested_extension_lite.proto";
+
+message MyNestedExtensionLite {
+ extend MessageLiteToBeExtended {
+ optional MessageLiteToBeExtended recursiveExtensionLite = 3;
+ }
+}
diff --git a/java/src/test/java/com/google/protobuf/non_nested_extension.proto b/java/src/test/java/com/google/protobuf/non_nested_extension.proto
new file mode 100644
index 00000000..f61b419b
--- /dev/null
+++ b/java/src/test/java/com/google/protobuf/non_nested_extension.proto
@@ -0,0 +1,48 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// http://code.google.com/p/protobuf/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Author: Darick Tong (darick@google.com)
+//
+// A proto file with extensions.
+
+
+package protobuf_unittest;
+
+message MessageToBeExtended {
+ extensions 1 to max;
+}
+
+message MyNonNestedExtension {
+}
+
+extend MessageToBeExtended {
+ optional MyNonNestedExtension nonNestedExtension = 1;
+}
+
diff --git a/java/src/test/java/com/google/protobuf/non_nested_extension_lite.proto b/java/src/test/java/com/google/protobuf/non_nested_extension_lite.proto
new file mode 100644
index 00000000..3c82659b
--- /dev/null
+++ b/java/src/test/java/com/google/protobuf/non_nested_extension_lite.proto
@@ -0,0 +1,50 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// http://code.google.com/p/protobuf/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Author: Darick Tong (darick@google.com)
+//
+// A proto file with extensions for a MessageLite messages.
+
+
+package protobuf_unittest;
+
+option optimize_for = LITE_RUNTIME;
+
+message MessageLiteToBeExtended {
+ extensions 1 to max;
+}
+
+message MyNonNestedExtensionLite {
+}
+
+extend MessageLiteToBeExtended {
+ optional MyNonNestedExtensionLite nonNestedExtensionLite = 1;
+}
+
diff --git a/java/src/test/java/com/google/protobuf/test_bad_identifiers.proto b/java/src/test/java/com/google/protobuf/test_bad_identifiers.proto
new file mode 100644
index 00000000..5cc42af8
--- /dev/null
+++ b/java/src/test/java/com/google/protobuf/test_bad_identifiers.proto
@@ -0,0 +1,66 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// http://code.google.com/p/protobuf/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Author: jonp@google.com (Jon Perlow)
+
+// This file tests that various identifiers work as field and type names even
+// though the same identifiers are used internally by the java code generator.
+
+
+// Some generic_services option(s) added automatically.
+// See: http://go/proto2-generic-services-default
+option java_generic_services = true; // auto-added
+
+package io_protocol_tests;
+
+option java_package = "com.google.protobuf";
+option java_outer_classname = "TestBadIdentifiersProto";
+
+message TestMessage {
+}
+
+message Deprecated {
+ enum TestEnum {
+ FOO = 1;
+ }
+
+ optional int32 field1 = 1 [deprecated=true];
+ optional TestEnum field2 = 2 [deprecated=true];
+ optional TestMessage field3 = 3 [deprecated=true];
+}
+
+message Override {
+ optional int32 override = 1;
+}
+
+service TestConflictingMethodNames {
+ rpc Override(TestMessage) returns (TestMessage);
+}
+