From 885b612f74f133678bf82808c589331e4c59dad9 Mon Sep 17 00:00:00 2001 From: Jisi Liu Date: Sat, 28 Feb 2015 14:51:22 -0800 Subject: Down integrate from Google internal branch for C++ and Java. - Maps for C++ lite - C++ Arena optimizations. - Java Lite runtime code size optimization. Change-Id: I7537a4357c1cb385d23f9e8aa7ffdfeefe079f13 --- .../java/com/google/protobuf/AbstractMessage.java | 6 +- .../com/google/protobuf/BoundedByteString.java | 2 +- .../main/java/com/google/protobuf/ByteString.java | 7 +- .../main/java/com/google/protobuf/Descriptors.java | 2 +- .../java/com/google/protobuf/DynamicMessage.java | 11 +- .../main/java/com/google/protobuf/FieldSet.java | 56 ++--- .../java/com/google/protobuf/GeneratedMessage.java | 212 ++++++++++++++++-- .../com/google/protobuf/GeneratedMessageLite.java | 145 ++++++++++-- .../java/com/google/protobuf/LazyFieldLite.java | 249 +++++++++++++++++---- .../com/google/protobuf/LiteralByteString.java | 6 +- .../src/main/java/com/google/protobuf/Message.java | 4 +- .../com/google/protobuf/MessageReflection.java | 60 +++-- .../java/com/google/protobuf/RopeByteString.java | 8 +- .../main/java/com/google/protobuf/TextFormat.java | 119 +++++----- .../main/java/com/google/protobuf/WireFormat.java | 82 +++++++ .../com/google/protobuf/GeneratedMessageTest.java | 2 +- .../com/google/protobuf/LazyFieldLiteTest.java | 139 +++++++++++- .../com/google/protobuf/LazyMessageLiteTest.java | 19 ++ .../com/google/protobuf/LiteralByteStringTest.java | 24 +- .../java/com/google/protobuf/MapForProto2Test.java | 14 ++ .../src/test/java/com/google/protobuf/MapTest.java | 1 - .../com/google/protobuf/RopeByteStringTest.java | 28 +++ .../com/google/protobuf/lazy_fields_lite.proto | 9 + .../google/protobuf/map_for_proto2_lite_test.proto | 7 + .../com/google/protobuf/map_for_proto2_test.proto | 7 + .../test/java/com/google/protobuf/map_test.proto | 1 + .../com/google/protobuf/test_bad_identifiers.proto | 1 + 27 files changed, 1011 insertions(+), 210 deletions(-) (limited to 'java') diff --git a/java/src/main/java/com/google/protobuf/AbstractMessage.java b/java/src/main/java/com/google/protobuf/AbstractMessage.java index 6de4cae3..cc89173a 100644 --- a/java/src/main/java/com/google/protobuf/AbstractMessage.java +++ b/java/src/main/java/com/google/protobuf/AbstractMessage.java @@ -83,10 +83,10 @@ public abstract class AbstractMessage extends AbstractMessageLite } public void writeTo(final CodedOutputStream output) throws IOException { - MessageReflection.writeMessageTo(this, output, false); + MessageReflection.writeMessageTo(this, getAllFields(), output, false); } - private int memoizedSize = -1; + protected int memoizedSize = -1; public int getSerializedSize() { int size = memoizedSize; @@ -94,7 +94,7 @@ public abstract class AbstractMessage extends AbstractMessageLite return size; } - memoizedSize = MessageReflection.getSerializedSize(this); + memoizedSize = MessageReflection.getSerializedSize(this, getAllFields()); return memoizedSize; } diff --git a/java/src/main/java/com/google/protobuf/BoundedByteString.java b/java/src/main/java/com/google/protobuf/BoundedByteString.java index 8cb6f463..b4c3fb1b 100644 --- a/java/src/main/java/com/google/protobuf/BoundedByteString.java +++ b/java/src/main/java/com/google/protobuf/BoundedByteString.java @@ -30,8 +30,8 @@ package com.google.protobuf; -import java.io.InvalidObjectException; import java.io.IOException; +import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.util.NoSuchElementException; diff --git a/java/src/main/java/com/google/protobuf/ByteString.java b/java/src/main/java/com/google/protobuf/ByteString.java index cff1ee51..9b0a524b 100644 --- a/java/src/main/java/com/google/protobuf/ByteString.java +++ b/java/src/main/java/com/google/protobuf/ByteString.java @@ -76,6 +76,9 @@ public abstract class ByteString implements Iterable, Serializable { static final int MIN_READ_FROM_CHUNK_SIZE = 0x100; // 256b static final int MAX_READ_FROM_CHUNK_SIZE = 0x2000; // 8k + // Defined by java.nio.charset.Charset + protected static final String UTF_8 = "UTF-8"; + /** * Empty {@code ByteString}. */ @@ -267,7 +270,7 @@ public abstract class ByteString implements Iterable, Serializable { */ public static ByteString copyFromUtf8(String text) { try { - return new LiteralByteString(text.getBytes("UTF-8")); + return new LiteralByteString(text.getBytes(UTF_8)); } catch (UnsupportedEncodingException e) { throw new RuntimeException("UTF-8 not supported?", e); } @@ -622,7 +625,7 @@ public abstract class ByteString implements Iterable, Serializable { */ public String toStringUtf8() { try { - return toString("UTF-8"); + return toString(UTF_8); } catch (UnsupportedEncodingException e) { throw new RuntimeException("UTF-8 not supported?", e); } diff --git a/java/src/main/java/com/google/protobuf/Descriptors.java b/java/src/main/java/com/google/protobuf/Descriptors.java index d65e8b49..806f46c1 100644 --- a/java/src/main/java/com/google/protobuf/Descriptors.java +++ b/java/src/main/java/com/google/protobuf/Descriptors.java @@ -897,7 +897,7 @@ public final class Descriptors { return (type == Type.STRING) && (getFile().getOptions().getJavaStringCheckUtf8()); } - boolean isMapField() { + public boolean isMapField() { return getType() == Type.MESSAGE && isRepeated() && getMessageType().getOptions().getMapEntry(); } diff --git a/java/src/main/java/com/google/protobuf/DynamicMessage.java b/java/src/main/java/com/google/protobuf/DynamicMessage.java index 9c5e6c61..3ea1b688 100644 --- a/java/src/main/java/com/google/protobuf/DynamicMessage.java +++ b/java/src/main/java/com/google/protobuf/DynamicMessage.java @@ -35,8 +35,8 @@ import com.google.protobuf.Descriptors.EnumValueDescriptor; import com.google.protobuf.Descriptors.FieldDescriptor; import com.google.protobuf.Descriptors.OneofDescriptor; -import java.io.InputStream; import java.io.IOException; +import java.io.InputStream; import java.util.Collections; import java.util.List; import java.util.Map; @@ -600,9 +600,12 @@ public final class DynamicMessage extends AbstractMessage { } // TODO(xiaofeng): Re-enable this check after Orgstore is fixed to not // set incorrect EnumValueDescriptors. - // if (field.getEnumType() != ((EnumValueDescriptor) value).getType()) { - // throw new IllegalArgumentException( - // "EnumValueDescriptor doesn't match Enum Field."); + // EnumDescriptor fieldType = field.getEnumType(); + // EnumDescriptor fieldValueType = ((EnumValueDescriptor) value).getType(); + // if (fieldType != fieldValueType) { + // throw new IllegalArgumentException(String.format( + // "EnumDescriptor %s of field doesn't match EnumDescriptor %s of field value", + // fieldType.getFullName(), fieldValueType.getFullName())); // } } diff --git a/java/src/main/java/com/google/protobuf/FieldSet.java b/java/src/main/java/com/google/protobuf/FieldSet.java index ff9b5bc0..47924b65 100644 --- a/java/src/main/java/com/google/protobuf/FieldSet.java +++ b/java/src/main/java/com/google/protobuf/FieldSet.java @@ -553,42 +553,13 @@ final class FieldSet getAllFieldsMutable() { + /** + * Internal helper to return a modifiable map containing all the fields. + * The returned Map is modifialbe so that the caller can add additional + * extension fields to implement {@link #getAllFields()}. + * + * @param getBytesForString whether to generate ByteString for string fields + */ + private Map getAllFieldsMutable( + boolean getBytesForString) { final TreeMap result = new TreeMap(); final Descriptor descriptor = internalGetFieldAccessorTable().descriptor; @@ -122,7 +129,12 @@ public abstract class GeneratedMessage extends AbstractMessage } } else { if (hasField(field)) { - result.put(field, getField(field)); + if (getBytesForString + && field.getJavaType() == FieldDescriptor.JavaType.STRING) { + result.put(field, getFieldRaw(field)); + } else { + result.put(field, getField(field)); + } } } } @@ -161,7 +173,23 @@ public abstract class GeneratedMessage extends AbstractMessage //@Override (Java 1.6 override semantics, but we must support 1.5) public Map getAllFields() { - return Collections.unmodifiableMap(getAllFieldsMutable()); + return Collections.unmodifiableMap( + getAllFieldsMutable(/* getBytesForString = */ false)); + } + + /** + * Returns a collection of all the fields in this message which are set + * and their corresponding values. A singular ("required" or "optional") + * field is set iff hasField() returns true for that field. A "repeated" + * field is set iff getRepeatedFieldCount() is greater than zero. The + * values are exactly what would be returned by calling + * {@link #getFieldRaw(Descriptors.FieldDescriptor)} for each field. The map + * is guaranteed to be a sorted map, so iterating over it will return fields + * in order by field number. + */ + Map getAllFieldsRaw() { + return Collections.unmodifiableMap( + getAllFieldsMutable(/* getBytesForString = */ true)); } //@Override (Java 1.6 override semantics, but we must support 1.5) @@ -184,6 +212,18 @@ public abstract class GeneratedMessage extends AbstractMessage return internalGetFieldAccessorTable().getField(field).get(this); } + /** + * Obtains the value of the given field, or the default value if it is + * not set. For primitive fields, the boxed primitive value is returned. + * For enum fields, the EnumValueDescriptor for the value is returned. For + * embedded message fields, the sub-message is returned. For repeated + * fields, a java.util.List is returned. For present string fields, a + * ByteString is returned representing the bytes that the field contains. + */ + Object getFieldRaw(final FieldDescriptor field) { + return internalGetFieldAccessorTable().getField(field).getRaw(this); + } + //@Override (Java 1.6 override semantics, but we must support 1.5) public int getRepeatedFieldCount(final FieldDescriptor field) { return internalGetFieldAccessorTable().getField(field) @@ -214,6 +254,24 @@ public abstract class GeneratedMessage extends AbstractMessage return unknownFields.mergeFieldFrom(tag, input); } + @Override + public void writeTo(final CodedOutputStream output) throws IOException { + MessageReflection.writeMessageTo(this, getAllFieldsRaw(), output, false); + } + + @Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) { + return size; + } + + memoizedSize = MessageReflection.getSerializedSize( + this, getAllFieldsRaw()); + return memoizedSize; + } + + /** * Used by parsing constructors in generated classes. @@ -563,6 +621,15 @@ public abstract class GeneratedMessage extends AbstractMessage throw new RuntimeException( "No map fields found in " + getClass().getName()); } + + /** Like {@link internalGetMapField} but return a mutable version. */ + @SuppressWarnings({"unused", "rawtypes"}) + protected MapField internalGetMutableMapField(int fieldNumber) { + // Note that we can't use descriptor names here because this method will + // be called when descriptor is being initialized. + throw new RuntimeException( + "No map fields found in " + getClass().getName()); + } } // ================================================================= @@ -825,7 +892,16 @@ public abstract class GeneratedMessage extends AbstractMessage @Override public Map getAllFields() { - final Map result = super.getAllFieldsMutable(); + final Map result = + super.getAllFieldsMutable(/* getBytesForString = */ false); + result.putAll(getExtensionFields()); + return Collections.unmodifiableMap(result); + } + + @Override + public Map getAllFieldsRaw() { + final Map result = + super.getAllFieldsMutable(/* getBytesForString = */ false); result.putAll(getExtensionFields()); return Collections.unmodifiableMap(result); } @@ -1761,6 +1837,10 @@ public abstract class GeneratedMessage extends AbstractMessage fields[i] = new SingularEnumFieldAccessor( field, camelCaseNames[i], messageClass, builderClass, containingOneofCamelCaseName); + } else if (field.getJavaType() == FieldDescriptor.JavaType.STRING) { + fields[i] = new SingularStringFieldAccessor( + field, camelCaseNames[i], messageClass, builderClass, + containingOneofCamelCaseName); } else { fields[i] = new SingularFieldAccessor( field, camelCaseNames[i], messageClass, builderClass, @@ -1817,9 +1897,13 @@ public abstract class GeneratedMessage extends AbstractMessage private interface FieldAccessor { Object get(GeneratedMessage message); Object get(GeneratedMessage.Builder builder); + Object getRaw(GeneratedMessage message); + Object getRaw(GeneratedMessage.Builder builder); void set(Builder builder, Object value); Object getRepeated(GeneratedMessage message, int index); Object getRepeated(GeneratedMessage.Builder builder, int index); + Object getRepeatedRaw(GeneratedMessage message, int index); + Object getRepeatedRaw(GeneratedMessage.Builder builder, int index); void setRepeated(Builder builder, int index, Object value); void addRepeated(Builder builder, Object value); @@ -1949,6 +2033,12 @@ public abstract class GeneratedMessage extends AbstractMessage public Object get(GeneratedMessage.Builder builder) { return invokeOrDie(getMethodBuilder, builder); } + public Object getRaw(final GeneratedMessage message) { + return get(message); + } + public Object getRaw(GeneratedMessage.Builder builder) { + return get(builder); + } public void set(final Builder builder, final Object value) { invokeOrDie(setMethod, builder, value); } @@ -1957,12 +2047,22 @@ public abstract class GeneratedMessage extends AbstractMessage throw new UnsupportedOperationException( "getRepeatedField() called on a singular field."); } + public Object getRepeatedRaw(final GeneratedMessage message, + final int index) { + throw new UnsupportedOperationException( + "getRepeatedFieldRaw() called on a singular field."); + } public Object getRepeated(GeneratedMessage.Builder builder, int index) { throw new UnsupportedOperationException( "getRepeatedField() called on a singular field."); } - public void setRepeated(final Builder builder, - final int index, final Object value) { + public Object getRepeatedRaw(GeneratedMessage.Builder builder, + int index) { + throw new UnsupportedOperationException( + "getRepeatedFieldRaw() called on a singular field."); + } + public void setRepeated(final Builder builder, final int index, + final Object value) { throw new UnsupportedOperationException( "setRepeatedField() called on a singular field."); } @@ -2058,6 +2158,12 @@ public abstract class GeneratedMessage extends AbstractMessage public Object get(GeneratedMessage.Builder builder) { return invokeOrDie(getMethodBuilder, builder); } + public Object getRaw(final GeneratedMessage message) { + return get(message); + } + public Object getRaw(GeneratedMessage.Builder builder) { + return get(builder); + } public void set(final Builder builder, final Object value) { // Add all the elements individually. This serves two purposes: // 1) Verifies that each element has the correct type. @@ -2075,6 +2181,13 @@ public abstract class GeneratedMessage extends AbstractMessage public Object getRepeated(GeneratedMessage.Builder builder, int index) { return invokeOrDie(getRepeatedMethodBuilder, builder, index); } + public Object getRepeatedRaw(GeneratedMessage message, int index) { + return getRepeated(message, index); + } + public Object getRepeatedRaw(GeneratedMessage.Builder builder, + int index) { + return getRepeated(builder, index); + } public void setRepeated(final Builder builder, final int index, final Object value) { invokeOrDie(setRepeatedMethod, builder, index, value); @@ -2139,6 +2252,12 @@ public abstract class GeneratedMessage extends AbstractMessage return (MapField) builder.internalGetMapField(field.getNumber()); } + private MapField getMutableMapField( + GeneratedMessage.Builder builder) { + return (MapField) builder.internalGetMutableMapField( + field.getNumber()); + } + public Object get(GeneratedMessage message) { List result = new ArrayList(); for (int i = 0; i < getRepeatedCount(message); i++) { @@ -2155,6 +2274,14 @@ public abstract class GeneratedMessage extends AbstractMessage return Collections.unmodifiableList(result); } + public Object getRaw(GeneratedMessage message) { + return get(message); + } + + public Object getRaw(GeneratedMessage.Builder builder) { + return get(builder); + } + public void set(Builder builder, Object value) { clear(builder); for (Object entry : (List) value) { @@ -2170,14 +2297,20 @@ public abstract class GeneratedMessage extends AbstractMessage return getMapField(builder).getList().get(index); } + public Object getRepeatedRaw(GeneratedMessage message, int index) { + return getRepeated(message, index); + } + + public Object getRepeatedRaw(Builder builder, int index) { + return getRepeated(builder, index); + } + public void setRepeated(Builder builder, int index, Object value) { - builder.onChanged(); - getMapField(builder).getMutableList().set(index, (Message) value); + getMutableMapField(builder).getMutableList().set(index, (Message) value); } public void addRepeated(Builder builder, Object value) { - builder.onChanged(); - getMapField(builder).getMutableList().add((Message) value); + getMutableMapField(builder).getMutableList().add((Message) value); } public boolean has(GeneratedMessage message) { @@ -2199,8 +2332,7 @@ public abstract class GeneratedMessage extends AbstractMessage } public void clear(Builder builder) { - builder.onChanged(); - getMapField(builder).getMutableList().clear(); + getMutableMapField(builder).getMutableList().clear(); } public com.google.protobuf.Message.Builder newBuilder() { @@ -2391,6 +2523,60 @@ public abstract class GeneratedMessage extends AbstractMessage // --------------------------------------------------------------- + /** + * Field accessor for string fields. + * + *

This class makes getFooBytes() and setFooBytes() available for + * reflection API so that reflection based serialize/parse functions can + * access the raw bytes of the field to preserve non-UTF8 bytes in the + * string. + * + *

This ensures the serialize/parse round-trip safety, which is important + * for servers which forward messages. + */ + private static final class SingularStringFieldAccessor + extends SingularFieldAccessor { + SingularStringFieldAccessor( + final FieldDescriptor descriptor, final String camelCaseName, + final Class messageClass, + final Class builderClass, + final String containingOneofCamelCaseName) { + super(descriptor, camelCaseName, messageClass, builderClass, + containingOneofCamelCaseName); + getBytesMethod = getMethodOrDie(messageClass, + "get" + camelCaseName + "Bytes"); + getBytesMethodBuilder = getMethodOrDie(builderClass, + "get" + camelCaseName + "Bytes"); + setBytesMethodBuilder = getMethodOrDie(builderClass, + "set" + camelCaseName + "Bytes", ByteString.class); + } + + private final Method getBytesMethod; + private final Method getBytesMethodBuilder; + private final Method setBytesMethodBuilder; + + @Override + public Object getRaw(final GeneratedMessage message) { + return invokeOrDie(getBytesMethod, message); + } + + @Override + public Object getRaw(GeneratedMessage.Builder builder) { + return invokeOrDie(getBytesMethodBuilder, builder); + } + + @Override + public void set(GeneratedMessage.Builder builder, Object value) { + if (value instanceof ByteString) { + invokeOrDie(setBytesMethodBuilder, builder, value); + } else { + super.set(builder, value); + } + } + } + + // --------------------------------------------------------------- + private static final class SingularMessageFieldAccessor extends SingularFieldAccessor { SingularMessageFieldAccessor( diff --git a/java/src/main/java/com/google/protobuf/GeneratedMessageLite.java b/java/src/main/java/com/google/protobuf/GeneratedMessageLite.java index 4d25c077..6839c9dd 100644 --- a/java/src/main/java/com/google/protobuf/GeneratedMessageLite.java +++ b/java/src/main/java/com/google/protobuf/GeneratedMessageLite.java @@ -42,22 +42,58 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** * Lite version of {@link GeneratedMessage}. * * @author kenton@google.com Kenton Varda */ -public abstract class GeneratedMessageLite extends AbstractMessageLite - implements Serializable { +public abstract class GeneratedMessageLite< + MessageType extends GeneratedMessageLite, + BuilderType extends GeneratedMessageLite.Builder> + extends AbstractMessageLite + implements Serializable { + + /** + * Holds all the {@link PrototypeHolder}s for loaded classes. + */ + // TODO(dweis): Consider different concurrency values. + // TODO(dweis): This will prevent garbage collection of the class loader. + // Ideally we'd use something like ClassValue but that's Java 7 only. + private static final Map, PrototypeHolder> PROTOTYPE_MAP = + new ConcurrentHashMap, PrototypeHolder>(); + + // For use by generated code only. + protected static < + MessageType extends GeneratedMessageLite, + BuilderType extends GeneratedMessageLite.Builder< + MessageType, BuilderType>> void onLoad(Class clazz, + PrototypeHolder protoTypeHolder) { + PROTOTYPE_MAP.put(clazz, protoTypeHolder); + } + private static final long serialVersionUID = 1L; /** For use by generated code only. */ protected UnknownFieldSetLite unknownFields; - public Parser getParserForType() { - throw new UnsupportedOperationException( - "This is supposed to be overridden by subclasses."); + @SuppressWarnings("unchecked") // Guaranteed by runtime. + public final Parser getParserForType() { + return (Parser) PROTOTYPE_MAP + .get(getClass()).getParserForType(); + } + + @SuppressWarnings("unchecked") // Guaranteed by runtime. + public final MessageType getDefaultInstanceForType() { + return (MessageType) PROTOTYPE_MAP + .get(getClass()).getDefaultInstanceForType(); + } + + @SuppressWarnings("unchecked") // Guaranteed by runtime. + public final BuilderType newBuilderForType() { + return (BuilderType) PROTOTYPE_MAP + .get(getClass()).newBuilderForType(); } /** @@ -73,10 +109,17 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite return unknownFields.mergeFieldFrom(tag, input); } + // The default behavior. If a message has required fields in its subtree, the + // generated code will override. + public boolean isInitialized() { + return true; + } + @SuppressWarnings("unchecked") - public abstract static class Builder - extends AbstractMessageLite.Builder { + public abstract static class Builder< + MessageType extends GeneratedMessageLite, + BuilderType extends Builder> + extends AbstractMessageLite.Builder { private final MessageType defaultInstance; @@ -88,6 +131,12 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite this.defaultInstance = defaultInstance; } + // The default behavior. If a message has required fields in its subtree, + // the generated code will override. + public boolean isInitialized() { + return true; + } + //@Override (Java 1.6 override semantics, but we must support 1.5) public BuilderType clear() { unknownFields = UnknownFieldSetLite.getDefaultInstance(); @@ -174,7 +223,9 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite * Lite equivalent of {@link com.google.protobuf.GeneratedMessage.ExtendableMessageOrBuilder}. */ public interface ExtendableMessageOrBuilder< - MessageType extends ExtendableMessage> extends MessageLiteOrBuilder { + MessageType extends ExtendableMessage, + BuilderType extends ExtendableBuilder> + extends MessageLiteOrBuilder { /** Check if a singular extension is present. */ boolean hasExtension( @@ -197,16 +248,30 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite * Lite equivalent of {@link GeneratedMessage.ExtendableMessage}. */ public abstract static class ExtendableMessage< - MessageType extends ExtendableMessage> - extends GeneratedMessageLite - implements ExtendableMessageOrBuilder { + MessageType extends ExtendableMessage, + BuilderType extends ExtendableBuilder> + extends GeneratedMessageLite + implements ExtendableMessageOrBuilder { /** * Represents the set of extensions on this message. For use by generated * code only. */ protected FieldSet extensions = FieldSet.newFieldSet(); - + + // -1 => not memoized, 0 => false, 1 => true. + private byte memoizedIsInitialized = -1; + + // The default behavior. If a message has required fields in its subtree, + // the generated code will override. + public boolean isInitialized() { + if (memoizedIsInitialized == -1) { + memoizedIsInitialized = (byte) (extensions.isInitialized() ? 1 : 0); + } + + return memoizedIsInitialized == 1; + } + private void verifyExtensionContainingType( final GeneratedExtension extension) { if (extension.getContainingTypeDefaultInstance() != @@ -349,10 +414,10 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite */ @SuppressWarnings("unchecked") public abstract static class ExtendableBuilder< - MessageType extends ExtendableMessage, + MessageType extends ExtendableMessage, BuilderType extends ExtendableBuilder> extends Builder - implements ExtendableMessageOrBuilder { + implements ExtendableMessageOrBuilder { protected ExtendableBuilder(MessageType defaultInstance) { super(defaultInstance); } @@ -360,6 +425,12 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite private FieldSet extensions = FieldSet.emptySet(); private boolean extensionsIsMutable; + // The default behavior. If a message has required fields in its subtree, + // the generated code will override. + public boolean isInitialized() { + return extensions.isInitialized(); + } + // For immutable message conversion. void internalSetExtensionSet(FieldSet extensions) { this.extensions = extensions; @@ -991,7 +1062,10 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite * Checks that the {@link Extension} is Lite and returns it as a * {@link GeneratedExtension}. */ - private static , T> + private static < + MessageType extends ExtendableMessage, + BuilderType extends ExtendableBuilder, + T> GeneratedExtension checkIsLite( ExtensionLite extension) { if (!extension.isLite()) { @@ -1000,4 +1074,43 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite return (GeneratedExtension) extension; } + + /** + * Represents the state needed to implement *ForType methods. Generated code + * must provide a static singleton instance by adding it with + * {@link GeneratedMessageLite#onLoad(Class, PrototypeHolder)} on class load. + *

    + *
  • {@link #getDefaultInstanceForType()} + *
  • {@link #getParserForType()} + *
  • {@link #newBuilderForType()} + *
+ * This allows us to trade three generated methods for a static Map. + */ + protected static class PrototypeHolder< + MessageType extends GeneratedMessageLite, + BuilderType extends GeneratedMessageLite.Builder< + MessageType, BuilderType>> { + + private final MessageType defaultInstance; + private final Parser parser; + + public PrototypeHolder( + MessageType defaultInstance, Parser parser) { + this.defaultInstance = defaultInstance; + this.parser = parser; + } + + public MessageType getDefaultInstanceForType() { + return defaultInstance; + } + + public Parser getParserForType() { + return parser; + } + + @SuppressWarnings("unchecked") // Guaranteed by runtime. + public BuilderType newBuilderForType() { + return (BuilderType) defaultInstance.toBuilder(); + } + } } diff --git a/java/src/main/java/com/google/protobuf/LazyFieldLite.java b/java/src/main/java/com/google/protobuf/LazyFieldLite.java index 1fc80e87..eea1fe3c 100644 --- a/java/src/main/java/com/google/protobuf/LazyFieldLite.java +++ b/java/src/main/java/com/google/protobuf/LazyFieldLite.java @@ -30,8 +30,6 @@ package com.google.protobuf; -import java.io.IOException; - /** * LazyFieldLite encapsulates the logic of lazily parsing message fields. It stores * the message in a ByteString initially and then parse it on-demand. @@ -44,40 +42,102 @@ import java.io.IOException; * @author xiangl@google.com (Xiang Li) */ public class LazyFieldLite { - private ByteString bytes; + private static final ExtensionRegistryLite EMPTY_REGISTRY = + ExtensionRegistryLite.getEmptyRegistry(); + + /** + * A delayed-parsed version of the bytes. When this is non-null then {@code extensionRegistry } is + * also non-null and {@code value} and {@code memoizedBytes} are null. + */ + private ByteString delayedBytes; + + /** + * An {@code ExtensionRegistryLite} for parsing bytes. It is non-null on a best-effort basis. It + * is only guaranteed to be non-null if this message was initialized using bytes and an + * {@code ExtensionRegistry}. If it directly had a value set then it will be null, unless it has + * been merged with another {@code LazyFieldLite} that had an {@code ExtensionRegistry}. + */ private ExtensionRegistryLite extensionRegistry; - private volatile boolean isDirty = false; + /** + * The parsed value. When this is non-null then {@code delayedBytes} will be null. + */ protected volatile MessageLite value; + /** + * The memoized bytes for {@code value}. Will be null when {@code value} is null. + */ + private volatile ByteString memoizedBytes; + + /** + * Constructs a LazyFieldLite with bytes that will be parsed lazily. + */ public LazyFieldLite(ExtensionRegistryLite extensionRegistry, ByteString bytes) { + checkArguments(extensionRegistry, bytes); this.extensionRegistry = extensionRegistry; - this.bytes = bytes; + this.delayedBytes = bytes; } + /** + * Constructs a LazyFieldLite with no contents, and no ability to parse extensions. + */ public LazyFieldLite() { } + /** + * Constructs a LazyFieldLite instance with a value. The LazyFieldLite may not be able to parse + * the extensions in the value as it has no ExtensionRegistry. + */ public static LazyFieldLite fromValue(MessageLite value) { LazyFieldLite lf = new LazyFieldLite(); lf.setValue(value); return lf; } + /** + * Determines whether this LazyFieldLite instance represents the default instance of this type. + */ public boolean containsDefaultInstance() { - return value == null && bytes == null; + return memoizedBytes == ByteString.EMPTY + || value == null && (delayedBytes == null || delayedBytes == ByteString.EMPTY); } + /** + * Clears the value state of this instance. + * + *

LazyField is not thread-safe for write access. Synchronizations are needed + * under read/write situations. + */ public void clear() { - bytes = null; + // Don't clear the ExtensionRegistry. It might prove useful later on when merging in another + // value, but there is no guarantee that it will contain all extensions that were directly set + // on the values that need to be merged. + delayedBytes = null; value = null; - extensionRegistry = null; - isDirty = true; + memoizedBytes = null; + } + + /** + * Overrides the contents of this LazyField. + * + *

LazyField is not thread-safe for write access. Synchronizations are needed + * under read/write situations. + */ + public void set(LazyFieldLite other) { + this.delayedBytes = other.delayedBytes; + this.value = other.value; + this.memoizedBytes = other.memoizedBytes; + // If the other LazyFieldLite was created by directly setting the value rather than first by + // parsing, then it will not have an extensionRegistry. In this case we hold on to the existing + // extensionRegistry, which has no guarantees that it has all the extensions that will be + // directly set on the value. + if (other.extensionRegistry != null) { + this.extensionRegistry = other.extensionRegistry; + } } /** - * Returns message instance. At first time, serialized data is parsed by - * {@code defaultInstance.getParserForType()}. + * Returns message instance. It may do some thread-safe delayed parsing of bytes. * * @param defaultInstance its message's default instance. It's also used to get parser for the * message type. @@ -88,38 +148,109 @@ public class LazyFieldLite { } /** - * LazyField is not thread-safe for write access. Synchronizations are needed + * Sets the value of the instance and returns the old value without delay parsing anything. + * + *

LazyField is not thread-safe for write access. Synchronizations are needed * under read/write situations. */ public MessageLite setValue(MessageLite value) { MessageLite originalValue = this.value; + this.delayedBytes = null; + this.memoizedBytes = null; this.value = value; - bytes = null; - isDirty = true; return originalValue; } - public void merge(LazyFieldLite value) { - if (value.containsDefaultInstance()) { + /** + * Merges another instance's contents. In some cases may drop some extensions if both fields + * contain data. If the other field has an {@code ExtensionRegistry} but this does not, then this + * field will copy over that {@code ExtensionRegistry}. + * + *

LazyField is not thread-safe for write access. Synchronizations are needed + * under read/write situations. + */ + public void merge(LazyFieldLite other) { + if (other.containsDefaultInstance()) { return; } - if (bytes == null) { - this.bytes = value.bytes; + if (this.containsDefaultInstance()) { + set(other); + return; + } + + // If the other field has an extension registry but this does not, copy over the other extension + // registry. + if (this.extensionRegistry == null) { + this.extensionRegistry = other.extensionRegistry; + } + + // In the case that both of them are not parsed we simply concatenate the bytes to save time. In + // the (probably rare) case that they have different extension registries there is a chance that + // some of the extensions may be dropped, but the tradeoff of making this operation fast seems + // to outway the benefits of combining the extension registries, which is not normally done for + // lite protos anyways. + if (this.delayedBytes != null && other.delayedBytes != null) { + this.delayedBytes = this.delayedBytes.concat(other.delayedBytes); + return; + } + + // At least one is parsed and both contain data. We won't drop any extensions here directly, but + // in the case that the extension registries are not the same then we might in the future if we + // need to serialze and parse a message again. + if (this.value == null && other.value != null) { + setValue(mergeValueAndBytes(other.value, this.delayedBytes, this.extensionRegistry)); + return; + } else if (this.value != null && other.value == null) { + setValue(mergeValueAndBytes(this.value, other.delayedBytes, other.extensionRegistry)); + return; + } + + // At this point we have two fully parsed messages. We can't merge directly from one to the + // other because only generated builder code contains methods to mergeFrom another parsed + // message. We have to serialize one instance and then merge the bytes into the other. This may + // drop extensions from one of the messages if one of the values had an extension set on it + // directly. + // + // To mitigate this we prefer serializing a message that has an extension registry, and + // therefore a chance that all extensions set on it are in that registry. + // + // NOTE: The check for other.extensionRegistry not being null must come first because at this + // point in time if other.extensionRegistry is not null then this.extensionRegistry will not be + // null either. + if (other.extensionRegistry != null) { + setValue(mergeValueAndBytes(this.value, other.toByteString(), other.extensionRegistry)); + return; + } else if (this.extensionRegistry != null) { + setValue(mergeValueAndBytes(other.value, this.toByteString(), this.extensionRegistry)); + return; } else { - this.bytes.concat(value.toByteString()); + // All extensions from the other message will be dropped because we have no registry. + setValue(mergeValueAndBytes(this.value, other.toByteString(), EMPTY_REGISTRY)); + return; } - isDirty = false; } - public void setByteString(ByteString bytes, ExtensionRegistryLite extensionRegistry) { - this.bytes = bytes; - this.extensionRegistry = extensionRegistry; - isDirty = false; + private static MessageLite mergeValueAndBytes( + MessageLite value, ByteString otherBytes, ExtensionRegistryLite extensionRegistry) { + try { + return value.toBuilder().mergeFrom(otherBytes, extensionRegistry).build(); + } catch (InvalidProtocolBufferException e) { + // Nothing is logged and no exceptions are thrown. Clients will be unaware that a proto + // was invalid. + return value; + } } - public ExtensionRegistryLite getExtensionRegistry() { - return extensionRegistry; + /** + * Sets this field with bytes to delay-parse. + */ + public void setByteString(ByteString bytes, ExtensionRegistryLite extensionRegistry) { + checkArguments(extensionRegistry, bytes); + this.delayedBytes = bytes; + this.extensionRegistry = extensionRegistry; + this.value = null; + this.memoizedBytes = null; } /** @@ -128,30 +259,43 @@ public class LazyFieldLite { * parsed. Be careful when using this method. */ public int getSerializedSize() { - if (isDirty) { + if (delayedBytes != null) { + return delayedBytes.size(); + } else if (memoizedBytes != null) { + return memoizedBytes.size(); + } else if (value != null) { return value.getSerializedSize(); + } else { + return 0; } - return bytes.size(); } + /** + * Returns a BytesString for this field in a thread-safe way. + */ public ByteString toByteString() { - if (!isDirty) { - return bytes; + if (delayedBytes != null) { + return delayedBytes; + } + if (memoizedBytes != null) { + return memoizedBytes; } synchronized (this) { - if (!isDirty) { - return bytes; + if (memoizedBytes != null) { + return memoizedBytes; } if (value == null) { - bytes = ByteString.EMPTY; + memoizedBytes = ByteString.EMPTY; } else { - bytes = value.toByteString(); + memoizedBytes = value.toByteString(); } - isDirty = false; - return bytes; + return memoizedBytes; } } + /** + * Might lazily parse the bytes that were previously passed in. Is thread-safe. + */ protected void ensureInitialized(MessageLite defaultInstance) { if (value != null) { return; @@ -161,16 +305,35 @@ public class LazyFieldLite { return; } try { - if (bytes != null) { - value = defaultInstance.getParserForType() - .parseFrom(bytes, extensionRegistry); + if (delayedBytes != null) { + // The extensionRegistry shouldn't be null here since we have delayedBytes. + MessageLite parsedValue = defaultInstance.getParserForType() + .parseFrom(delayedBytes, extensionRegistry); + this.value = parsedValue; + this.memoizedBytes = delayedBytes; + this.delayedBytes = null; } else { - value = defaultInstance; + this.value = defaultInstance; + this.memoizedBytes = ByteString.EMPTY; + this.delayedBytes = null; } - } catch (IOException e) { - // TODO(xiangl): Refactory the API to support the exception thrown from - // lazily load messages. + } catch (InvalidProtocolBufferException e) { + // Nothing is logged and no exceptions are thrown. Clients will be unaware that this proto + // was invalid. + this.value = defaultInstance; + this.memoizedBytes = ByteString.EMPTY; + this.delayedBytes = null; } } } + + + private static void checkArguments(ExtensionRegistryLite extensionRegistry, ByteString bytes) { + if (extensionRegistry == null) { + throw new NullPointerException("found null ExtensionRegistry"); + } + if (bytes == null) { + throw new NullPointerException("found null ByteString"); + } + } } diff --git a/java/src/main/java/com/google/protobuf/LiteralByteString.java b/java/src/main/java/com/google/protobuf/LiteralByteString.java index 767b9f35..6893ddf1 100644 --- a/java/src/main/java/com/google/protobuf/LiteralByteString.java +++ b/java/src/main/java/com/google/protobuf/LiteralByteString.java @@ -154,7 +154,11 @@ class LiteralByteString extends ByteString { @Override public String toString(String charsetName) throws UnsupportedEncodingException { - return new String(bytes, getOffsetIntoBytes(), size(), charsetName); + // Optimize for empty strings, but ensure we don't silently ignore invalid + // encodings. + return size() == 0 && UTF_8.equals(charsetName) + ? "" + : new String(bytes, getOffsetIntoBytes(), size(), charsetName); } // ================================================================= diff --git a/java/src/main/java/com/google/protobuf/Message.java b/java/src/main/java/com/google/protobuf/Message.java index 5c75b58b..fa0265e2 100644 --- a/java/src/main/java/com/google/protobuf/Message.java +++ b/java/src/main/java/com/google/protobuf/Message.java @@ -163,7 +163,7 @@ public interface Message extends MessageLite, MessageOrBuilder { * field builder, which will then be nested into its parent builder. *

* NOTE: implementations that do not support nested builders will throw - * UnsupportedException. + * UnsupportedOperationException. */ Builder getFieldBuilder(Descriptors.FieldDescriptor field); @@ -181,7 +181,7 @@ public interface Message extends MessageLite, MessageOrBuilder { * field builder, which will then be nested into its parent builder. *

* NOTE: implementations that do not support nested builders will throw - * UnsupportedException. + * UnsupportedOperationException. */ Builder getRepeatedFieldBuilder(Descriptors.FieldDescriptor field, int index); diff --git a/java/src/main/java/com/google/protobuf/MessageReflection.java b/java/src/main/java/com/google/protobuf/MessageReflection.java index 06e3c99b..de4bfd3e 100644 --- a/java/src/main/java/com/google/protobuf/MessageReflection.java +++ b/java/src/main/java/com/google/protobuf/MessageReflection.java @@ -45,13 +45,14 @@ import java.util.TreeMap; */ class MessageReflection { - static void writeMessageTo(Message message, CodedOutputStream output, + static void writeMessageTo( + Message message, + Map fields, + CodedOutputStream output, boolean alwaysWriteRequiredFields) throws IOException { final boolean isMessageSet = message.getDescriptorForType().getOptions().getMessageSetWireFormat(); - - Map fields = message.getAllFields(); if (alwaysWriteRequiredFields) { fields = new TreeMap(fields); for (final FieldDescriptor field : @@ -82,13 +83,15 @@ class MessageReflection { } } - static int getSerializedSize(Message message) { + static int getSerializedSize( + Message message, + Map fields) { int size = 0; final boolean isMessageSet = message.getDescriptorForType().getOptions().getMessageSetWireFormat(); for (final Map.Entry entry : - message.getAllFields().entrySet()) { + fields.entrySet()) { final Descriptors.FieldDescriptor field = entry.getKey(); final Object value = entry.getValue(); if (isMessageSet && field.isExtension() && @@ -340,14 +343,12 @@ class MessageReflection { ByteString bytes, ExtensionRegistryLite registry, Descriptors.FieldDescriptor descriptor, Message defaultInstance) throws IOException; - + /** - * Read a primitive field from input. Note that builders and mutable - * messages may use different Java types to represent a primtive field. + * Returns the UTF8 validation level for the field. */ - Object readPrimitiveField( - CodedInputStream input, WireFormat.FieldType type, - boolean checkUtf8) throws IOException; + WireFormat.Utf8Validation getUtf8Validation(Descriptors.FieldDescriptor + descriptor); /** * Returns a new merge target for a sub-field. When defaultInstance is @@ -513,11 +514,18 @@ class MessageReflection { return new BuilderAdapter(builder.newBuilderForField(field)); } } - - public Object readPrimitiveField( - CodedInputStream input, WireFormat.FieldType type, - boolean checkUtf8) throws IOException { - return FieldSet.readPrimitiveField(input, type, checkUtf8); + + public WireFormat.Utf8Validation + getUtf8Validation(Descriptors.FieldDescriptor descriptor) { + if (descriptor.needsUtf8Check()) { + return WireFormat.Utf8Validation.STRICT; + } + // TODO(liujisi): support lazy strings for repeated fields. + if (!descriptor.isRepeated() + && builder instanceof GeneratedMessage.Builder) { + return WireFormat.Utf8Validation.LAZY; + } + return WireFormat.Utf8Validation.LOOSE; } public Object finish() { @@ -651,11 +659,14 @@ class MessageReflection { throw new UnsupportedOperationException( "newMergeTargetForField() called on FieldSet object"); } - - public Object readPrimitiveField( - CodedInputStream input, WireFormat.FieldType type, - boolean checkUtf8) throws IOException { - return FieldSet.readPrimitiveField(input, type, checkUtf8); + + public WireFormat.Utf8Validation + getUtf8Validation(Descriptors.FieldDescriptor descriptor) { + if (descriptor.needsUtf8Check()) { + return WireFormat.Utf8Validation.STRICT; + } + // TODO(liujisi): support lazy strings for ExtesnsionSet. + return WireFormat.Utf8Validation.LOOSE; } public Object finish() { @@ -767,8 +778,8 @@ class MessageReflection { } } else { while (input.getBytesUntilLimit() > 0) { - final Object value = - target.readPrimitiveField(input, field.getLiteType(), field.needsUtf8Check()); + final Object value = WireFormat.readPrimitiveField( + input, field.getLiteType(), target.getUtf8Validation(field)); target.addRepeatedField(field, value); } } @@ -801,7 +812,8 @@ class MessageReflection { } break; default: - value = target.readPrimitiveField(input, field.getLiteType(), field.needsUtf8Check()); + value = WireFormat.readPrimitiveField( + input, field.getLiteType(), target.getUtf8Validation(field)); break; } diff --git a/java/src/main/java/com/google/protobuf/RopeByteString.java b/java/src/main/java/com/google/protobuf/RopeByteString.java index fa23c9dd..168bcce2 100644 --- a/java/src/main/java/com/google/protobuf/RopeByteString.java +++ b/java/src/main/java/com/google/protobuf/RopeByteString.java @@ -30,9 +30,9 @@ package com.google.protobuf; -import java.io.InvalidObjectException; import java.io.IOException; import java.io.InputStream; +import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; @@ -420,7 +420,11 @@ class RopeByteString extends ByteString { @Override public String toString(String charsetName) throws UnsupportedEncodingException { - return new String(toByteArray(), charsetName); + // Optimize for empty strings, but ensure we don't silently ignore invalid + // encodings. + return size() == 0 && UTF_8.equals(charsetName) + ? "" + : new String(toByteArray(), charsetName); } // ================================================================= diff --git a/java/src/main/java/com/google/protobuf/TextFormat.java b/java/src/main/java/com/google/protobuf/TextFormat.java index 4f6756ed..dd2b4600 100644 --- a/java/src/main/java/com/google/protobuf/TextFormat.java +++ b/java/src/main/java/com/google/protobuf/TextFormat.java @@ -409,9 +409,9 @@ public final class TextFormat { case STRING: generator.print("\""); - generator.print(escapeNonAscii ? - escapeText((String) value) : - escapeDoubleQuotesAndBackslashes((String) value) + generator.print(escapeNonAscii + ? escapeText((String) value) + : escapeDoubleQuotesAndBackslashes((String) value) .replace("\n", "\\n")); generator.print("\""); break; @@ -730,8 +730,8 @@ public final class TextFormat { } final char c = currentToken.charAt(0); - return ('0' <= c && c <= '9') || - c == '-' || c == '+'; + return ('0' <= c && c <= '9') + || c == '-' || c == '+'; } /** @@ -749,10 +749,10 @@ public final class TextFormat { public String consumeIdentifier() throws ParseException { for (int i = 0; i < currentToken.length(); i++) { final char c = currentToken.charAt(i); - if (('a' <= c && c <= 'z') || - ('A' <= c && c <= 'Z') || - ('0' <= c && c <= '9') || - (c == '_') || (c == '.')) { + if (('a' <= c && c <= 'z') + || ('A' <= c && c <= 'Z') + || ('0' <= c && c <= '9') + || (c == '_') || (c == '.')) { // OK } else { throw parseException( @@ -941,14 +941,14 @@ public final class TextFormat { * Otherwise, throw a {@link ParseException}. */ public boolean consumeBoolean() throws ParseException { - if (currentToken.equals("true") || - currentToken.equals("t") || - currentToken.equals("1")) { + if (currentToken.equals("true") + || currentToken.equals("t") + || currentToken.equals("1")) { nextToken(); return true; - } else if (currentToken.equals("false") || - currentToken.equals("f") || - currentToken.equals("0")) { + } else if (currentToken.equals("false") + || currentToken.equals("f") + || currentToken.equals("0")) { nextToken(); return false; } else { @@ -999,14 +999,15 @@ public final class TextFormat { */ private void consumeByteString(List list) throws ParseException { - final char quote = currentToken.length() > 0 ? currentToken.charAt(0) - : '\0'; + final char quote = currentToken.length() > 0 + ? currentToken.charAt(0) + : '\0'; if (quote != '\"' && quote != '\'') { throw parseException("Expected string."); } - if (currentToken.length() < 2 || - currentToken.charAt(currentToken.length() - 1) != quote) { + if (currentToken.length() < 2 + || currentToken.charAt(currentToken.length() - 1) != quote) { throw parseException("String missing ending quote."); } @@ -1340,8 +1341,8 @@ public final class TextFormat { } else { if (extension.descriptor.getContainingType() != type) { throw tokenizer.parseExceptionPreviousToken( - "Extension \"" + name + "\" does not extend message type \"" + - type.getFullName() + "\"."); + "Extension \"" + name + "\" does not extend message type \"" + + type.getFullName() + "\"."); } field = extension.descriptor; } @@ -1365,20 +1366,20 @@ public final class TextFormat { } } // Again, special-case group names as described above. - if (field != null && field.getType() == FieldDescriptor.Type.GROUP && - !field.getMessageType().getName().equals(name)) { + if (field != null && field.getType() == FieldDescriptor.Type.GROUP + && !field.getMessageType().getName().equals(name)) { field = null; } if (field == null) { if (!allowUnknownFields) { throw tokenizer.parseExceptionPreviousToken( - "Message type \"" + type.getFullName() + - "\" has no field named \"" + name + "\"."); + "Message type \"" + type.getFullName() + + "\" has no field named \"" + name + "\"."); } else { logger.warning( - "Message type \"" + type.getFullName() + - "\" has no field named \"" + name + "\"."); + "Message type \"" + type.getFullName() + + "\" has no field named \"" + name + "\"."); } } } @@ -1391,8 +1392,9 @@ public final class TextFormat { // start with "{" or "<" which indicates the beginning of a message body. // If there is no ":" or there is a "{" or "<" after ":", this field has // to be a message or the input is ill-formed. - if (tokenizer.tryConsume(":") && !tokenizer.lookingAt("{") && - !tokenizer.lookingAt("<")) { + if (tokenizer.tryConsume(":") + && !tokenizer.lookingAt("{") + && !tokenizer.lookingAt("<")) { skipFieldValue(tokenizer); } else { skipFieldMessage(tokenizer); @@ -1516,16 +1518,16 @@ public final class TextFormat { value = enumType.findValueByNumber(number); if (value == null) { throw tokenizer.parseExceptionPreviousToken( - "Enum type \"" + enumType.getFullName() + - "\" has no value with number " + number + '.'); + "Enum type \"" + enumType.getFullName() + + "\" has no value with number " + number + '.'); } } else { final String id = tokenizer.consumeIdentifier(); value = enumType.findValueByName(id); if (value == null) { throw tokenizer.parseExceptionPreviousToken( - "Enum type \"" + enumType.getFullName() + - "\" has no value named \"" + id + "\"."); + "Enum type \"" + enumType.getFullName() + + "\" has no value named \"" + id + "\"."); } } @@ -1578,8 +1580,9 @@ public final class TextFormat { // start with "{" or "<" which indicates the beginning of a message body. // If there is no ":" or there is a "{" or "<" after ":", this field has // to be a message or the input is ill-formed. - if (tokenizer.tryConsume(":") && !tokenizer.lookingAt("<") && - !tokenizer.lookingAt("{")) { + if (tokenizer.tryConsume(":") + && !tokenizer.lookingAt("<") + && !tokenizer.lookingAt("{")) { skipFieldValue(tokenizer); } else { skipFieldMessage(tokenizer); @@ -1617,11 +1620,11 @@ public final class TextFormat { while (tokenizer.tryConsumeString()) {} return; } - if (!tokenizer.tryConsumeIdentifier() && // includes enum & boolean - !tokenizer.tryConsumeInt64() && // includes int32 - !tokenizer.tryConsumeUInt64() && // includes uint32 - !tokenizer.tryConsumeDouble() && - !tokenizer.tryConsumeFloat()) { + if (!tokenizer.tryConsumeIdentifier() // includes enum & boolean + && !tokenizer.tryConsumeInt64() // includes int32 + && !tokenizer.tryConsumeUInt64() // includes uint32 + && !tokenizer.tryConsumeDouble() + && !tokenizer.tryConsumeFloat()) { throw tokenizer.parseException( "Invalid field value: " + tokenizer.currentToken); } @@ -1647,19 +1650,19 @@ public final class TextFormat { * which no defined short-hand escape sequence is defined will be escaped * using 3-digit octal sequences. */ - private static String escapeBytes(final ByteSequence input) { + public static String escapeBytes(final ByteSequence input) { final StringBuilder builder = new StringBuilder(input.size()); for (int i = 0; i < input.size(); i++) { final byte b = input.byteAt(i); switch (b) { // Java does not recognize \a or \v, apparently. - case 0x07: builder.append("\\a" ); break; - case '\b': builder.append("\\b" ); break; - case '\f': builder.append("\\f" ); break; - case '\n': builder.append("\\n" ); break; - case '\r': builder.append("\\r" ); break; - case '\t': builder.append("\\t" ); break; - case 0x0b: builder.append("\\v" ); break; + case 0x07: builder.append("\\a"); break; + case '\b': builder.append("\\b"); break; + case '\f': builder.append("\\f"); break; + case '\n': builder.append("\\n"); break; + case '\r': builder.append("\\r"); break; + case '\t': builder.append("\\t"); break; + case 0x0b: builder.append("\\v"); break; case '\\': builder.append("\\\\"); break; case '\'': builder.append("\\\'"); break; case '"' : builder.append("\\\""); break; @@ -1688,11 +1691,13 @@ public final class TextFormat { * which no defined short-hand escape sequence is defined will be escaped * using 3-digit octal sequences. */ - static String escapeBytes(final ByteString input) { + public static String escapeBytes(final ByteString input) { return escapeBytes(new ByteSequence() { + @Override public int size() { return input.size(); } + @Override public byte byteAt(int offset) { return input.byteAt(offset); } @@ -1702,11 +1707,13 @@ public final class TextFormat { /** * Like {@link #escapeBytes(ByteString)}, but used for byte array. */ - static String escapeBytes(final byte[] input) { + public static String escapeBytes(final byte[] input) { return escapeBytes(new ByteSequence() { + @Override public int size() { return input.length; } + @Override public byte byteAt(int offset) { return input[offset]; } @@ -1749,7 +1756,7 @@ public final class TextFormat { code = code * 8 + digitValue(input.byteAt(i)); } // TODO: Check that 0 <= code && code <= 0xFF. - result[pos++] = (byte)code; + result[pos++] = (byte) code; } else { switch (c) { case 'a' : result[pos++] = 0x07; break; @@ -1777,12 +1784,12 @@ public final class TextFormat { ++i; code = code * 16 + digitValue(input.byteAt(i)); } - result[pos++] = (byte)code; + result[pos++] = (byte) code; break; default: throw new InvalidEscapeSequenceException( - "Invalid escape sequence: '\\" + (char)c + '\''); + "Invalid escape sequence: '\\" + (char) c + '\''); } } } else { @@ -1841,9 +1848,9 @@ public final class TextFormat { /** Is this a hex digit? */ private static boolean isHex(final byte c) { - return ('0' <= c && c <= '9') || - ('a' <= c && c <= 'f') || - ('A' <= c && c <= 'F'); + return ('0' <= c && c <= '9') + || ('a' <= c && c <= 'f') + || ('A' <= c && c <= 'F'); } /** diff --git a/java/src/main/java/com/google/protobuf/WireFormat.java b/java/src/main/java/com/google/protobuf/WireFormat.java index eba2528e..ba83b666 100644 --- a/java/src/main/java/com/google/protobuf/WireFormat.java +++ b/java/src/main/java/com/google/protobuf/WireFormat.java @@ -30,6 +30,8 @@ package com.google.protobuf; +import java.io.IOException; + /** * This class is used internally by the Protocol Buffer library and generated * message implementations. It is public only because those generated messages @@ -160,4 +162,84 @@ public final class WireFormat { makeTag(MESSAGE_SET_TYPE_ID, WIRETYPE_VARINT); static final int MESSAGE_SET_MESSAGE_TAG = makeTag(MESSAGE_SET_MESSAGE, WIRETYPE_LENGTH_DELIMITED); + + /** + * Validation level for handling incoming string field data which potentially + * contain non-UTF8 bytes. + */ + enum Utf8Validation { + /** Eagerly parses to String; silently accepts invalid UTF8 bytes. */ + LOOSE { + Object readString(CodedInputStream input) throws IOException { + return input.readString(); + } + }, + /** Eagerly parses to String; throws an IOException on invalid bytes. */ + STRICT { + Object readString(CodedInputStream input) throws IOException { + return input.readStringRequireUtf8(); + } + }, + /** Keep data as ByteString; validation/conversion to String is lazy. */ + LAZY { + Object readString(CodedInputStream input) throws IOException { + return input.readBytes(); + } + }; + + /** Read a string field from the input with the proper UTF8 validation. */ + abstract Object readString(CodedInputStream input) throws IOException; + } + + /** + * Read a field of any primitive type for immutable messages from a + * CodedInputStream. Enums, groups, and embedded messages are not handled by + * this method. + * + * @param input The stream from which to read. + * @param type Declared type of the field. + * @param utf8Validation Different string UTF8 validation level for handling + * string fields. + * @return An object representing the field's value, of the exact + * type which would be returned by + * {@link Message#getField(Descriptors.FieldDescriptor)} for + * this field. + */ + static Object readPrimitiveField( + CodedInputStream input, + FieldType type, + Utf8Validation utf8Validation) throws IOException { + switch (type) { + case DOUBLE : return input.readDouble (); + case FLOAT : return input.readFloat (); + case INT64 : return input.readInt64 (); + case UINT64 : return input.readUInt64 (); + case INT32 : return input.readInt32 (); + case FIXED64 : return input.readFixed64 (); + case FIXED32 : return input.readFixed32 (); + case BOOL : return input.readBool (); + case BYTES : return input.readBytes (); + case UINT32 : return input.readUInt32 (); + case SFIXED32: return input.readSFixed32(); + case SFIXED64: return input.readSFixed64(); + case SINT32 : return input.readSInt32 (); + case SINT64 : return input.readSInt64 (); + + case STRING : return utf8Validation.readString(input); + case GROUP: + throw new IllegalArgumentException( + "readPrimitiveField() cannot handle nested groups."); + case MESSAGE: + throw new IllegalArgumentException( + "readPrimitiveField() cannot handle embedded messages."); + case ENUM: + // We don't handle enums because we don't know what to do if the + // value is not recognized. + throw new IllegalArgumentException( + "readPrimitiveField() cannot handle enums."); + } + + throw new RuntimeException( + "There is no way to get here, but the compiler thinks otherwise."); + } } diff --git a/java/src/test/java/com/google/protobuf/GeneratedMessageTest.java b/java/src/test/java/com/google/protobuf/GeneratedMessageTest.java index 2d101ba7..2bd8d1a9 100644 --- a/java/src/test/java/com/google/protobuf/GeneratedMessageTest.java +++ b/java/src/test/java/com/google/protobuf/GeneratedMessageTest.java @@ -58,8 +58,8 @@ import protobuf_unittest.UnittestProto.ForeignMessage; import protobuf_unittest.UnittestProto.ForeignMessageOrBuilder; import protobuf_unittest.UnittestProto.NestedTestAllTypes; import protobuf_unittest.UnittestProto.TestAllExtensions; -import protobuf_unittest.UnittestProto.TestAllTypes.NestedMessage; import protobuf_unittest.UnittestProto.TestAllTypes; +import protobuf_unittest.UnittestProto.TestAllTypes.NestedMessage; import protobuf_unittest.UnittestProto.TestAllTypesOrBuilder; import protobuf_unittest.UnittestProto.TestExtremeDefaultValues; import protobuf_unittest.UnittestProto.TestOneof2; diff --git a/java/src/test/java/com/google/protobuf/LazyFieldLiteTest.java b/java/src/test/java/com/google/protobuf/LazyFieldLiteTest.java index e67c6d27..211b5697 100644 --- a/java/src/test/java/com/google/protobuf/LazyFieldLiteTest.java +++ b/java/src/test/java/com/google/protobuf/LazyFieldLiteTest.java @@ -30,6 +30,9 @@ package com.google.protobuf; +import static protobuf_unittest.UnittestProto.optionalInt32Extension; +import static protobuf_unittest.UnittestProto.optionalInt64Extension; + import protobuf_unittest.UnittestProto.TestAllExtensions; import protobuf_unittest.UnittestProto.TestAllTypes; @@ -111,12 +114,146 @@ public class LazyFieldLiteTest extends TestCase { assertNotEqual(message.toByteString(), lazyField.toByteString()); } + public void testMergeExtensions() throws Exception { + TestAllExtensions message = TestUtil.getAllExtensionsSet(); + LazyFieldLite original = createLazyFieldLiteFromMessage(message); + LazyFieldLite merged = new LazyFieldLite(); + merged.merge(original); + TestAllExtensions value = (TestAllExtensions) merged.getValue( + TestAllExtensions.getDefaultInstance()); + assertEquals(message, value); + } + + public void testEmptyLazyField() throws Exception { + LazyFieldLite field = new LazyFieldLite(); + assertEquals(0, field.getSerializedSize()); + assertEquals(ByteString.EMPTY, field.toByteString()); + } + + public void testInvalidProto() throws Exception { + // Silently fails and uses the default instance. + LazyFieldLite field = new LazyFieldLite( + TestUtil.getExtensionRegistry(), ByteString.copyFromUtf8("invalid")); + assertEquals( + TestAllTypes.getDefaultInstance(), field.getValue(TestAllTypes.getDefaultInstance())); + assertEquals(0, field.getSerializedSize()); + assertEquals(ByteString.EMPTY, field.toByteString()); + } + + public void testMergeBeforeParsing() throws Exception { + TestAllTypes message1 = TestAllTypes.newBuilder().setOptionalInt32(1).build(); + LazyFieldLite field1 = createLazyFieldLiteFromMessage(message1); + TestAllTypes message2 = TestAllTypes.newBuilder().setOptionalInt64(2).build(); + LazyFieldLite field2 = createLazyFieldLiteFromMessage(message2); + + field1.merge(field2); + TestAllTypes expected = + TestAllTypes.newBuilder().setOptionalInt32(1).setOptionalInt64(2).build(); + assertEquals(expected, field1.getValue(TestAllTypes.getDefaultInstance())); + } + + public void testMergeOneNotParsed() throws Exception { + // Test a few different paths that involve one message that was not parsed. + TestAllTypes message1 = TestAllTypes.newBuilder().setOptionalInt32(1).build(); + TestAllTypes message2 = TestAllTypes.newBuilder().setOptionalInt64(2).build(); + TestAllTypes expected = + TestAllTypes.newBuilder().setOptionalInt32(1).setOptionalInt64(2).build(); + + LazyFieldLite field1 = LazyFieldLite.fromValue(message1); + field1.getValue(TestAllTypes.getDefaultInstance()); // Force parsing. + LazyFieldLite field2 = createLazyFieldLiteFromMessage(message2); + field1.merge(field2); + assertEquals(expected, field1.getValue(TestAllTypes.getDefaultInstance())); + + // Now reverse which one is parsed first. + field1 = LazyFieldLite.fromValue(message1); + field2 = createLazyFieldLiteFromMessage(message2); + field2.getValue(TestAllTypes.getDefaultInstance()); // Force parsing. + field1.merge(field2); + assertEquals(expected, field1.getValue(TestAllTypes.getDefaultInstance())); + } + + public void testMergeInvalid() throws Exception { + // Test a few different paths that involve one message that was not parsed. + TestAllTypes message = TestAllTypes.newBuilder().setOptionalInt32(1).build(); + LazyFieldLite valid = LazyFieldLite.fromValue(message); + LazyFieldLite invalid = new LazyFieldLite( + TestUtil.getExtensionRegistry(), ByteString.copyFromUtf8("invalid")); + invalid.merge(valid); + + // We swallow the exception and just use the set field. + assertEquals(message, invalid.getValue(TestAllTypes.getDefaultInstance())); + } + + public void testMergeKeepsExtensionsWhenPossible() throws Exception { + // In this test we attempt to only use the empty registry, which will strip out all extensions + // when serializing and then parsing. We verify that each code path will attempt to not + // serialize and parse a message that was set directly without going through the + // extensionRegistry. + TestAllExtensions messageWithExtensions = + TestAllExtensions.newBuilder().setExtension(optionalInt32Extension, 42).build(); + TestAllExtensions emptyMessage = TestAllExtensions.newBuilder().build(); + + ExtensionRegistryLite emptyRegistry = ExtensionRegistryLite.getEmptyRegistry(); + + LazyFieldLite field = LazyFieldLite.fromValue(messageWithExtensions); + field.merge(createLazyFieldLiteFromMessage(emptyRegistry, emptyMessage)); + assertEquals(messageWithExtensions, field.getValue(TestAllExtensions.getDefaultInstance())); + + // Now reverse the order of the merging. + field = createLazyFieldLiteFromMessage(emptyRegistry, emptyMessage); + field.merge(LazyFieldLite.fromValue(messageWithExtensions)); + assertEquals(messageWithExtensions, field.getValue(TestAllExtensions.getDefaultInstance())); + + // Now try parsing the empty field first. + field = LazyFieldLite.fromValue(messageWithExtensions); + LazyFieldLite other = createLazyFieldLiteFromMessage(emptyRegistry, emptyMessage); + other.getValue(TestAllExtensions.getDefaultInstance()); // Force parsing. + field.merge(other); + assertEquals(messageWithExtensions, field.getValue(TestAllExtensions.getDefaultInstance())); + + // And again reverse. + field = createLazyFieldLiteFromMessage(emptyRegistry, emptyMessage); + field.getValue(TestAllExtensions.getDefaultInstance()); // Force parsing. + other = LazyFieldLite.fromValue(messageWithExtensions); + field.merge(other); + assertEquals(messageWithExtensions, field.getValue(TestAllExtensions.getDefaultInstance())); + } + + public void testMergeMightLoseExtensions() throws Exception { + // Test that we don't know about the extensions when parsing. + TestAllExtensions message1 = + TestAllExtensions.newBuilder().setExtension(optionalInt32Extension, 1).build(); + TestAllExtensions message2 = + TestAllExtensions.newBuilder().setExtension(optionalInt64Extension, 2L).build(); + + LazyFieldLite field = LazyFieldLite.fromValue(message1); + field.merge(LazyFieldLite.fromValue(message2)); + + // We lose the extensions from message 2 because we have to serialize it and then parse it + // again, using the empty registry this time. + TestAllExtensions value = + (TestAllExtensions) field.getValue(TestAllExtensions.getDefaultInstance()); + assertTrue(value.hasExtension(optionalInt32Extension)); + assertEquals(Integer.valueOf(1), value.getExtension(optionalInt32Extension)); + assertFalse(value.hasExtension(optionalInt64Extension)); + + // The field is still there, it is just unknown. + assertTrue(value.getUnknownFields() + .hasField(optionalInt64Extension.getDescriptor().getNumber())); + } + // Help methods. private LazyFieldLite createLazyFieldLiteFromMessage(MessageLite message) { + return createLazyFieldLiteFromMessage(TestUtil.getExtensionRegistry(), message); + } + + private LazyFieldLite createLazyFieldLiteFromMessage( + ExtensionRegistryLite extensionRegistry, MessageLite message) { ByteString bytes = message.toByteString(); - return new LazyFieldLite(TestUtil.getExtensionRegistry(), bytes); + return new LazyFieldLite(extensionRegistry, bytes); } private void changeValue(LazyFieldLite lazyField) { diff --git a/java/src/test/java/com/google/protobuf/LazyMessageLiteTest.java b/java/src/test/java/com/google/protobuf/LazyMessageLiteTest.java index 9de794fe..afe0fffd 100644 --- a/java/src/test/java/com/google/protobuf/LazyMessageLiteTest.java +++ b/java/src/test/java/com/google/protobuf/LazyMessageLiteTest.java @@ -30,6 +30,7 @@ package com.google.protobuf; +import protobuf_unittest.LazyFieldsLite.LazyExtension; import protobuf_unittest.LazyFieldsLite.LazyInnerMessageLite; import protobuf_unittest.LazyFieldsLite.LazyMessageLite; import protobuf_unittest.LazyFieldsLite.LazyNestedInnerMessageLite; @@ -285,4 +286,22 @@ public class LazyMessageLiteTest extends TestCase { assertEquals(bytes, deserialized.toByteString()); } + + public void testExtensions() throws Exception { + LazyInnerMessageLite.Builder innerBuilder = LazyInnerMessageLite.newBuilder(); + innerBuilder.setExtension( + LazyExtension.extension, LazyExtension.newBuilder() + .setName("name").build()); + assertTrue(innerBuilder.hasExtension(LazyExtension.extension)); + assertEquals("name", innerBuilder.getExtension(LazyExtension.extension).getName()); + + LazyInnerMessageLite innerMessage = innerBuilder.build(); + assertTrue(innerMessage.hasExtension(LazyExtension.extension)); + assertEquals("name", innerMessage.getExtension(LazyExtension.extension).getName()); + + LazyMessageLite lite = LazyMessageLite.newBuilder() + .setInner(innerMessage).build(); + assertTrue(lite.getInner().hasExtension(LazyExtension.extension)); + assertEquals("name", lite.getInner().getExtension(LazyExtension.extension).getName()); + } } diff --git a/java/src/test/java/com/google/protobuf/LiteralByteStringTest.java b/java/src/test/java/com/google/protobuf/LiteralByteStringTest.java index ff39ca3f..046832de 100644 --- a/java/src/test/java/com/google/protobuf/LiteralByteStringTest.java +++ b/java/src/test/java/com/google/protobuf/LiteralByteStringTest.java @@ -289,7 +289,6 @@ public class LiteralByteStringTest extends TestCase { assertEquals("Output.reset() resets the output", 0, output.size()); assertEquals("Output.reset() resets the output", ByteString.EMPTY, output.toByteString()); - } public void testToString() throws UnsupportedEncodingException { @@ -299,6 +298,27 @@ public class LiteralByteStringTest extends TestCase { assertEquals(classUnderTest + " unicode must match", testString, roundTripString); } + public void testToString_returnsCanonicalEmptyString() throws UnsupportedEncodingException{ + assertSame(classUnderTest + " must be the same string references", + ByteString.EMPTY.toString(UTF_8), new LiteralByteString(new byte[]{}).toString(UTF_8)); + } + + public void testToString_raisesException() throws UnsupportedEncodingException{ + try { + ByteString.EMPTY.toString("invalid"); + fail("Should have thrown an exception."); + } catch (UnsupportedEncodingException expected) { + // This is success + } + + try { + new LiteralByteString(referenceBytes).toString("invalid"); + fail("Should have thrown an exception."); + } catch (UnsupportedEncodingException expected) { + // This is success + } + } + public void testEquals() { assertEquals(classUnderTest + " must not equal null", false, stringUnderTest.equals(null)); assertEquals(classUnderTest + " must equal self", stringUnderTest, stringUnderTest); @@ -311,7 +331,7 @@ public class LiteralByteStringTest extends TestCase { byte[] mungedBytes = new byte[referenceBytes.length]; System.arraycopy(referenceBytes, 0, mungedBytes, 0, referenceBytes.length); - mungedBytes[mungedBytes.length - 5] ^= 0xFF; + mungedBytes[mungedBytes.length - 5] = (byte) (mungedBytes[mungedBytes.length - 5] ^ 0xFF); assertFalse(classUnderTest + " must not equal every string with the same length", stringUnderTest.equals(new LiteralByteString(mungedBytes))); } diff --git a/java/src/test/java/com/google/protobuf/MapForProto2Test.java b/java/src/test/java/com/google/protobuf/MapForProto2Test.java index 33ba7150..78cba1b4 100644 --- a/java/src/test/java/com/google/protobuf/MapForProto2Test.java +++ b/java/src/test/java/com/google/protobuf/MapForProto2Test.java @@ -34,6 +34,7 @@ import com.google.protobuf.Descriptors.FieldDescriptor; import map_test.MapForProto2TestProto.TestMap; import map_test.MapForProto2TestProto.TestMap.MessageValue; import map_test.MapForProto2TestProto.TestMap.MessageWithRequiredFields; +import map_test.MapForProto2TestProto.TestRecursiveMap; import map_test.MapForProto2TestProto.TestUnknownEnumValue; import junit.framework.TestCase; @@ -499,4 +500,17 @@ public class MapForProto2Test extends TestCase { message = builder.build(); assertTrue(message.isInitialized()); } + + public void testRecursiveMap() throws Exception { + TestRecursiveMap.Builder builder = TestRecursiveMap.newBuilder(); + builder.getMutableRecursiveMapField().put( + 1, TestRecursiveMap.newBuilder().setValue(2).build()); + builder.getMutableRecursiveMapField().put( + 3, TestRecursiveMap.newBuilder().setValue(4).build()); + ByteString data = builder.build().toByteString(); + + TestRecursiveMap message = TestRecursiveMap.parseFrom(data); + assertEquals(2, message.getRecursiveMapField().get(1).getValue()); + assertEquals(4, message.getRecursiveMapField().get(3).getValue()); + } } diff --git a/java/src/test/java/com/google/protobuf/MapTest.java b/java/src/test/java/com/google/protobuf/MapTest.java index 6a1e9078..b8e67b7c 100644 --- a/java/src/test/java/com/google/protobuf/MapTest.java +++ b/java/src/test/java/com/google/protobuf/MapTest.java @@ -269,7 +269,6 @@ public class MapTest extends TestCase { assertFalse(m2.equals(m1)); } - public void testNestedBuilderOnChangeEventPropagation() { TestOnChangeEventPropagation.Builder parent = TestOnChangeEventPropagation.newBuilder(); diff --git a/java/src/test/java/com/google/protobuf/RopeByteStringTest.java b/java/src/test/java/com/google/protobuf/RopeByteStringTest.java index b3970196..0f2344d6 100644 --- a/java/src/test/java/com/google/protobuf/RopeByteStringTest.java +++ b/java/src/test/java/com/google/protobuf/RopeByteStringTest.java @@ -118,6 +118,34 @@ public class RopeByteStringTest extends LiteralByteStringTest { flatString.hashCode(), unicode.hashCode()); } + @Override + public void testToString_returnsCanonicalEmptyString() throws UnsupportedEncodingException { + RopeByteString ropeByteString = + RopeByteString.newInstanceForTest(ByteString.EMPTY, ByteString.EMPTY); + assertSame(classUnderTest + " must be the same string references", + ByteString.EMPTY.toString(UTF_8), ropeByteString.toString(UTF_8)); + } + + public void testToString_raisesException() throws UnsupportedEncodingException{ + try { + ByteString byteString = + RopeByteString.newInstanceForTest(ByteString.EMPTY, ByteString.EMPTY); + byteString.toString("invalid"); + fail("Should have thrown an exception."); + } catch (UnsupportedEncodingException expected) { + // This is success + } + + try { + ByteString byteString = RopeByteString.concatenate(ByteString.copyFromUtf8("foo"), + ByteString.copyFromUtf8("bar")); + byteString.toString("invalid"); + fail("Should have thrown an exception."); + } catch (UnsupportedEncodingException expected) { + // This is success + } + } + public void testJavaSerialization() throws Exception { ByteArrayOutputStream out = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(out); diff --git a/java/src/test/java/com/google/protobuf/lazy_fields_lite.proto b/java/src/test/java/com/google/protobuf/lazy_fields_lite.proto index 015dc267..5580f72d 100644 --- a/java/src/test/java/com/google/protobuf/lazy_fields_lite.proto +++ b/java/src/test/java/com/google/protobuf/lazy_fields_lite.proto @@ -54,6 +54,15 @@ message LazyInnerMessageLite { optional int32 num = 1; optional int32 num_with_default = 2 [default = 42]; optional LazyNestedInnerMessageLite nested = 3 [lazy = true]; + + extensions 1000 to max; +} + +message LazyExtension { + extend LazyInnerMessageLite { + optional LazyExtension extension = 1000; + } + optional string name = 1; } message LazyNestedInnerMessageLite { diff --git a/java/src/test/java/com/google/protobuf/map_for_proto2_lite_test.proto b/java/src/test/java/com/google/protobuf/map_for_proto2_lite_test.proto index a1fe856c..d5418f28 100644 --- a/java/src/test/java/com/google/protobuf/map_for_proto2_lite_test.proto +++ b/java/src/test/java/com/google/protobuf/map_for_proto2_lite_test.proto @@ -63,6 +63,13 @@ message TestUnknownEnumValue { // parsing behavior of TestMap regarding unknown enum values. map int32_to_int32_field = 4; } + +// Test that the maps initialization code works correctly when the map field +// references the containing message. +message TestRecursiveMap { + optional int32 value = 1; + map recursive_map_field = 2; +} package map_for_proto2_lite_test; option java_package = "map_lite_test"; option optimize_for = LITE_RUNTIME; diff --git a/java/src/test/java/com/google/protobuf/map_for_proto2_test.proto b/java/src/test/java/com/google/protobuf/map_for_proto2_test.proto index a0ec7ac5..a9be5166 100644 --- a/java/src/test/java/com/google/protobuf/map_for_proto2_test.proto +++ b/java/src/test/java/com/google/protobuf/map_for_proto2_test.proto @@ -65,3 +65,10 @@ message TestUnknownEnumValue { // parsing behavior of TestMap regarding unknown enum values. map int32_to_int32_field = 4; } + +// Test that the maps initialization code works correctly when the map field +// references the containing message. +message TestRecursiveMap { + optional int32 value = 1; + map recursive_map_field = 2; +} diff --git a/java/src/test/java/com/google/protobuf/map_test.proto b/java/src/test/java/com/google/protobuf/map_test.proto index 105ee3f8..bf692c22 100644 --- a/java/src/test/java/com/google/protobuf/map_test.proto +++ b/java/src/test/java/com/google/protobuf/map_test.proto @@ -36,6 +36,7 @@ option java_package = "map_test"; option java_outer_classname = "MapTestProto"; option java_generate_equals_and_hash = true; + message TestMap { message MessageValue { optional int32 value = 1; diff --git a/java/src/test/java/com/google/protobuf/test_bad_identifiers.proto b/java/src/test/java/com/google/protobuf/test_bad_identifiers.proto index 67035fd5..dc082615 100644 --- a/java/src/test/java/com/google/protobuf/test_bad_identifiers.proto +++ b/java/src/test/java/com/google/protobuf/test_bad_identifiers.proto @@ -45,6 +45,7 @@ option java_package = "com.google.protobuf"; option java_outer_classname = "TestBadIdentifiersProto"; option java_generate_equals_and_hash = true; + message TestMessage { optional string cached_size = 1; optional string serialized_size = 2; -- cgit v1.2.3