diff --git a/CHANGES.txt b/CHANGES.txt
index 11b332eda8..6d1932be9a 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,3 +1,66 @@
+2010-11-01 version 2.4.0:
+
+ General
+ * The RPC (cc|java|py)_generic_services default value is now false instead of
+ true.
+ * Custom options can have aggregate types. For example,
+ message MyOption {
+ optional string comment = 1;
+ optional string author = 2;
+ }
+ extend google.protobuf.FieldOptions {
+ optional MyOption myoption = 12345;
+ }
+ This option can now be set as follows:
+ message SomeType {
+ optional int32 field = 1 [(myoption) = { comment:'x' author:'y' }];
+ }
+
+ C++
+ * Various speed and code size optimizations.
+ * Added a release_foo() method on string and message fields.
+ * Fixed gzip_output_stream sub-stream handling.
+
+ Java
+ * Builders now maintain sub-builders for sub-messages. Use getFooBuilder() to
+ get the builder for the sub-message "foo". This allows you to repeatedly
+ modify deeply-nested sub-messages without rebuilding them.
+ * Builder.build() no longer invalidates the Builder for generated messages
+ (You may continue to modify it and then build another message).
+ * Code generator will generate efficient equals() and hashCode()
+ implementations if new option java_generate_equals_and_hash is enabled.
+ (Otherwise, reflection-based implementations are used.)
+ * Generated messages now implement Serializable.
+ * Fields with [deprecated=true] will be marked with @Deprecated in Java.
+ * Added lazy conversion of UTF-8 encoded strings to String objects to improve
+ performance.
+ * Various optimizations.
+ * Enum value can be accessed directly, instead of calling getNumber() on the
+ enum member.
+ * For each enum value, an integer constant is also generated with the suffix
+ _VALUE.
+
+ Python
+ * Added an experimental C++ implementation for Python messages via a Python
+ extension. Implementation type is controlled by an environment variable
+ PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION (valid values: "cpp" and "python")
+ The default value is currently "python" but will be changed to "cpp" in
+ future release.
+ * Improved performance on message instantiation significantly.
+ Most of the work on message instantiation is done just once per message
+ class, instead of once per message instance.
+ * Improved performance on text message parsing.
+ * Allow add() to forward keyword arguments to the concrete class.
+ E.g. instead of
+ item = repeated_field.add()
+ item.foo = bar
+ item.baz = quux
+ You can do:
+ repeated_field.add(foo=bar, baz=quux)
+ * Added a sort() interface to the BaseContainer.
+ * Added an extend() method to repeated composite fields.
+ * Added UTF8 debug string support.
+
2010-01-08 version 2.3.0:
General
diff --git a/Makefile.am b/Makefile.am
index b65daee0ef..9b960fc583 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -83,33 +83,57 @@ EXTRA_DIST = \
java/src/main/java/com/google/protobuf/GeneratedMessageLite.java \
java/src/main/java/com/google/protobuf/Internal.java \
java/src/main/java/com/google/protobuf/InvalidProtocolBufferException.java \
+ java/src/main/java/com/google/protobuf/LazyStringArrayList.java \
+ java/src/main/java/com/google/protobuf/LazyStringList.java \
java/src/main/java/com/google/protobuf/Message.java \
java/src/main/java/com/google/protobuf/MessageLite.java \
+ java/src/main/java/com/google/protobuf/MessageLiteOrBuilder.java \
+ java/src/main/java/com/google/protobuf/MessageOrBuilder.java \
java/src/main/java/com/google/protobuf/ProtocolMessageEnum.java \
+ java/src/main/java/com/google/protobuf/RepeatedFieldBuilder.java \
java/src/main/java/com/google/protobuf/RpcCallback.java \
java/src/main/java/com/google/protobuf/RpcChannel.java \
java/src/main/java/com/google/protobuf/RpcController.java \
java/src/main/java/com/google/protobuf/RpcUtil.java \
java/src/main/java/com/google/protobuf/Service.java \
java/src/main/java/com/google/protobuf/ServiceException.java \
+ java/src/main/java/com/google/protobuf/SingleFieldBuilder.java \
+ java/src/main/java/com/google/protobuf/SmallSortedMap.java \
java/src/main/java/com/google/protobuf/TextFormat.java \
java/src/main/java/com/google/protobuf/UninitializedMessageException.java \
java/src/main/java/com/google/protobuf/UnknownFieldSet.java \
+ java/src/main/java/com/google/protobuf/UnmodifiableLazyStringList.java \
java/src/main/java/com/google/protobuf/WireFormat.java \
java/src/test/java/com/google/protobuf/AbstractMessageTest.java \
java/src/test/java/com/google/protobuf/CodedInputStreamTest.java \
java/src/test/java/com/google/protobuf/CodedOutputStreamTest.java \
+ java/src/test/java/com/google/protobuf/DeprecatedFieldTest.java \
java/src/test/java/com/google/protobuf/DescriptorsTest.java \
java/src/test/java/com/google/protobuf/DynamicMessageTest.java \
+ java/src/test/java/com/google/protobuf/ForceFieldBuildersPreRun.java \
java/src/test/java/com/google/protobuf/GeneratedMessageTest.java \
+ java/src/test/java/com/google/protobuf/LazyStringArrayListTest.java \
+ java/src/test/java/com/google/protobuf/LazyStringEndToEndTest.java \
java/src/test/java/com/google/protobuf/LiteTest.java \
java/src/test/java/com/google/protobuf/MessageTest.java \
+ java/src/test/java/com/google/protobuf/NestedBuildersTest.java \
+ java/src/test/java/com/google/protobuf/RepeatedFieldBuilderTest.java \
java/src/test/java/com/google/protobuf/ServiceTest.java \
+ java/src/test/java/com/google/protobuf/SingleFieldBuilderTest.java \
+ java/src/test/java/com/google/protobuf/SmallSortedMapTest.java \
+ java/src/test/java/com/google/protobuf/TestBadIdentifiers.java \
java/src/test/java/com/google/protobuf/TestUtil.java \
java/src/test/java/com/google/protobuf/TextFormatTest.java \
java/src/test/java/com/google/protobuf/UnknownFieldSetTest.java \
+ java/src/test/java/com/google/protobuf/UnmodifiableLazyStringListTest.java \
java/src/test/java/com/google/protobuf/WireFormatTest.java \
java/src/test/java/com/google/protobuf/multiple_files_test.proto \
+ java/src/test/java/com/google/protobuf/nested_builders_test.proto \
+ java/src/test/java/com/google/protobuf/nested_extension.proto \
+ java/src/test/java/com/google/protobuf/nested_extension_lite.proto \
+ java/src/test/java/com/google/protobuf/non_nested_extension.proto \
+ java/src/test/java/com/google/protobuf/non_nested_extension_lite.proto \
+ java/src/test/java/com/google/protobuf/test_bad_identifiers.proto \
java/pom.xml \
java/README.txt \
python/google/protobuf/internal/generator_test.py \
@@ -121,6 +145,9 @@ EXTRA_DIST = \
python/google/protobuf/internal/message_test.py \
python/google/protobuf/internal/more_extensions.proto \
python/google/protobuf/internal/more_messages.proto \
+ python/google/protobuf/internal/python_message.py \
+ python/google/protobuf/internal/cpp_message.py \
+ python/google/protobuf/internal/api_implementation.py \
python/google/protobuf/internal/reflection_test.py \
python/google/protobuf/internal/service_reflection_test.py \
python/google/protobuf/internal/test_util.py \
@@ -129,6 +156,11 @@ EXTRA_DIST = \
python/google/protobuf/internal/wire_format.py \
python/google/protobuf/internal/wire_format_test.py \
python/google/protobuf/internal/__init__.py \
+ python/google/protobuf/pyext/python-proto2.cc \
+ python/google/protobuf/pyext/python_descriptor.cc \
+ python/google/protobuf/pyext/python_descriptor.h \
+ python/google/protobuf/pyext/python_protobuf.cc \
+ python/google/protobuf/pyext/python_protobuf.h \
python/google/protobuf/descriptor.py \
python/google/protobuf/message.py \
python/google/protobuf/reflection.py \
diff --git a/java/pom.xml b/java/pom.xml
index 94f1d7f68b..cdf85de45e 100644
--- a/java/pom.xml
+++ b/java/pom.xml
@@ -105,6 +105,12 @@
+
+
+
+
+
+ map) {
+ for (Map.Entry 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()}.
+ *
+ * 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 5fade03ada..913563573e 100644
--- a/java/src/main/java/com/google/protobuf/ByteString.java
+++ b/java/src/main/java/com/google/protobuf/ByteString.java
@@ -193,6 +193,18 @@ public final class ByteString {
System.arraycopy(bytes, sourceOffset, target, targetOffset, size);
}
+ /**
+ * 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[]}.
*/
diff --git a/java/src/main/java/com/google/protobuf/CodedInputStream.java b/java/src/main/java/com/google/protobuf/CodedInputStream.java
index ad43f96d4f..b3e08555dc 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 58dd150673..51a932a381 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 c5e9a04bee..2ee8459474 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 bc1bb79790..a85dbaa634 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 fields;
+ private final SmallSortedMap 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();
+ this.fields = SmallSortedMap.newFieldMap(16);
}
/**
@@ -84,7 +79,8 @@ final class FieldSet 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 clone() {
+ // We can't just call fields.clone because List objects in the map
+ // should not be shared.
+ FieldSet clone = FieldSet.newFieldSet();
+ for (int i = 0; i < fields.getNumArrayEntries(); i++) {
+ Map.Entry entry = fields.getArrayEntryAt(i);
+ FieldDescriptorType descriptor = entry.getKey();
+ clone.setField(descriptor, entry.getValue());
+ }
+ for (Map.Entry entry :
+ fields.getOverflowEntries()) {
+ FieldDescriptorType descriptor = entry.getKey();
+ clone.setField(descriptor, entry.getValue());
+ }
+ return clone;
}
// =================================================================
@@ -126,12 +153,13 @@ final class FieldSet 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> iterator() {
return fields.entrySet().iterator();
@@ -336,27 +364,39 @@ final class FieldSet entry:
- fields.entrySet()) {
- final FieldDescriptorType descriptor = entry.getKey();
- if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE) {
- if (descriptor.isRepeated()) {
- for (final MessageLite element:
- (List) 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 entry :
+ fields.getOverflowEntries()) {
+ if (!isInitialized(entry)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @SuppressWarnings("unchecked")
+ private boolean isInitialized(
+ final Map.Entry entry) {
+ final FieldDescriptorType descriptor = entry.getKey();
+ if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE) {
+ if (descriptor.isRepeated()) {
+ for (final MessageLite element:
+ (List) entry.getValue()) {
+ if (!element.isInitialized()) {
return false;
}
}
+ } else {
+ if (!((MessageLite) entry.getValue()).isInitialized()) {
+ return false;
+ }
}
}
-
return true;
}
@@ -378,39 +418,48 @@ final class FieldSet other) {
- for (final Map.Entry 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 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 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 entry:
- fields.entrySet()) {
+ for (int i = 0; i < fields.getNumArrayEntries(); i++) {
+ final Map.Entry entry =
+ fields.getArrayEntryAt(i);
+ writeField(entry.getKey(), entry.getValue(), output);
+ }
+ for (final Map.Entry entry :
+ fields.getOverflowEntries()) {
writeField(entry.getKey(), entry.getValue(), output);
}
}
@@ -479,16 +533,25 @@ final class FieldSet 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 entry :
+ fields.getOverflowEntries()) {
+ writeMessageSetTo(entry, output);
+ }
+ }
+
+ private void writeMessageSetTo(
+ final Map.Entry 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 entry:
- fields.entrySet()) {
+ for (int i = 0; i < fields.getNumArrayEntries(); i++) {
+ final Map.Entry entry =
+ fields.getArrayEntryAt(i);
+ size += computeFieldSize(entry.getKey(), entry.getValue());
+ }
+ for (final Map.Entry entry :
+ fields.getOverflowEntries()) {
size += computeFieldSize(entry.getKey(), entry.getValue());
}
return size;
@@ -605,20 +673,28 @@ final class FieldSet 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 entry :
+ fields.getOverflowEntries()) {
+ size += getMessageSetSerializedSize(entry);
}
return size;
}
+ private int getMessageSetSerializedSize(
+ final Map.Entry 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 42ccbfd86a..fc2e530357 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 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.
+ *
+ * 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
extends AbstractMessage.Builder {
- 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 getAllFields() {
- return internalGetResult().getAllFields();
+ return Collections.unmodifiableMap(getAllFieldsMutable());
+ }
+
+ /** Internal helper which returns a mutable map. */
+ private Map getAllFieldsMutable() {
+ final TreeMap result =
+ new TreeMap();
+ 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 messageList = (List) 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. */
+ boolean hasExtension(
+ GeneratedExtension extension);
+
+ /** Get the number of elements in a repeated extension. */
+ int getExtensionCount(
+ GeneratedExtension> extension);
+
+ /** Get the value of an extension. */
+ Type getExtension(GeneratedExtension extension);
+
+ /** Get one element of a repeated extension. */
+ Type getExtension(
+ GeneratedExtension> 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 extensions = FieldSet.newFieldSet();
+ extends GeneratedMessage
+ implements ExtendableMessageOrBuilder {
+
+ private final FieldSet extensions;
+
+ protected ExtendableMessage() {
+ this.extensions = FieldSet.newFieldSet();
+ }
+
+ protected ExtendableMessage(
+ ExtendableBuilder builder) {
+ super(builder);
+ this.extensions = builder.buildExtensions();
+ }
private void verifyExtensionContainingType(
final GeneratedExtension extension) {
@@ -330,13 +564,15 @@ public abstract class GeneratedMessage extends AbstractMessage {
}
/** Check if a singular extension is present. */
- public final boolean hasExtension(
- final GeneratedExtension extension) {
+ //@Override (Java 1.6 override semantics, but we must support 1.5)
+ public final boolean hasExtension(
+ final GeneratedExtension 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 int getExtensionCount(
final GeneratedExtension> 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 getExtension(
final GeneratedExtension 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 getExtension(
final GeneratedExtension> extension,
@@ -448,10 +686,14 @@ public abstract class GeneratedMessage extends AbstractMessage {
// ---------------------------------------------------------------
// Reflection
+ protected Map getExtensionFields() {
+ return extensions.getAllFields();
+ }
+
@Override
public Map getAllFields() {
final Map 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 {
+ extends Builder
+ implements ExtendableMessageOrBuilder {
+
+ private FieldSet 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 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 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 boolean hasExtension(
+ final GeneratedExtension 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 int getExtensionCount(
final GeneratedExtension> 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 getExtension(
final GeneratedExtension 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 getExtension(
final GeneratedExtension> 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 BuilderType setExtension(
final GeneratedExtension extension,
final Type value) {
- final ExtendableMessage 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 BuilderType setExtension(
final GeneratedExtension> extension,
final int index, final Type value) {
- final ExtendableMessage 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 BuilderType addExtension(
final GeneratedExtension> extension,
final Type value) {
- final ExtendableMessage 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 BuilderType clearExtension(
final GeneratedExtension extension) {
- final ExtendableMessage 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 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 getAllFields() {
+ final Map 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 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 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 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 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
GeneratedExtension
- newGeneratedExtension() {
- return new GeneratedExtension();
+ 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(
+ new ExtensionDescriptorRetriever() {
+ @Override
+ public FieldDescriptor getDescriptor() {
+ return scope.getDescriptorForType().getExtensions()
+ .get(descriptorIndex);
+ }
+ },
+ singularType,
+ defaultInstance);
+ }
+
+ /** For use by generated code only. */
+ public static
+ GeneratedExtension
+ 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(
+ 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 return super.writeReplace();
+ * @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 9a799b44c6..30a7571574 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 {
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.
@@ -65,12 +80,6 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite {
// Defined here for return type covariance.
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.
@@ -86,15 +95,46 @@ 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. */
+ boolean hasExtension(
+ GeneratedExtension extension);
+
+ /** Get the number of elements in a repeated extension. */
+ int getExtensionCount(
+ GeneratedExtension> extension);
+
+ /** Get the value of an extension. */
+ Type getExtension(GeneratedExtension extension);
+
+ /** Get one element of a repeated extension. */
+ Type getExtension(
+ GeneratedExtension> extension,
+ int index);
+ }
+
/**
* Lite equivalent of {@link GeneratedMessage.ExtendableMessage}.
*/
public abstract static class ExtendableMessage<
MessageType extends ExtendableMessage>
- extends GeneratedMessageLite {
- protected ExtendableMessage() {}
- private final FieldSet extensions =
- FieldSet.newFieldSet();
+ extends GeneratedMessageLite
+ implements ExtendableMessageOrBuilder {
+
+ private final FieldSet extensions;
+
+ protected ExtendableMessage() {
+ this.extensions = FieldSet.newFieldSet();
+ }
+
+ protected ExtendableMessage(ExtendableBuilder builder) {
+ this.extensions = builder.buildExtensions();
+ }
private void verifyExtensionContainingType(
final GeneratedExtension extension) {
@@ -108,13 +148,15 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite {
}
/** Check if a singular extension is present. */
- public final boolean hasExtension(
- final GeneratedExtension extension) {
+ //@Override (Java 1.6 override semantics, but we must support 1.5)
+ public final boolean hasExtension(
+ final GeneratedExtension 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 int getExtensionCount(
final GeneratedExtension> 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 getExtension(
final GeneratedExtension 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 getExtension(
final GeneratedExtension> extension,
@@ -214,53 +258,104 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite {
public abstract static class ExtendableBuilder<
MessageType extends ExtendableMessage,
BuilderType extends ExtendableBuilder>
- extends Builder {
+ extends Builder
+ implements ExtendableMessageOrBuilder {
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 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 buildExtensions() {
+ extensions.makeImmutable();
+ extensionsIsMutable = false;
+ return extensions;
+ }
+
+ private void verifyExtensionContainingType(
final GeneratedExtension 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 boolean hasExtension(
+ final GeneratedExtension 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 int getExtensionCount(
final GeneratedExtension> 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 getExtension(
final GeneratedExtension 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 getExtension(
final GeneratedExtension> 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 BuilderType setExtension(
final GeneratedExtension extension,
final Type value) {
- final ExtendableMessage 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 BuilderType setExtension(
final GeneratedExtension> extension,
final int index, final Type value) {
- final ExtendableMessage 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 BuilderType addExtension(
final GeneratedExtension> extension,
final Type value) {
- final ExtendableMessage 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 BuilderType clearExtension(
final GeneratedExtension extension) {
- final ExtendableMessage 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 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
GeneratedExtension
- newGeneratedExtension() {
- return new GeneratedExtension();
+ newSingularGeneratedExtension(
+ final ContainingType containingTypeDefaultInstance,
+ final Type defaultValue,
+ final MessageLite messageDefaultInstance,
+ final Internal.EnumLiteMap> enumTypeMap,
+ final int number,
+ final WireFormat.FieldType type) {
+ return new GeneratedExtension(
+ containingTypeDefaultInstance,
+ defaultValue,
+ messageDefaultInstance,
+ new ExtensionDescriptor(enumTypeMap, number, type,
+ false /* isRepeated */,
+ false /* isPacked */));
+ }
+
+ /** For use by generated code only. */
+ public static
+ GeneratedExtension
+ 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(
+ 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 return super.writeReplace();
+ * @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 965465e14b..05eab57ad4 100644
--- a/java/src/main/java/com/google/protobuf/Internal.java
+++ b/java/src/main/java/com/google/protobuf/Internal.java
@@ -99,6 +99,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.
+ *
+ * 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
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 0000000000..1683a64000
--- /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.
+ *
+ * Note that this implementation is not synchronized.
+ * If multiple threads access an ArrayList instance concurrently,
+ * and at least one of the threads modifies the list structurally, it
+ * must 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.
+ *
+ * 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
+ implements LazyStringList, RandomAccess {
+
+ public final static LazyStringList EMPTY = new UnmodifiableLazyStringList(
+ new LazyStringArrayList());
+
+ private final List