From 4de8f55113007fdc8e34107950e605fc0209d465 Mon Sep 17 00:00:00 2001 From: "jieluo@google.com" Date: Fri, 18 Jul 2014 00:47:59 +0000 Subject: down integrate to svn --- .../java/com/google/protobuf/AbstractMessage.java | 730 +++------------- .../com/google/protobuf/AbstractMessageLite.java | 24 +- .../java/com/google/protobuf/AbstractParser.java | 8 - .../main/java/com/google/protobuf/ByteString.java | 52 ++ .../java/com/google/protobuf/CodedInputStream.java | 671 +++++++++++---- .../com/google/protobuf/CodedOutputStream.java | 230 ++++- .../main/java/com/google/protobuf/Descriptors.java | 364 ++++++-- .../java/com/google/protobuf/DynamicMessage.java | 118 ++- .../main/java/com/google/protobuf/Extension.java | 96 +++ .../com/google/protobuf/ExtensionRegistry.java | 186 +++- .../main/java/com/google/protobuf/FieldSet.java | 100 ++- .../java/com/google/protobuf/GeneratedMessage.java | 388 +++++++-- .../com/google/protobuf/GeneratedMessageLite.java | 228 ++++- .../main/java/com/google/protobuf/Internal.java | 238 ++++++ .../protobuf/InvalidProtocolBufferException.java | 8 + .../main/java/com/google/protobuf/LazyField.java | 106 +-- .../com/google/protobuf/LazyStringArrayList.java | 215 ++++- .../java/com/google/protobuf/LazyStringList.java | 110 ++- .../com/google/protobuf/LiteralByteString.java | 25 +- .../src/main/java/com/google/protobuf/Message.java | 7 + .../main/java/com/google/protobuf/MessageLite.java | 1 + .../java/com/google/protobuf/MessageOrBuilder.java | 16 +- .../com/google/protobuf/MessageReflection.java | 931 +++++++++++++++++++++ java/src/main/java/com/google/protobuf/Parser.java | 2 + .../com/google/protobuf/ProtocolStringList.java | 48 ++ .../java/com/google/protobuf/RopeByteString.java | 14 + .../src/main/java/com/google/protobuf/RpcUtil.java | 5 +- .../main/java/com/google/protobuf/TextFormat.java | 792 ++++++++++++++---- .../java/com/google/protobuf/UnknownFieldSet.java | 17 + .../protobuf/UnmodifiableLazyStringList.java | 57 +- .../com/google/protobuf/AbstractMessageTest.java | 18 + .../java/com/google/protobuf/ByteStringTest.java | 67 ++ .../java/com/google/protobuf/CheckUtf8Test.java | 141 ++++ .../com/google/protobuf/CodedInputStreamTest.java | 279 +++++- .../com/google/protobuf/CodedOutputStreamTest.java | 84 ++ .../java/com/google/protobuf/DescriptorsTest.java | 66 +- .../com/google/protobuf/DynamicMessageTest.java | 46 + .../com/google/protobuf/GeneratedMessageTest.java | 371 +++++++- .../com/google/protobuf/LazyFieldLiteTest.java | 134 +++ .../java/com/google/protobuf/LazyFieldTest.java | 121 +++ .../com/google/protobuf/LazyMessageLiteTest.java | 319 +++++++ .../google/protobuf/LazyStringArrayListTest.java | 12 + .../com/google/protobuf/LiteEqualsAndHashTest.java | 85 ++ .../test/java/com/google/protobuf/ParserTest.java | 24 +- .../com/google/protobuf/TestBadIdentifiers.java | 33 + .../test/java/com/google/protobuf/TestUtil.java | 175 +++- .../java/com/google/protobuf/TextFormatTest.java | 325 ++++++- .../com/google/protobuf/UnknownFieldSetTest.java | 216 +++++ .../protobuf/UnmodifiableLazyStringListTest.java | 75 ++ .../java/com/google/protobuf/WireFormatTest.java | 30 +- .../com/google/protobuf/lazy_fields_lite.proto | 61 ++ .../com/google/protobuf/lite_equals_and_hash.proto | 55 ++ .../com/google/protobuf/multiple_files_test.proto | 8 +- .../com/google/protobuf/nested_extension.proto | 1 + .../com/google/protobuf/non_nested_extension.proto | 1 + .../google/protobuf/outer_class_name_test.proto | 38 + .../google/protobuf/outer_class_name_test2.proto | 42 + .../google/protobuf/outer_class_name_test3.proto | 43 + .../com/google/protobuf/test_bad_identifiers.proto | 49 ++ .../java/com/google/protobuf/test_check_utf8.proto | 50 ++ .../com/google/protobuf/test_check_utf8_size.proto | 51 ++ .../com/google/protobuf/test_custom_options.proto | 43 + .../google/protobuf/test_extra_interfaces.proto | 60 ++ 63 files changed, 7578 insertions(+), 1332 deletions(-) create mode 100644 java/src/main/java/com/google/protobuf/Extension.java create mode 100644 java/src/main/java/com/google/protobuf/MessageReflection.java create mode 100644 java/src/main/java/com/google/protobuf/ProtocolStringList.java create mode 100644 java/src/test/java/com/google/protobuf/CheckUtf8Test.java create mode 100644 java/src/test/java/com/google/protobuf/LazyFieldLiteTest.java create mode 100644 java/src/test/java/com/google/protobuf/LazyFieldTest.java create mode 100644 java/src/test/java/com/google/protobuf/LazyMessageLiteTest.java create mode 100644 java/src/test/java/com/google/protobuf/LiteEqualsAndHashTest.java create mode 100644 java/src/test/java/com/google/protobuf/lazy_fields_lite.proto create mode 100644 java/src/test/java/com/google/protobuf/lite_equals_and_hash.proto create mode 100644 java/src/test/java/com/google/protobuf/outer_class_name_test.proto create mode 100644 java/src/test/java/com/google/protobuf/outer_class_name_test2.proto create mode 100644 java/src/test/java/com/google/protobuf/outer_class_name_test3.proto create mode 100644 java/src/test/java/com/google/protobuf/test_check_utf8.proto create mode 100644 java/src/test/java/com/google/protobuf/test_check_utf8_size.proto create mode 100644 java/src/test/java/com/google/protobuf/test_custom_options.proto create mode 100644 java/src/test/java/com/google/protobuf/test_extra_interfaces.proto (limited to 'java/src') diff --git a/java/src/main/java/com/google/protobuf/AbstractMessage.java b/java/src/main/java/com/google/protobuf/AbstractMessage.java index f4d115de..a645c205 100644 --- a/java/src/main/java/com/google/protobuf/AbstractMessage.java +++ b/java/src/main/java/com/google/protobuf/AbstractMessage.java @@ -30,14 +30,13 @@ package com.google.protobuf; -import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.FieldDescriptor; -import com.google.protobuf.GeneratedMessage.ExtendableBuilder; +import com.google.protobuf.Descriptors.OneofDescriptor; import com.google.protobuf.Internal.EnumLite; import java.io.IOException; import java.io.InputStream; -import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; @@ -49,56 +48,30 @@ import java.util.Map; */ public abstract class AbstractMessage extends AbstractMessageLite implements Message { - @SuppressWarnings("unchecked") public boolean isInitialized() { - // Check that all required fields are present. - for (final FieldDescriptor field : getDescriptorForType().getFields()) { - if (field.isRequired()) { - if (!hasField(field)) { - return false; - } - } - } - - // Check that embedded messages are initialized. - for (final Map.Entry entry : - getAllFields().entrySet()) { - final FieldDescriptor field = entry.getKey(); - if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { - if (field.isRepeated()) { - for (final Message element : (List) entry.getValue()) { - if (!element.isInitialized()) { - return false; - } - } - } else { - if (!((Message) entry.getValue()).isInitialized()) { - return false; - } - } - } - } - - return true; + return MessageReflection.isInitialized(this); } + public List findInitializationErrors() { - return Builder.findMissingFields(this); + return MessageReflection.findMissingFields(this); } public String getInitializationErrorString() { - return delimitWithCommas(findInitializationErrors()); + return MessageReflection.delimitWithCommas(findInitializationErrors()); } - private static String delimitWithCommas(List parts) { - StringBuilder result = new StringBuilder(); - for (String part : parts) { - if (result.length() > 0) { - result.append(", "); - } - result.append(part); - } - return result.toString(); + /** TODO(jieluo): Clear it when all subclasses have implemented this method. */ + @Override + public boolean hasOneof(OneofDescriptor oneof) { + throw new UnsupportedOperationException("hasOneof() is not implemented."); + } + + /** TODO(jieluo): Clear it when all subclasses have implemented this method. */ + @Override + public FieldDescriptor getOneofFieldDescriptor(OneofDescriptor oneof) { + throw new UnsupportedOperationException( + "getOneofFieldDescriptor() is not implemented."); } @Override @@ -107,28 +80,7 @@ public abstract class AbstractMessage extends AbstractMessageLite } public void writeTo(final CodedOutputStream output) throws IOException { - final boolean isMessageSet = - getDescriptorForType().getOptions().getMessageSetWireFormat(); - - for (final Map.Entry entry : - getAllFields().entrySet()) { - final FieldDescriptor field = entry.getKey(); - final Object value = entry.getValue(); - if (isMessageSet && field.isExtension() && - field.getType() == FieldDescriptor.Type.MESSAGE && - !field.isRepeated()) { - output.writeMessageSetExtension(field.getNumber(), (Message) value); - } else { - FieldSet.writeField(field, value, output); - } - } - - final UnknownFieldSet unknownFields = getUnknownFields(); - if (isMessageSet) { - unknownFields.writeAsMessageSetTo(output); - } else { - unknownFields.writeTo(output); - } + MessageReflection.writeMessageTo(this, output, false); } private int memoizedSize = -1; @@ -139,33 +91,8 @@ public abstract class AbstractMessage extends AbstractMessageLite return size; } - size = 0; - final boolean isMessageSet = - getDescriptorForType().getOptions().getMessageSetWireFormat(); - - for (final Map.Entry entry : - getAllFields().entrySet()) { - final FieldDescriptor field = entry.getKey(); - final Object value = entry.getValue(); - if (isMessageSet && field.isExtension() && - field.getType() == FieldDescriptor.Type.MESSAGE && - !field.isRepeated()) { - size += CodedOutputStream.computeMessageSetExtensionSize( - field.getNumber(), (Message) value); - } else { - size += FieldSet.computeFieldSize(field, value); - } - } - - final UnknownFieldSet unknownFields = getUnknownFields(); - if (isMessageSet) { - size += unknownFields.getSerializedSizeAsMessageSet(); - } else { - size += unknownFields.getSerializedSize(); - } - - memoizedSize = size; - return size; + memoizedSize = MessageReflection.getSerializedSize(this); + return memoizedSize; } @Override @@ -180,22 +107,93 @@ public abstract class AbstractMessage extends AbstractMessageLite if (getDescriptorForType() != otherMessage.getDescriptorForType()) { return false; } - return getAllFields().equals(otherMessage.getAllFields()) && + return compareFields(getAllFields(), otherMessage.getAllFields()) && getUnknownFields().equals(otherMessage.getUnknownFields()); } @Override public int hashCode() { - int hash = 41; - hash = (19 * hash) + getDescriptorForType().hashCode(); - hash = hashFields(hash, getAllFields()); - hash = (29 * hash) + getUnknownFields().hashCode(); + int hash = memoizedHashCode; + if (hash == 0) { + hash = 41; + hash = (19 * hash) + getDescriptorForType().hashCode(); + hash = hashFields(hash, getAllFields()); + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + } return hash; } + + private static ByteString toByteString(Object value) { + if (value instanceof byte[]) { + return ByteString.copyFrom((byte[]) value); + } else { + return (ByteString) value; + } + } + + /** + * Compares two bytes fields. The parameters must be either a byte array or a + * ByteString object. They can be of different type though. + */ + private static boolean compareBytes(Object a, Object b) { + if (a instanceof byte[] && b instanceof byte[]) { + return Arrays.equals((byte[])a, (byte[])b); + } + return toByteString(a).equals(toByteString(b)); + } + + /** + * Compares two set of fields. + * This method is used to implement {@link AbstractMessage#equals(Object)} + * and {@link AbstractMutableMessage#equals(Object)}. It takes special care + * of bytes fields because immutable messages and mutable messages use + * different Java type to reprensent a bytes field and this method should be + * able to compare immutable messages, mutable messages and also an immutable + * message to a mutable message. + */ + static boolean compareFields(Map a, + Map b) { + if (a.size() != b.size()) { + return false; + } + for (FieldDescriptor descriptor : a.keySet()) { + if (!b.containsKey(descriptor)) { + return false; + } + Object value1 = a.get(descriptor); + Object value2 = b.get(descriptor); + if (descriptor.getType() == FieldDescriptor.Type.BYTES) { + if (descriptor.isRepeated()) { + List list1 = (List) value1; + List list2 = (List) value2; + if (list1.size() != list2.size()) { + return false; + } + for (int i = 0; i < list1.size(); i++) { + if (!compareBytes(list1.get(i), list2.get(i))) { + return false; + } + } + } else { + // Compares a singular bytes field. + if (!compareBytes(value1, value2)) { + return false; + } + } + } else { + // Compare non-bytes fields. + if (!value1.equals(value2)) { + return false; + } + } + } + return true; + } /** Get a hash code for given fields and values, using the given seed. */ @SuppressWarnings("unchecked") - protected int hashFields(int hash, Map map) { + protected static int hashFields(int hash, Map map) { for (Map.Entry entry : map.entrySet()) { FieldDescriptor field = entry.getKey(); Object value = entry.getValue(); @@ -204,30 +202,14 @@ public abstract class AbstractMessage extends AbstractMessageLite hash = (53 * hash) + value.hashCode(); } else if (field.isRepeated()) { List list = (List) value; - hash = (53 * hash) + hashEnumList(list); + hash = (53 * hash) + Internal.hashEnumList(list); } else { - hash = (53 * hash) + hashEnum((EnumLite) value); + hash = (53 * hash) + Internal.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; - } - /** * Package private helper method for AbstractParser to create * UninitializedMessageException with missing field information. @@ -237,26 +219,6 @@ public abstract class AbstractMessage extends AbstractMessageLite return Builder.newUninitializedMessageException(this); } - /** - * 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 list) { - int hash = 1; - for (EnumLite e : list) { - hash = 31 * hash + hashEnum(e); - } - return hash; - } - // ================================================================= /** @@ -272,6 +234,25 @@ public abstract class AbstractMessage extends AbstractMessageLite @Override public abstract BuilderType clone(); + /** TODO(jieluo): Clear it when all subclasses have implemented this method. */ + @Override + public boolean hasOneof(OneofDescriptor oneof) { + throw new UnsupportedOperationException("hasOneof() is not implemented."); + } + + /** TODO(jieluo): Clear it when all subclasses have implemented this method. */ + @Override + public FieldDescriptor getOneofFieldDescriptor(OneofDescriptor oneof) { + throw new UnsupportedOperationException( + "getOneofFieldDescriptor() is not implemented."); + } + + /** TODO(jieluo): Clear it when all subclasses have implemented this method. */ + @Override + public BuilderType clearOneof(OneofDescriptor oneof) { + throw new UnsupportedOperationException("clearOneof() is not implemented."); + } + public BuilderType clear() { for (final Map.Entry entry : getAllFields().entrySet()) { @@ -281,11 +262,11 @@ public abstract class AbstractMessage extends AbstractMessageLite } public List findInitializationErrors() { - return findMissingFields(this); + return MessageReflection.findMissingFields(this); } public String getInitializationErrorString() { - return delimitWithCommas(findInitializationErrors()); + return MessageReflection.delimitWithCommas(findInitializationErrors()); } public BuilderType mergeFrom(final Message other) { @@ -350,8 +331,13 @@ public abstract class AbstractMessage extends AbstractMessageLite break; } - if (!mergeFieldFrom(input, unknownFields, extensionRegistry, - getDescriptorForType(), this, null, tag)) { + MessageReflection.BuilderAdapter builderAdapter = + new MessageReflection.BuilderAdapter(this); + if (!MessageReflection.mergeFieldFrom(input, unknownFields, + extensionRegistry, + getDescriptorForType(), + builderAdapter, + tag)) { // end group tag break; } @@ -360,394 +346,6 @@ public abstract class AbstractMessage extends AbstractMessageLite return (BuilderType) this; } - /** helper method to handle {@code builder} and {@code extensions}. */ - private static void addRepeatedField( - Message.Builder builder, - FieldSet extensions, - FieldDescriptor field, - Object value) { - if (builder != null) { - builder.addRepeatedField(field, value); - } else { - extensions.addRepeatedField(field, value); - } - } - - /** helper method to handle {@code builder} and {@code extensions}. */ - private static void setField( - Message.Builder builder, - FieldSet extensions, - FieldDescriptor field, - Object value) { - if (builder != null) { - builder.setField(field, value); - } else { - extensions.setField(field, value); - } - } - - /** helper method to handle {@code builder} and {@code extensions}. */ - private static boolean hasOriginalMessage( - Message.Builder builder, - FieldSet extensions, - FieldDescriptor field) { - if (builder != null) { - return builder.hasField(field); - } else { - return extensions.hasField(field); - } - } - - /** helper method to handle {@code builder} and {@code extensions}. */ - private static Message getOriginalMessage( - Message.Builder builder, - FieldSet extensions, - FieldDescriptor field) { - if (builder != null) { - return (Message) builder.getField(field); - } else { - return (Message) extensions.getField(field); - } - } - - /** helper method to handle {@code builder} and {@code extensions}. */ - private static void mergeOriginalMessage( - Message.Builder builder, - FieldSet extensions, - FieldDescriptor field, - Message.Builder subBuilder) { - Message originalMessage = getOriginalMessage(builder, extensions, field); - if (originalMessage != null) { - subBuilder.mergeFrom(originalMessage); - } - } - - /** - * Like {@link #mergeFrom(CodedInputStream, ExtensionRegistryLite)}, but - * parses a single field. - * - * When {@code builder} is not null, the method will parse and merge the - * field into {@code builder}. Otherwise, it will try to parse the field - * into {@code extensions}, when it's called by the parsing constructor in - * generated classes. - * - * Package-private because it is used by GeneratedMessage.ExtendableMessage. - * @param tag The tag, which should have already been read. - * @return {@code true} unless the tag is an end-group tag. - */ - static boolean mergeFieldFrom( - CodedInputStream input, - UnknownFieldSet.Builder unknownFields, - ExtensionRegistryLite extensionRegistry, - Descriptor type, - Message.Builder builder, - FieldSet extensions, - int tag) throws IOException { - if (type.getOptions().getMessageSetWireFormat() && - tag == WireFormat.MESSAGE_SET_ITEM_TAG) { - mergeMessageSetExtensionFromCodedStream( - input, unknownFields, extensionRegistry, type, builder, extensions); - return true; - } - - final int wireType = WireFormat.getTagWireType(tag); - final int fieldNumber = WireFormat.getTagFieldNumber(tag); - - final FieldDescriptor field; - Message defaultInstance = null; - - if (type.isExtensionNumber(fieldNumber)) { - // extensionRegistry may be either ExtensionRegistry or - // ExtensionRegistryLite. Since the type we are parsing is a full - // message, only a full ExtensionRegistry could possibly contain - // extensions of it. Otherwise we will treat the registry as if it - // were empty. - if (extensionRegistry instanceof ExtensionRegistry) { - final ExtensionRegistry.ExtensionInfo extension = - ((ExtensionRegistry) extensionRegistry) - .findExtensionByNumber(type, fieldNumber); - if (extension == null) { - field = null; - } else { - field = extension.descriptor; - defaultInstance = extension.defaultInstance; - if (defaultInstance == null && - field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { - throw new IllegalStateException( - "Message-typed extension lacked default instance: " + - field.getFullName()); - } - } - } else { - field = null; - } - } else if (builder != null) { - field = type.findFieldByNumber(fieldNumber); - } else { - field = null; - } - - boolean unknown = false; - boolean packed = false; - if (field == null) { - unknown = true; // Unknown field. - } else if (wireType == FieldSet.getWireFormatForFieldType( - field.getLiteType(), - false /* isPacked */)) { - packed = false; - } else if (field.isPackable() && - wireType == FieldSet.getWireFormatForFieldType( - field.getLiteType(), - true /* isPacked */)) { - packed = true; - } else { - unknown = true; // Unknown wire type. - } - - if (unknown) { // Unknown field or wrong wire type. Skip. - return unknownFields.mergeFieldFrom(tag, input); - } - - if (packed) { - final int length = input.readRawVarint32(); - final int limit = input.pushLimit(length); - if (field.getLiteType() == WireFormat.FieldType.ENUM) { - while (input.getBytesUntilLimit() > 0) { - final int rawValue = input.readEnum(); - final Object value = field.getEnumType().findValueByNumber(rawValue); - if (value == null) { - // If the number isn't recognized as a valid value for this - // enum, drop it (don't even add it to unknownFields). - return true; - } - addRepeatedField(builder, extensions, field, value); - } - } else { - while (input.getBytesUntilLimit() > 0) { - final Object value = - FieldSet.readPrimitiveField(input, field.getLiteType()); - addRepeatedField(builder, extensions, field, value); - } - } - input.popLimit(limit); - } else { - final Object value; - switch (field.getType()) { - case GROUP: { - final Message.Builder subBuilder; - if (defaultInstance != null) { - subBuilder = defaultInstance.newBuilderForType(); - } else { - subBuilder = builder.newBuilderForField(field); - } - if (!field.isRepeated()) { - mergeOriginalMessage(builder, extensions, field, subBuilder); - } - input.readGroup(field.getNumber(), subBuilder, extensionRegistry); - value = subBuilder.buildPartial(); - break; - } - case MESSAGE: { - final Message.Builder subBuilder; - if (defaultInstance != null) { - subBuilder = defaultInstance.newBuilderForType(); - } else { - subBuilder = builder.newBuilderForField(field); - } - if (!field.isRepeated()) { - mergeOriginalMessage(builder, extensions, field, subBuilder); - } - input.readMessage(subBuilder, extensionRegistry); - value = subBuilder.buildPartial(); - break; - } - case ENUM: - final int rawValue = input.readEnum(); - value = field.getEnumType().findValueByNumber(rawValue); - // If the number isn't recognized as a valid value for this enum, - // drop it. - if (value == null) { - unknownFields.mergeVarintField(fieldNumber, rawValue); - return true; - } - break; - default: - value = FieldSet.readPrimitiveField(input, field.getLiteType()); - break; - } - - if (field.isRepeated()) { - addRepeatedField(builder, extensions, field, value); - } else { - setField(builder, extensions, field, value); - } - } - - return true; - } - - /** - * Called by {@code #mergeFieldFrom()} to parse a MessageSet extension. - * If {@code builder} is not null, this method will merge MessageSet into - * the builder. Otherwise, it will merge the MessageSet into {@code - * extensions}. - */ - private static void mergeMessageSetExtensionFromCodedStream( - CodedInputStream input, - UnknownFieldSet.Builder unknownFields, - ExtensionRegistryLite extensionRegistry, - Descriptor type, - Message.Builder builder, - FieldSet extensions) throws IOException { - - // The wire format for MessageSet is: - // message MessageSet { - // repeated group Item = 1 { - // required int32 typeId = 2; - // required bytes message = 3; - // } - // } - // "typeId" is the extension's field number. The extension can only be - // a message type, where "message" contains the encoded bytes of that - // message. - // - // In practice, we will probably never see a MessageSet item in which - // the message appears before the type ID, or where either field does not - // appear exactly once. However, in theory such cases are valid, so we - // should be prepared to accept them. - - int typeId = 0; - ByteString rawBytes = null; // If we encounter "message" before "typeId" - ExtensionRegistry.ExtensionInfo extension = null; - - // Read bytes from input, if we get it's type first then parse it eagerly, - // otherwise we store the raw bytes in a local variable. - while (true) { - final int tag = input.readTag(); - if (tag == 0) { - break; - } - - if (tag == WireFormat.MESSAGE_SET_TYPE_ID_TAG) { - typeId = input.readUInt32(); - if (typeId != 0) { - // extensionRegistry may be either ExtensionRegistry or - // ExtensionRegistryLite. Since the type we are parsing is a full - // message, only a full ExtensionRegistry could possibly contain - // extensions of it. Otherwise we will treat the registry as if it - // were empty. - if (extensionRegistry instanceof ExtensionRegistry) { - extension = ((ExtensionRegistry) extensionRegistry) - .findExtensionByNumber(type, typeId); - } - } - - } else if (tag == WireFormat.MESSAGE_SET_MESSAGE_TAG) { - if (typeId != 0) { - if (extension != null && ExtensionRegistryLite.isEagerlyParseMessageSets()) { - // We already know the type, so we can parse directly from the - // input with no copying. Hooray! - eagerlyMergeMessageSetExtension( - input, extension, extensionRegistry, builder, extensions); - rawBytes = null; - continue; - } - } - // We haven't seen a type ID yet or we want parse message lazily. - rawBytes = input.readBytes(); - - } else { // Unknown tag. Skip it. - if (!input.skipField(tag)) { - break; // End of group - } - } - } - input.checkLastTagWas(WireFormat.MESSAGE_SET_ITEM_END_TAG); - - // Process the raw bytes. - if (rawBytes != null && typeId != 0) { // Zero is not a valid type ID. - if (extension != null) { // We known the type - mergeMessageSetExtensionFromBytes( - rawBytes, extension, extensionRegistry, builder, extensions); - } else { // We don't know how to parse this. Ignore it. - if (rawBytes != null) { - unknownFields.mergeField(typeId, UnknownFieldSet.Field.newBuilder() - .addLengthDelimited(rawBytes).build()); - } - } - } - } - - private static void eagerlyMergeMessageSetExtension( - CodedInputStream input, - ExtensionRegistry.ExtensionInfo extension, - ExtensionRegistryLite extensionRegistry, - Message.Builder builder, - FieldSet extensions) throws IOException { - - FieldDescriptor field = extension.descriptor; - Message value = null; - if (hasOriginalMessage(builder, extensions, field)) { - Message originalMessage = - getOriginalMessage(builder, extensions, field); - Message.Builder subBuilder = originalMessage.toBuilder(); - input.readMessage(subBuilder, extensionRegistry); - value = subBuilder.buildPartial(); - } else { - value = input.readMessage(extension.defaultInstance.getParserForType(), - extensionRegistry); - } - - if (builder != null) { - builder.setField(field, value); - } else { - extensions.setField(field, value); - } - } - - private static void mergeMessageSetExtensionFromBytes( - ByteString rawBytes, - ExtensionRegistry.ExtensionInfo extension, - ExtensionRegistryLite extensionRegistry, - Message.Builder builder, - FieldSet extensions) throws IOException { - - FieldDescriptor field = extension.descriptor; - boolean hasOriginalValue = hasOriginalMessage(builder, extensions, field); - - if (hasOriginalValue || ExtensionRegistryLite.isEagerlyParseMessageSets()) { - // If the field already exists, we just parse the field. - Message value = null; - if (hasOriginalValue) { - Message originalMessage = - getOriginalMessage(builder, extensions, field); - Message.Builder subBuilder= originalMessage.toBuilder(); - subBuilder.mergeFrom(rawBytes, extensionRegistry); - value = subBuilder.buildPartial(); - } else { - value = extension.defaultInstance.getParserForType() - .parsePartialFrom(rawBytes, extensionRegistry); - } - setField(builder, extensions, field, value); - } else { - // Use LazyField to load MessageSet lazily. - LazyField lazyField = new LazyField( - extension.defaultInstance, extensionRegistry, rawBytes); - if (builder != null) { - // TODO(xiangl): it looks like this method can only be invoked by - // ExtendableBuilder, but I'm not sure. So I double check the type of - // builder here. It may be useless and need more investigation. - if (builder instanceof ExtendableBuilder) { - builder.setField(field, lazyField); - } else { - builder.setField(field, lazyField.getValue()); - } - } else { - extensions.setField(field, lazyField); - } - } - } - public BuilderType mergeUnknownFields(final UnknownFieldSet unknownFields) { setUnknownFields( UnknownFieldSet.newBuilder(getUnknownFields()) @@ -761,79 +359,18 @@ public abstract class AbstractMessage extends AbstractMessageLite "getFieldBuilder() called on an unsupported message type."); } + public String toString() { + return TextFormat.printToString(this); + } + /** * Construct an UninitializedMessageException reporting missing fields in * the given message. */ protected static UninitializedMessageException newUninitializedMessageException(Message message) { - return new UninitializedMessageException(findMissingFields(message)); - } - - /** - * Populates {@code this.missingFields} with the full "path" of each - * missing required field in the given message. - */ - private static List findMissingFields( - final MessageOrBuilder message) { - final List results = new ArrayList(); - findMissingFields(message, "", results); - return results; - } - - /** Recursive helper implementing {@link #findMissingFields(Message)}. */ - private static void findMissingFields(final MessageOrBuilder message, - final String prefix, - final List results) { - for (final FieldDescriptor field : - message.getDescriptorForType().getFields()) { - if (field.isRequired() && !message.hasField(field)) { - results.add(prefix + field.getName()); - } - } - - for (final Map.Entry entry : - message.getAllFields().entrySet()) { - final FieldDescriptor field = entry.getKey(); - final Object value = entry.getValue(); - - if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { - if (field.isRepeated()) { - int i = 0; - for (final Object element : (List) value) { - findMissingFields((MessageOrBuilder) element, - subMessagePrefix(prefix, field, i++), - results); - } - } else { - if (message.hasField(field)) { - findMissingFields((MessageOrBuilder) value, - subMessagePrefix(prefix, field, -1), - results); - } - } - } - } - } - - private static String subMessagePrefix(final String prefix, - final FieldDescriptor field, - final int index) { - final StringBuilder result = new StringBuilder(prefix); - if (field.isExtension()) { - result.append('(') - .append(field.getFullName()) - .append(')'); - } else { - result.append(field.getName()); - } - if (index != -1) { - result.append('[') - .append(index) - .append(']'); - } - result.append('.'); - return result.toString(); + return new UninitializedMessageException( + MessageReflection.findMissingFields(message)); } // =============================================================== @@ -925,6 +462,5 @@ public abstract class AbstractMessage extends AbstractMessageLite throws IOException { return super.mergeDelimitedFrom(input, extensionRegistry); } - } } diff --git a/java/src/main/java/com/google/protobuf/AbstractMessageLite.java b/java/src/main/java/com/google/protobuf/AbstractMessageLite.java index 9926f3db..bce713b0 100644 --- a/java/src/main/java/com/google/protobuf/AbstractMessageLite.java +++ b/java/src/main/java/com/google/protobuf/AbstractMessageLite.java @@ -44,6 +44,8 @@ import java.util.Collection; * @author kenton@google.com Kenton Varda */ public abstract class AbstractMessageLite implements MessageLite { + protected int memoizedHashCode = 0; + public ByteString toByteString() { try { final ByteString.CodedBuilder out = @@ -91,6 +93,7 @@ public abstract class AbstractMessageLite implements MessageLite { codedOutput.flush(); } + /** * Package private helper method for AbstractParser to create * UninitializedMessageException. @@ -99,6 +102,13 @@ public abstract class AbstractMessageLite implements MessageLite { return new UninitializedMessageException(this); } + protected static void checkByteStringIsUtf8(ByteString byteString) + throws IllegalArgumentException { + if (!byteString.isValidUtf8()) { + throw new IllegalArgumentException("Byte string is not UTF-8."); + } + } + /** * A partial implementation of the {@link Message.Builder} interface which * implements as many methods of that interface as possible in terms of @@ -311,7 +321,8 @@ public abstract class AbstractMessageLite implements MessageLite { * used by generated code. Users should ignore it. * * @throws NullPointerException if any of the elements of {@code values} is - * null. + * null. When that happens, some elements of {@code values} may have already + * been added to the result {@code list}. */ protected static void addAll(final Iterable values, final Collection list) { @@ -319,14 +330,15 @@ public abstract class AbstractMessageLite implements MessageLite { // For StringOrByteStringLists, check the underlying elements to avoid // forcing conversions of ByteStrings to Strings. checkForNullValues(((LazyStringList) values).getUnderlyingElements()); - } else { + list.addAll((Collection) values); + } else if (values instanceof Collection) { checkForNullValues(values); - } - if (values instanceof Collection) { - final Collection collection = (Collection) values; - list.addAll(collection); + list.addAll((Collection) values); } else { for (final T value : values) { + if (value == null) { + throw new NullPointerException(); + } list.add(value); } } diff --git a/java/src/main/java/com/google/protobuf/AbstractParser.java b/java/src/main/java/com/google/protobuf/AbstractParser.java index 9bd9d397..3f30124f 100644 --- a/java/src/main/java/com/google/protobuf/AbstractParser.java +++ b/java/src/main/java/com/google/protobuf/AbstractParser.java @@ -110,10 +110,6 @@ public abstract class AbstractParser return message; } catch (InvalidProtocolBufferException e) { throw e; - } catch (IOException e) { - throw new RuntimeException( - "Reading from a ByteString threw an IOException (should " + - "never happen).", e); } } @@ -147,10 +143,6 @@ public abstract class AbstractParser return message; } catch (InvalidProtocolBufferException e) { throw e; - } catch (IOException e) { - throw new RuntimeException( - "Reading from a byte array threw an IOException (should " + - "never happen).", e); } } diff --git a/java/src/main/java/com/google/protobuf/ByteString.java b/java/src/main/java/com/google/protobuf/ByteString.java index 73d831f6..89b57897 100644 --- a/java/src/main/java/com/google/protobuf/ByteString.java +++ b/java/src/main/java/com/google/protobuf/ByteString.java @@ -177,6 +177,20 @@ public abstract class ByteString implements Iterable { substring(0, prefix.size()).equals(prefix); } + /** + * Tests if this bytestring ends with the specified suffix. + * Similar to {@link String#endsWith(String)} + * + * @param suffix the suffix. + * @return true if the byte sequence represented by the + * argument is a suffix of the byte sequence represented by + * this string; false otherwise. + */ + public boolean endsWith(ByteString suffix) { + return size() >= suffix.size() && + substring(size() - suffix.size()).equals(suffix); + } + // ================================================================= // byte[] -> ByteString @@ -512,6 +526,9 @@ public abstract class ByteString implements Iterable { */ public byte[] toByteArray() { int size = size(); + if (size == 0) { + return Internal.EMPTY_BYTE_ARRAY; + } byte[] result = new byte[size]; copyToInternal(result, 0, 0, size); return result; @@ -525,6 +542,41 @@ public abstract class ByteString implements Iterable { * @throws IOException if an I/O error occurs. */ public abstract void writeTo(OutputStream out) throws IOException; + + /** + * Writes a specified part of this byte string to an output stream. + * + * @param out the output stream to which to write the data. + * @param sourceOffset offset within these bytes + * @param numberToWrite number of bytes to write + * @throws IOException if an I/O error occurs. + * @throws IndexOutOfBoundsException if an offset or size is negative or too + * large + */ + void writeTo(OutputStream out, int sourceOffset, int numberToWrite) + throws IOException { + if (sourceOffset < 0) { + throw new IndexOutOfBoundsException("Source offset < 0: " + sourceOffset); + } + if (numberToWrite < 0) { + throw new IndexOutOfBoundsException("Length < 0: " + numberToWrite); + } + if (sourceOffset + numberToWrite > size()) { + throw new IndexOutOfBoundsException( + "Source end offset exceeded: " + (sourceOffset + numberToWrite)); + } + if (numberToWrite > 0) { + writeToInternal(out, sourceOffset, numberToWrite); + } + + } + + /** + * Internal version of {@link #writeTo(OutputStream,int,int)} that assumes + * all error checking has already been done. + */ + abstract void writeToInternal(OutputStream out, int sourceOffset, + int numberToWrite) throws IOException; /** * Constructs a read-only {@code java.nio.ByteBuffer} whose content diff --git a/java/src/main/java/com/google/protobuf/CodedInputStream.java b/java/src/main/java/com/google/protobuf/CodedInputStream.java index 33417a7f..b25b9713 100644 --- a/java/src/main/java/com/google/protobuf/CodedInputStream.java +++ b/java/src/main/java/com/google/protobuf/CodedInputStream.java @@ -30,9 +30,12 @@ package com.google.protobuf; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** @@ -88,6 +91,53 @@ public final class CodedInputStream { return result; } + /** + * Create a new CodedInputStream wrapping the given ByteBuffer. The data + * starting from the ByteBuffer's current position to its limit will be read. + * The returned CodedInputStream may or may not share the underlying data + * in the ByteBuffer, therefore the ByteBuffer cannot be changed while the + * CodedInputStream is in use. + * Note that the ByteBuffer's position won't be changed by this function. + * Concurrent calls with the same ByteBuffer object are safe if no other + * thread is trying to alter the ByteBuffer's status. + */ + public static CodedInputStream newInstance(ByteBuffer buf) { + if (buf.hasArray()) { + return newInstance(buf.array(), buf.arrayOffset() + buf.position(), + buf.remaining()); + } else { + ByteBuffer temp = buf.duplicate(); + byte[] buffer = new byte[temp.remaining()]; + temp.get(buffer); + return newInstance(buffer); + } + } + + /** + * Create a new CodedInputStream wrapping a LiteralByteString. + */ + static CodedInputStream newInstance(LiteralByteString byteString) { + CodedInputStream result = new CodedInputStream(byteString); + 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(byteString.size()); + } 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; + } + // ----------------------------------------------------------------- /** @@ -125,6 +175,10 @@ public final class CodedInputStream { } } + public int getLastTag() { + return lastTag; + } + /** * Reads and discards a single field, given its tag value. * @@ -134,10 +188,10 @@ public final class CodedInputStream { public boolean skipField(final int tag) throws IOException { switch (WireFormat.getTagWireType(tag)) { case WireFormat.WIRETYPE_VARINT: - readInt32(); + skipRawVarint(); return true; case WireFormat.WIRETYPE_FIXED64: - readRawLittleEndian64(); + skipRawBytes(8); return true; case WireFormat.WIRETYPE_LENGTH_DELIMITED: skipRawBytes(readRawVarint32()); @@ -151,8 +205,59 @@ public final class CodedInputStream { case WireFormat.WIRETYPE_END_GROUP: return false; case WireFormat.WIRETYPE_FIXED32: - readRawLittleEndian32(); + skipRawBytes(4); + return true; + default: + throw InvalidProtocolBufferException.invalidWireType(); + } + } + + /** + * Reads a single field and writes it to output in wire format, + * given its tag value. + * + * @return {@code false} if the tag is an endgroup tag, in which case + * nothing is skipped. Otherwise, returns {@code true}. + */ + public boolean skipField(final int tag, final CodedOutputStream output) + throws IOException { + switch (WireFormat.getTagWireType(tag)) { + case WireFormat.WIRETYPE_VARINT: { + long value = readInt64(); + output.writeRawVarint32(tag); + output.writeUInt64NoTag(value); + return true; + } + case WireFormat.WIRETYPE_FIXED64: { + long value = readRawLittleEndian64(); + output.writeRawVarint32(tag); + output.writeFixed64NoTag(value); + return true; + } + case WireFormat.WIRETYPE_LENGTH_DELIMITED: { + ByteString value = readBytes(); + output.writeRawVarint32(tag); + output.writeBytesNoTag(value); + return true; + } + case WireFormat.WIRETYPE_START_GROUP: { + output.writeRawVarint32(tag); + skipMessage(output); + int endtag = WireFormat.makeTag(WireFormat.getTagFieldNumber(tag), + WireFormat.WIRETYPE_END_GROUP); + checkLastTagWas(endtag); + output.writeRawVarint32(endtag); return true; + } + case WireFormat.WIRETYPE_END_GROUP: { + return false; + } + case WireFormat.WIRETYPE_FIXED32: { + int value = readRawLittleEndian32(); + output.writeRawVarint32(tag); + output.writeFixed32NoTag(value); + return true; + } default: throw InvalidProtocolBufferException.invalidWireType(); } @@ -171,6 +276,51 @@ public final class CodedInputStream { } } + /** + * Reads an entire message and writes it to output in wire format. + * This will read either until EOF or until an endgroup tag, + * whichever comes first. + */ + public void skipMessage(CodedOutputStream output) throws IOException { + while (true) { + final int tag = readTag(); + if (tag == 0 || !skipField(tag, output)) { + return; + } + } + } + + /** + * Collects the bytes skipped and returns the data in a ByteBuffer. + */ + private class SkippedDataSink implements RefillCallback { + private int lastPos = bufferPos; + private ByteArrayOutputStream byteArrayStream; + + @Override + public void onRefill() { + if (byteArrayStream == null) { + byteArrayStream = new ByteArrayOutputStream(); + } + byteArrayStream.write(buffer, lastPos, bufferPos - lastPos); + lastPos = 0; + } + + /** + * Gets skipped data in a ByteBuffer. This method should only be + * called once. + */ + ByteBuffer getSkippedData() { + if (byteArrayStream == null) { + return ByteBuffer.wrap(buffer, lastPos, bufferPos - lastPos); + } else { + byteArrayStream.write(buffer, lastPos, bufferPos); + return ByteBuffer.wrap(byteArrayStream.toByteArray()); + } + } + } + + // ----------------------------------------------------------------- /** Read a {@code double} field value from the stream. */ @@ -210,10 +360,14 @@ public final class CodedInputStream { /** Read a {@code bool} field value from the stream. */ public boolean readBool() throws IOException { - return readRawVarint32() != 0; + return readRawVarint64() != 0; } - /** Read a {@code string} field value from the stream. */ + /** + * Read a {@code string} field value from the stream. + * If the stream contains malformed UTF-8, + * replace the offending bytes with the standard UTF-8 replacement character. + */ public String readString() throws IOException { final int size = readRawVarint32(); if (size <= (bufferSize - bufferPos) && size > 0) { @@ -222,10 +376,40 @@ public final class CodedInputStream { final String result = new String(buffer, bufferPos, size, "UTF-8"); bufferPos += size; return result; + } else if (size == 0) { + return ""; + } else { + // Slow path: Build a byte array first then copy it. + return new String(readRawBytesSlowPath(size), "UTF-8"); + } + } + + /** + * Read a {@code string} field value from the stream. + * If the stream contains malformed UTF-8, + * throw exception {@link InvalidProtocolBufferException}. + */ + public String readStringRequireUtf8() throws IOException { + final int size = readRawVarint32(); + final byte[] bytes; + int pos = bufferPos; + if (size <= (bufferSize - pos) && size > 0) { + // Fast path: We already have the bytes in a contiguous buffer, so + // just copy directly from it. + bytes = buffer; + bufferPos = pos + size; + } else if (size == 0) { + return ""; } else { // Slow path: Build a byte array first then copy it. - return new String(readRawBytes(size), "UTF-8"); + bytes = readRawBytesSlowPath(size); + pos = 0; } + // TODO(martinrb): We could save a pass by validating while decoding. + if (!Utf8.isValidUtf8(bytes, pos, pos + size)) { + throw InvalidProtocolBufferException.invalidUtf8(); + } + return new String(bytes, pos, size, "UTF-8"); } /** Read a {@code group} field value from the stream. */ @@ -243,6 +427,7 @@ public final class CodedInputStream { --recursionDepth; } + /** Read a {@code group} field value from the stream. */ public T readGroup( final int fieldNumber, @@ -295,6 +480,7 @@ public final class CodedInputStream { popLimit(oldLimit); } + /** Read an embedded message field value from the stream. */ public T readMessage( final Parser parser, @@ -316,17 +502,58 @@ public final class CodedInputStream { /** Read a {@code bytes} field value from the stream. */ public ByteString readBytes() throws IOException { final int size = readRawVarint32(); - if (size == 0) { - return ByteString.EMPTY; - } else if (size <= (bufferSize - bufferPos) && size > 0) { + 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); + final ByteString result = bufferIsImmutable && enableAliasing + ? new BoundedByteString(buffer, bufferPos, size) + : ByteString.copyFrom(buffer, bufferPos, size); bufferPos += size; return result; + } else if (size == 0) { + return ByteString.EMPTY; } else { // Slow path: Build a byte array first then copy it. - return ByteString.copyFrom(readRawBytes(size)); + return new LiteralByteString(readRawBytesSlowPath(size)); + } + } + + /** Read a {@code bytes} field value from the stream. */ + public byte[] readByteArray() throws IOException { + final int size = readRawVarint32(); + if (size <= (bufferSize - bufferPos) && size > 0) { + // Fast path: We already have the bytes in a contiguous buffer, so + // just copy directly from it. + final byte[] result = + Arrays.copyOfRange(buffer, bufferPos, bufferPos + size); + bufferPos += size; + return result; + } else { + // Slow path: Build a byte array first then copy it. + return readRawBytesSlowPath(size); + } + } + + /** Read a {@code bytes} field value from the stream. */ + public ByteBuffer readByteBuffer() throws IOException { + final int size = readRawVarint32(); + if (size <= (bufferSize - bufferPos) && size > 0) { + // Fast path: We already have the bytes in a contiguous buffer. + // When aliasing is enabled, we can return a ByteBuffer pointing directly + // into the underlying byte array without copy if the CodedInputStream is + // constructed from a byte array. If aliasing is disabled or the input is + // from an InputStream or ByteString, we have to make a copy of the bytes. + ByteBuffer result = input == null && !bufferIsImmutable && enableAliasing + ? ByteBuffer.wrap(buffer, bufferPos, size).slice() + : ByteBuffer.wrap(Arrays.copyOfRange( + buffer, bufferPos, bufferPos + size)); + bufferPos += size; + return result; + } else if (size == 0) { + return Internal.EMPTY_BYTE_BUFFER; + } else { + // Slow path: Build a byte array first then copy it. + return ByteBuffer.wrap(readRawBytesSlowPath(size)); } } @@ -370,37 +597,67 @@ public final class CodedInputStream { * upper bits. */ public int readRawVarint32() throws IOException { - byte tmp = readRawByte(); - if (tmp >= 0) { - return tmp; - } - int result = tmp & 0x7f; - if ((tmp = readRawByte()) >= 0) { - result |= tmp << 7; - } else { - result |= (tmp & 0x7f) << 7; - if ((tmp = readRawByte()) >= 0) { - result |= tmp << 14; + // See implementation notes for readRawVarint64 + fastpath: { + int pos = bufferPos; + + if (bufferSize == pos) { + break fastpath; + } + + final byte[] buffer = this.buffer; + int x; + if ((x = buffer[pos++]) >= 0) { + bufferPos = pos; + return x; + } else if (bufferSize - pos < 9) { + break fastpath; + } else if ((x ^= (buffer[pos++] << 7)) < 0L) { + x ^= (~0L << 7); + } else if ((x ^= (buffer[pos++] << 14)) >= 0L) { + x ^= (~0L << 7) ^ (~0L << 14); + } else if ((x ^= (buffer[pos++] << 21)) < 0L) { + x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21); } else { - result |= (tmp & 0x7f) << 14; - if ((tmp = readRawByte()) >= 0) { - result |= tmp << 21; - } else { - result |= (tmp & 0x7f) << 21; - result |= (tmp = readRawByte()) << 28; - if (tmp < 0) { - // Discard upper 32 bits. - for (int i = 0; i < 5; i++) { - if (readRawByte() >= 0) { - return result; - } - } - throw InvalidProtocolBufferException.malformedVarint(); - } + int y = buffer[pos++]; + x ^= y << 28; + x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28); + if (y < 0 && + buffer[pos++] < 0 && + buffer[pos++] < 0 && + buffer[pos++] < 0 && + buffer[pos++] < 0 && + buffer[pos++] < 0) { + break fastpath; // Will throw malformedVarint() } } + bufferPos = pos; + return x; } - return result; + return (int) readRawVarint64SlowPath(); + } + + private void skipRawVarint() throws IOException { + if (bufferSize - bufferPos >= 10) { + final byte[] buffer = this.buffer; + int pos = bufferPos; + for (int i = 0; i < 10; i++) { + if (buffer[pos++] >= 0) { + bufferPos = pos; + return; + } + } + } + skipRawVarintSlowPath(); + } + + private void skipRawVarintSlowPath() throws IOException { + for (int i = 0; i < 10; i++) { + if (readRawByte() >= 0) { + return; + } + } + throw InvalidProtocolBufferException.malformedVarint(); } /** @@ -456,49 +713,115 @@ public final class CodedInputStream { /** Read a raw Varint from the stream. */ public long readRawVarint64() throws IOException { - int shift = 0; + // Implementation notes: + // + // Optimized for one-byte values, expected to be common. + // The particular code below was selected from various candidates + // empirically, by winning VarintBenchmark. + // + // Sign extension of (signed) Java bytes is usually a nuisance, but + // we exploit it here to more easily obtain the sign of bytes read. + // Instead of cleaning up the sign extension bits by masking eagerly, + // we delay until we find the final (positive) byte, when we clear all + // accumulated bits with one xor. We depend on javac to constant fold. + fastpath: { + int pos = bufferPos; + + if (bufferSize == pos) { + break fastpath; + } + + final byte[] buffer = this.buffer; + long x; + int y; + if ((y = buffer[pos++]) >= 0) { + bufferPos = pos; + return y; + } else if (bufferSize - pos < 9) { + break fastpath; + } else if ((x = y ^ (buffer[pos++] << 7)) < 0L) { + x ^= (~0L << 7); + } else if ((x ^= (buffer[pos++] << 14)) >= 0L) { + x ^= (~0L << 7) ^ (~0L << 14); + } else if ((x ^= (buffer[pos++] << 21)) < 0L) { + x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21); + } else if ((x ^= ((long) buffer[pos++] << 28)) >= 0L) { + x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28); + } else if ((x ^= ((long) buffer[pos++] << 35)) < 0L) { + x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35); + } else if ((x ^= ((long) buffer[pos++] << 42)) >= 0L) { + x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35) ^ (~0L << 42); + } else if ((x ^= ((long) buffer[pos++] << 49)) < 0L) { + x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35) ^ (~0L << 42) + ^ (~0L << 49); + } else { + x ^= ((long) buffer[pos++] << 56); + x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35) ^ (~0L << 42) + ^ (~0L << 49) ^ (~0L << 56); + if (x < 0L) { + if (buffer[pos++] < 0L) { + break fastpath; // Will throw malformedVarint() + } + } + } + bufferPos = pos; + return x; + } + return readRawVarint64SlowPath(); + } + + /** Variant of readRawVarint64 for when uncomfortably close to the limit. */ + /* Visible for testing */ + long readRawVarint64SlowPath() throws IOException { long result = 0; - while (shift < 64) { + for (int shift = 0; shift < 64; shift += 7) { final byte b = readRawByte(); - result |= (long)(b & 0x7F) << shift; + result |= (long) (b & 0x7F) << shift; if ((b & 0x80) == 0) { return result; } - shift += 7; } throw InvalidProtocolBufferException.malformedVarint(); } /** Read a 32-bit little-endian integer from the stream. */ public int readRawLittleEndian32() throws IOException { - final byte b1 = readRawByte(); - final byte b2 = readRawByte(); - final byte b3 = readRawByte(); - final byte b4 = readRawByte(); - return (((int)b1 & 0xff) ) | - (((int)b2 & 0xff) << 8) | - (((int)b3 & 0xff) << 16) | - (((int)b4 & 0xff) << 24); + int pos = bufferPos; + + // hand-inlined ensureAvailable(4); + if (bufferSize - pos < 4) { + refillBuffer(4); + pos = bufferPos; + } + + final byte[] buffer = this.buffer; + bufferPos = pos + 4; + return (((buffer[pos] & 0xff)) | + ((buffer[pos + 1] & 0xff) << 8) | + ((buffer[pos + 2] & 0xff) << 16) | + ((buffer[pos + 3] & 0xff) << 24)); } /** Read a 64-bit little-endian integer from the stream. */ public long readRawLittleEndian64() throws IOException { - final byte b1 = readRawByte(); - final byte b2 = readRawByte(); - final byte b3 = readRawByte(); - final byte b4 = readRawByte(); - final byte b5 = readRawByte(); - final byte b6 = readRawByte(); - final byte b7 = readRawByte(); - final byte b8 = readRawByte(); - return (((long)b1 & 0xff) ) | - (((long)b2 & 0xff) << 8) | - (((long)b3 & 0xff) << 16) | - (((long)b4 & 0xff) << 24) | - (((long)b5 & 0xff) << 32) | - (((long)b6 & 0xff) << 40) | - (((long)b7 & 0xff) << 48) | - (((long)b8 & 0xff) << 56); + int pos = bufferPos; + + // hand-inlined ensureAvailable(8); + if (bufferSize - pos < 8) { + refillBuffer(8); + pos = bufferPos; + } + + final byte[] buffer = this.buffer; + bufferPos = pos + 8; + return ((((long) buffer[pos] & 0xffL)) | + (((long) buffer[pos + 1] & 0xffL) << 8) | + (((long) buffer[pos + 2] & 0xffL) << 16) | + (((long) buffer[pos + 3] & 0xffL) << 24) | + (((long) buffer[pos + 4] & 0xffL) << 32) | + (((long) buffer[pos + 5] & 0xffL) << 40) | + (((long) buffer[pos + 6] & 0xffL) << 48) | + (((long) buffer[pos + 7] & 0xffL) << 56)); } /** @@ -532,11 +855,13 @@ public final class CodedInputStream { // ----------------------------------------------------------------- private final byte[] buffer; + private final boolean bufferIsImmutable; private int bufferSize; private int bufferSizeAfterLimit; private int bufferPos; private final InputStream input; private int lastTag; + private boolean enableAliasing = false; /** * The total number of bytes read before the current buffer. The total @@ -567,6 +892,7 @@ public final class CodedInputStream { bufferPos = off; totalBytesRetired = -off; input = null; + bufferIsImmutable = false; } private CodedInputStream(final InputStream input) { @@ -575,6 +901,20 @@ public final class CodedInputStream { bufferPos = 0; totalBytesRetired = 0; this.input = input; + bufferIsImmutable = false; + } + + private CodedInputStream(final LiteralByteString byteString) { + buffer = byteString.bytes; + bufferPos = byteString.getOffsetIntoBytes(); + bufferSize = bufferPos + byteString.size(); + totalBytesRetired = -bufferPos; + input = null; + bufferIsImmutable = true; + } + + public void enableAliasing(boolean enabled) { + this.enableAliasing = enabled; } /** @@ -698,7 +1038,7 @@ public final class CodedInputStream { * if the stream has reached a limit created using {@link #pushLimit(int)}. */ public boolean isAtEnd() throws IOException { - return bufferPos == bufferSize && !refillBuffer(false); + return bufferPos == bufferSize && !tryRefillBuffer(1); } /** @@ -709,53 +1049,93 @@ public final class CodedInputStream { return totalBytesRetired + bufferPos; } + private interface RefillCallback { + void onRefill(); + } + + private RefillCallback refillCallback = null; + + /** + * Ensures that at least {@code n} bytes are available in the buffer, reading + * more bytes from the input if necessary to make it so. Caller must ensure + * that the requested space is less than BUFFER_SIZE. + * + * @throws InvalidProtocolBufferException The end of the stream or the current + * limit was reached. + */ + private void ensureAvailable(int n) throws IOException { + if (bufferSize - bufferPos < n) { + refillBuffer(n); + } + } + /** - * Called with {@code this.buffer} is empty to read more bytes from the - * input. If {@code mustSucceed} is true, refillBuffer() guarantees that - * either there will be at least one byte in the buffer when it returns - * or it will throw an exception. If {@code mustSucceed} is false, - * refillBuffer() returns false if no more bytes were available. + * Reads more bytes from the input, making at least {@code n} bytes available + * in the buffer. Caller must ensure that the requested space is not yet + * available, and that the requested space is less than BUFFER_SIZE. + * + * @throws InvalidProtocolBufferException The end of the stream or the current + * limit was reached. */ - private boolean refillBuffer(final boolean mustSucceed) throws IOException { - if (bufferPos < bufferSize) { + private void refillBuffer(int n) throws IOException { + if (!tryRefillBuffer(n)) { + throw InvalidProtocolBufferException.truncatedMessage(); + } + } + + /** + * Tries to read more bytes from the input, making at least {@code n} bytes + * available in the buffer. Caller must ensure that the requested space is + * not yet available, and that the requested space is less than BUFFER_SIZE. + * + * @return {@code true} if the bytes could be made available; {@code false} + * if the end of the stream or the current limit was reached. + */ + private boolean tryRefillBuffer(int n) throws IOException { + if (bufferPos + n <= bufferSize) { throw new IllegalStateException( - "refillBuffer() called when buffer wasn't empty."); + "refillBuffer() called when " + n + + " bytes were already available in buffer"); } - if (totalBytesRetired + bufferSize == currentLimit) { + if (totalBytesRetired + bufferPos + n > currentLimit) { // Oops, we hit a limit. - if (mustSucceed) { - throw InvalidProtocolBufferException.truncatedMessage(); - } else { - return false; - } + return false; } - totalBytesRetired += bufferSize; - - bufferPos = 0; - bufferSize = (input == null) ? -1 : input.read(buffer); - if (bufferSize == 0 || bufferSize < -1) { - throw new IllegalStateException( - "InputStream#read(byte[]) returned invalid result: " + bufferSize + - "\nThe InputStream implementation is buggy."); + if (refillCallback != null) { + refillCallback.onRefill(); } - if (bufferSize == -1) { - bufferSize = 0; - if (mustSucceed) { - throw InvalidProtocolBufferException.truncatedMessage(); - } else { - return false; + + if (input != null) { + int pos = bufferPos; + if (pos > 0) { + if (bufferSize > pos) { + System.arraycopy(buffer, pos, buffer, 0, bufferSize - pos); + } + totalBytesRetired += pos; + bufferSize -= pos; + bufferPos = 0; } - } else { - recomputeBufferSizeAfterLimit(); - final int totalBytesRead = - totalBytesRetired + bufferSize + bufferSizeAfterLimit; - if (totalBytesRead > sizeLimit || totalBytesRead < 0) { - throw InvalidProtocolBufferException.sizeLimitExceeded(); + + int bytesRead = input.read(buffer, bufferSize, buffer.length - bufferSize); + if (bytesRead == 0 || bytesRead < -1 || bytesRead > buffer.length) { + throw new IllegalStateException( + "InputStream#read(byte[]) returned invalid result: " + bytesRead + + "\nThe InputStream implementation is buggy."); + } + if (bytesRead > 0) { + bufferSize += bytesRead; + // Integer-overflow-conscious check against sizeLimit + if (totalBytesRetired + n - sizeLimit > 0) { + throw InvalidProtocolBufferException.sizeLimitExceeded(); + } + recomputeBufferSizeAfterLimit(); + return (bufferSize >= n) ? true : tryRefillBuffer(n); } - return true; } + + return false; } /** @@ -766,7 +1146,7 @@ public final class CodedInputStream { */ public byte readRawByte() throws IOException { if (bufferPos == bufferSize) { - refillBuffer(true); + refillBuffer(1); } return buffer[bufferPos++]; } @@ -778,8 +1158,26 @@ public final class CodedInputStream { * limit was reached. */ public byte[] readRawBytes(final int size) throws IOException { - if (size < 0) { - throw InvalidProtocolBufferException.negativeSize(); + final int pos = bufferPos; + if (size <= (bufferSize - pos) && size > 0) { + bufferPos = pos + size; + return Arrays.copyOfRange(buffer, pos, pos + size); + } else { + return readRawBytesSlowPath(size); + } + } + + /** + * Exactly like readRawBytes, but caller must have already checked the fast + * path: (size <= (bufferSize - pos) && size > 0) + */ + private byte[] readRawBytesSlowPath(final int size) throws IOException { + if (size <= 0) { + if (size == 0) { + return Internal.EMPTY_BYTE_ARRAY; + } else { + throw InvalidProtocolBufferException.negativeSize(); + } } if (totalBytesRetired + bufferPos + size > currentLimit) { @@ -789,13 +1187,7 @@ public final class CodedInputStream { throw InvalidProtocolBufferException.truncatedMessage(); } - if (size <= bufferSize - bufferPos) { - // We have all the bytes we need already. - final byte[] bytes = new byte[size]; - System.arraycopy(buffer, bufferPos, bytes, 0, size); - bufferPos += size; - return bytes; - } else if (size < BUFFER_SIZE) { + if (size < BUFFER_SIZE) { // Reading more bytes than are in the buffer, but not an excessive number // of bytes. We can safely allocate the resulting array ahead of time. @@ -805,18 +1197,10 @@ public final class CodedInputStream { System.arraycopy(buffer, bufferPos, bytes, 0, pos); bufferPos = bufferSize; - // We want to use refillBuffer() and then copy from the buffer into our + // We want to refill the buffer and then copy from the buffer into our // byte array rather than reading directly into our byte array because // the input may be unbuffered. - refillBuffer(true); - - while (size - pos > bufferSize) { - System.arraycopy(buffer, 0, bytes, pos, bufferSize); - pos += bufferSize; - bufferPos = bufferSize; - refillBuffer(true); - } - + ensureAvailable(size - pos); System.arraycopy(buffer, 0, bytes, pos, size - pos); bufferPos = size - pos; @@ -885,6 +1269,19 @@ public final class CodedInputStream { * limit was reached. */ public void skipRawBytes(final int size) throws IOException { + if (size <= (bufferSize - bufferPos) && size >= 0) { + // We have all the bytes we need already. + bufferPos += size; + } else { + skipRawBytesSlowPath(size); + } + } + + /** + * Exactly like skipRawBytes, but caller must have already checked the fast + * path: (size <= (bufferSize - pos) && size >= 0) + */ + private void skipRawBytesSlowPath(final int size) throws IOException { if (size < 0) { throw InvalidProtocolBufferException.negativeSize(); } @@ -896,25 +1293,19 @@ public final class CodedInputStream { throw InvalidProtocolBufferException.truncatedMessage(); } - if (size <= bufferSize - bufferPos) { - // We have all the bytes we need already. - bufferPos += size; - } else { - // Skipping more bytes than are in the buffer. First skip what we have. - int pos = bufferSize - bufferPos; - bufferPos = bufferSize; - - // 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); - } + // Skipping more bytes than are in the buffer. First skip what we have. + int pos = bufferSize - bufferPos; + bufferPos = bufferSize; - bufferPos = size - pos; + // 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(1); + while (size - pos > bufferSize) { + pos += bufferSize; + bufferPos = bufferSize; + refillBuffer(1); } + + 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 ca24638d..0f232071 100644 --- a/java/src/main/java/com/google/protobuf/CodedOutputStream.java +++ b/java/src/main/java/com/google/protobuf/CodedOutputStream.java @@ -31,9 +31,9 @@ package com.google.protobuf; import java.io.IOException; -import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; /** * Encodes and writes protocol message fields. @@ -53,6 +53,7 @@ public final class CodedOutputStream { private final byte[] buffer; private final int limit; private int position; + private int totalBytesWritten = 0; private final OutputStream output; @@ -129,6 +130,38 @@ public final class CodedOutputStream { return new CodedOutputStream(flatArray, offset, length); } + /** + * Create a new {@code CodedOutputStream} that writes to the given ByteBuffer. + */ + public static CodedOutputStream newInstance(ByteBuffer byteBuffer) { + return newInstance(byteBuffer, DEFAULT_BUFFER_SIZE); + } + + /** + * Create a new {@code CodedOutputStream} that writes to the given ByteBuffer. + */ + public static CodedOutputStream newInstance(ByteBuffer byteBuffer, + int bufferSize) { + return newInstance(new ByteBufferOutputStream(byteBuffer), bufferSize); + } + + private static class ByteBufferOutputStream extends OutputStream { + private final ByteBuffer byteBuffer; + public ByteBufferOutputStream(ByteBuffer byteBuffer) { + this.byteBuffer = byteBuffer; + } + + @Override + public void write(int b) throws IOException { + byteBuffer.put((byte) b); + } + + @Override + public void write(byte[] data, int offset, int length) throws IOException { + byteBuffer.put(data, offset, length); + } + } + // ----------------------------------------------------------------- /** Write a {@code double} field, including tag, to the stream. */ @@ -202,6 +235,7 @@ public final class CodedOutputStream { writeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP); } + /** * Write a group represented by an {@link UnknownFieldSet}. * @@ -222,6 +256,7 @@ public final class CodedOutputStream { writeMessageNoTag(value); } + /** Write a {@code bytes} field, including tag, to the stream. */ public void writeBytes(final int fieldNumber, final ByteString value) throws IOException { @@ -229,6 +264,39 @@ public final class CodedOutputStream { writeBytesNoTag(value); } + /** Write a {@code bytes} field, including tag, to the stream. */ + public void writeByteArray(final int fieldNumber, final byte[] value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeByteArrayNoTag(value); + } + + /** Write a {@code bytes} field, including tag, to the stream. */ + public void writeByteArray(final int fieldNumber, + final byte[] value, + final int offset, + final int length) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeByteArrayNoTag(value, offset, length); + } + + /** + * Write a {@code bytes} field, including tag, to the stream. + * This method will write all content of the ByteBuffer regardless of the + * current position and limit (i.e., the number of bytes to be written is + * value.capacity(), not value.remaining()). Furthermore, this method doesn't + * alter the state of the passed-in ByteBuffer. Its position, limit, mark, + * etc. will remain unchanged. If you only want to write the remaining bytes + * of a ByteBuffer, you can call + * {@code writeByteBuffer(fieldNumber, byteBuffer.slice())}. + */ + public void writeByteBuffer(final int fieldNumber, final ByteBuffer value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeByteBufferNoTag(value); + } + /** Write a {@code uint32} field, including tag, to the stream. */ public void writeUInt32(final int fieldNumber, final int value) throws IOException { @@ -362,6 +430,7 @@ public final class CodedOutputStream { value.writeTo(this); } + /** * Write a group represented by an {@link UnknownFieldSet}. * @@ -380,12 +449,41 @@ public final class CodedOutputStream { value.writeTo(this); } + /** Write a {@code bytes} field to the stream. */ public void writeBytesNoTag(final ByteString value) throws IOException { writeRawVarint32(value.size()); writeRawBytes(value); } + /** Write a {@code bytes} field to the stream. */ + public void writeByteArrayNoTag(final byte[] value) throws IOException { + writeRawVarint32(value.length); + writeRawBytes(value); + } + + /** Write a {@code bytes} field to the stream. */ + public void writeByteArrayNoTag(final byte[] value, + final int offset, + final int length) throws IOException { + writeRawVarint32(length); + writeRawBytes(value, offset, length); + } + + /** + * Write a {@code bytes} field to the stream. This method will write all + * content of the ByteBuffer regardless of the current position and limit + * (i.e., the number of bytes to be written is value.capacity(), not + * value.remaining()). Furthermore, this method doesn't alter the state of + * the passed-in ByteBuffer. Its position, limit, mark, etc. will remain + * unchanged. If you only want to write the remaining bytes of a ByteBuffer, + * you can call {@code writeByteBufferNoTag(byteBuffer.slice())}. + */ + public void writeByteBufferNoTag(final ByteBuffer value) throws IOException { + writeRawVarint32(value.capacity()); + writeRawBytes(value); + } + /** Write a {@code uint32} field to the stream. */ public void writeUInt32NoTag(final int value) throws IOException { writeRawVarint32(value); @@ -539,12 +637,30 @@ public final class CodedOutputStream { return computeTagSize(fieldNumber) + computeBytesSizeNoTag(value); } + /** + * Compute the number of bytes that would be needed to encode a + * {@code bytes} field, including tag. + */ + public static int computeByteArraySize(final int fieldNumber, + final byte[] value) { + return computeTagSize(fieldNumber) + computeByteArraySizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code bytes} field, including tag. + */ + public static int computeByteBufferSize(final int fieldNumber, + final ByteBuffer value) { + return computeTagSize(fieldNumber) + computeByteBufferSizeNoTag(value); + } + /** * Compute the number of bytes that would be needed to encode an * embedded message in lazy field, including tag. */ public static int computeLazyFieldSize(final int fieldNumber, - final LazyField value) { + final LazyFieldLite value) { return computeTagSize(fieldNumber) + computeLazyFieldSizeNoTag(value); } @@ -629,7 +745,7 @@ public final class CodedOutputStream { * historical reasons, the wire format differs from normal fields. */ public static int computeLazyFieldMessageSetExtensionSize( - final int fieldNumber, final LazyField value) { + final int fieldNumber, final LazyFieldLite value) { return computeTagSize(WireFormat.MESSAGE_SET_ITEM) * 2 + computeUInt32Size(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber) + computeLazyFieldSize(WireFormat.MESSAGE_SET_MESSAGE, value); @@ -754,7 +870,7 @@ public final class CodedOutputStream { * Compute the number of bytes that would be needed to encode an embedded * message stored in lazy field. */ - public static int computeLazyFieldSizeNoTag(final LazyField value) { + public static int computeLazyFieldSizeNoTag(final LazyFieldLite value) { final int size = value.getSerializedSize(); return computeRawVarint32Size(size) + size; } @@ -768,6 +884,22 @@ public final class CodedOutputStream { value.size(); } + /** + * Compute the number of bytes that would be needed to encode a + * {@code bytes} field. + */ + public static int computeByteArraySizeNoTag(final byte[] value) { + return computeRawVarint32Size(value.length) + value.length; + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code bytes} field. + */ + public static int computeByteBufferSizeNoTag(final ByteBuffer value) { + return computeRawVarint32Size(value.capacity()) + value.capacity(); + } + /** * Compute the number of bytes that would be needed to encode a * {@code uint32} field. @@ -886,6 +1018,15 @@ public final class CodedOutputStream { } } + /** + * Get the total number of bytes successfully written to this stream. The + * returned value is not guaranteed to be accurate if exceptions have been + * found in the middle of writing. + */ + public int getTotalBytesWritten() { + return totalBytesWritten; + } + /** Write a single byte. */ public void writeRawByte(final byte value) throws IOException { if (position == limit) { @@ -893,6 +1034,7 @@ public final class CodedOutputStream { } buffer[position++] = value; + ++totalBytesWritten; } /** Write a single byte, represented by an integer value. */ @@ -910,6 +1052,61 @@ public final class CodedOutputStream { writeRawBytes(value, 0, value.length); } + /** + * Write a ByteBuffer. This method will write all content of the ByteBuffer + * regardless of the current position and limit (i.e., the number of bytes + * to be written is value.capacity(), not value.remaining()). Furthermore, + * this method doesn't alter the state of the passed-in ByteBuffer. Its + * position, limit, mark, etc. will remain unchanged. If you only want to + * write the remaining bytes of a ByteBuffer, you can call + * {@code writeRawBytes(byteBuffer.slice())}. + */ + public void writeRawBytes(final ByteBuffer value) throws IOException { + if (value.hasArray()) { + writeRawBytes(value.array(), value.arrayOffset(), value.capacity()); + } else { + ByteBuffer duplicated = value.duplicate(); + duplicated.clear(); + writeRawBytesInternal(duplicated); + } + } + + /** Write a ByteBuffer that isn't backed by an array. */ + private void writeRawBytesInternal(final ByteBuffer value) + throws IOException { + int length = value.remaining(); + if (limit - position >= length) { + // We have room in the current buffer. + value.get(buffer, position, length); + position += length; + totalBytesWritten += length; + } else { + // Write extends past current buffer. Fill the rest of this buffer and + // flush. + final int bytesWritten = limit - position; + value.get(buffer, position, bytesWritten); + length -= bytesWritten; + position = limit; + totalBytesWritten += bytesWritten; + refreshBuffer(); + + // Now deal with the rest. + // Since we have an output stream, this is our buffer + // and buffer offset == 0 + while (length > limit) { + // Copy data into the buffer before writing it to OutputStream. + // TODO(xiaofeng): Introduce ZeroCopyOutputStream to avoid this copy. + value.get(buffer, 0, limit); + output.write(buffer, 0, limit); + length -= limit; + totalBytesWritten += limit; + } + value.get(buffer, 0, length); + position = length; + totalBytesWritten += length; + } + } + /** Write part of an array of bytes. */ public void writeRawBytes(final byte[] value, int offset, int length) throws IOException { @@ -917,6 +1114,7 @@ public final class CodedOutputStream { // We have room in the current buffer. System.arraycopy(value, offset, buffer, position, length); position += length; + totalBytesWritten += length; } else { // Write extends past current buffer. Fill the rest of this buffer and // flush. @@ -925,6 +1123,7 @@ public final class CodedOutputStream { offset += bytesWritten; length -= bytesWritten; position = limit; + totalBytesWritten += bytesWritten; refreshBuffer(); // Now deal with the rest. @@ -938,6 +1137,7 @@ public final class CodedOutputStream { // Write is very big. Let's do it all at once. output.write(value, offset, length); } + totalBytesWritten += length; } } @@ -948,6 +1148,7 @@ public final class CodedOutputStream { // We have room in the current buffer. value.copyTo(buffer, offset, position, length); position += length; + totalBytesWritten += length; } else { // Write extends past current buffer. Fill the rest of this buffer and // flush. @@ -956,6 +1157,7 @@ public final class CodedOutputStream { offset += bytesWritten; length -= bytesWritten; position = limit; + totalBytesWritten += bytesWritten; refreshBuffer(); // Now deal with the rest. @@ -966,25 +1168,9 @@ public final class CodedOutputStream { 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; - } + value.writeTo(output, offset, length); } + totalBytesWritten += length; } } diff --git a/java/src/main/java/com/google/protobuf/Descriptors.java b/java/src/main/java/com/google/protobuf/Descriptors.java index a4913053..98e52320 100644 --- a/java/src/main/java/com/google/protobuf/Descriptors.java +++ b/java/src/main/java/com/google/protobuf/Descriptors.java @@ -32,6 +32,7 @@ package com.google.protobuf; import com.google.protobuf.DescriptorProtos.*; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -39,6 +40,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.logging.Logger; import java.io.UnsupportedEncodingException; /** @@ -60,19 +62,27 @@ import java.io.UnsupportedEncodingException; * @author kenton@google.com Kenton Varda */ public final class Descriptors { + private static final Logger logger = + Logger.getLogger(Descriptors.class.getName()); /** * 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 { + public static final class FileDescriptor extends GenericDescriptor { /** Convert the descriptor to its protocol message representation. */ public FileDescriptorProto toProto() { return proto; } /** Get the file name. */ public String getName() { return proto.getName(); } + /** Returns this object. */ + public FileDescriptor getFile() { return this; } + + /** Returns the same as getName(). */ + public String getFullName() { return proto.getName(); } + /** * Get the proto package name. This is the package name given by the * {@code package} statement in the {@code .proto} file, which differs @@ -213,8 +223,7 @@ public final class Descriptors { * * @param proto The protocol message form of the FileDescriptor. * @param dependencies {@code FileDescriptor}s corresponding to all of - * the file's dependencies, in the exact order listed - * in {@code proto}. + * the file's dependencies. * @throws DescriptorValidationException {@code proto} is not a valid * descriptor. This can occur for a number of reasons, e.g. * because a field has an undefined type or because two messages @@ -223,6 +232,28 @@ public final class Descriptors { public static FileDescriptor buildFrom(final FileDescriptorProto proto, final FileDescriptor[] dependencies) throws DescriptorValidationException { + return buildFrom(proto, dependencies, false); + } + + + /** + * Construct a {@code FileDescriptor}. + * + * @param proto The protocol message form of the FileDescriptor. + * @param dependencies {@code FileDescriptor}s corresponding to all of + * the file's dependencies. + * @param allowUnknownDependencies If true, non-exist dependenncies will be + * ignored and undefined message types will be replaced with a + * placeholder type. + * @throws DescriptorValidationException {@code proto} is not a valid + * descriptor. This can occur for a number of reasons, e.g. + * because a field has an undefined type or because two messages + * were defined with the same name. + */ + private static FileDescriptor buildFrom( + final FileDescriptorProto proto, final FileDescriptor[] dependencies, + final boolean allowUnknownDependencies) + throws DescriptorValidationException { // Building descriptors involves two steps: translating and linking. // In the translation step (implemented by FileDescriptor's // constructor), we build an object tree mirroring the @@ -232,23 +263,10 @@ public final class Descriptors { // FieldDescriptor for an embedded message contains a pointer directly // to the Descriptor for that message's type. We also detect undefined // types in the linking step. - final DescriptorPool pool = new DescriptorPool(dependencies); - final FileDescriptor result = - new FileDescriptor(proto, dependencies, pool); - - if (dependencies.length != proto.getDependencyCount()) { - throw new DescriptorValidationException(result, - "Dependencies passed to FileDescriptor.buildFrom() don't match " + - "those listed in the FileDescriptorProto."); - } - for (int i = 0; i < proto.getDependencyCount(); i++) { - if (!dependencies[i].getName().equals(proto.getDependency(i))) { - throw new DescriptorValidationException(result, - "Dependencies passed to FileDescriptor.buildFrom() don't match " + - "those listed in the FileDescriptorProto."); - } - } - + final DescriptorPool pool = new DescriptorPool( + dependencies, allowUnknownDependencies); + final FileDescriptor result = new FileDescriptor( + proto, dependencies, pool, allowUnknownDependencies); result.crossLink(); return result; } @@ -296,7 +314,9 @@ public final class Descriptors { final FileDescriptor result; try { - result = buildFrom(proto, dependencies); + // When building descriptors for generated code, we allow unknown + // dependencies by default. + result = buildFrom(proto, dependencies, true); } catch (DescriptorValidationException e) { throw new IllegalArgumentException( "Invalid embedded descriptor for \"" + proto.getName() + "\".", e); @@ -319,6 +339,56 @@ public final class Descriptors { } } + /** + * This method is to be called by generated code only. It uses Java + * reflection to load the dependencies' descriptors. + */ + public static void internalBuildGeneratedFileFrom( + final String[] descriptorDataParts, + final Class descriptorOuterClass, + final String[] dependencies, + final String[] dependencyFileNames, + final InternalDescriptorAssigner descriptorAssigner) { + List descriptors = new ArrayList(); + for (int i = 0; i < dependencies.length; i++) { + try { + Class clazz = + descriptorOuterClass.getClassLoader().loadClass(dependencies[i]); + descriptors.add( + (FileDescriptor) clazz.getField("descriptor").get(null)); + } catch (Exception e) { + // We allow unknown dependencies by default. If a dependency cannot + // be found we only generate a warning. + logger.warning("Descriptors for \"" + dependencyFileNames[i] + + "\" can not be found."); + } + } + FileDescriptor[] descriptorArray = new FileDescriptor[descriptors.size()]; + descriptors.toArray(descriptorArray); + internalBuildGeneratedFileFrom( + descriptorDataParts, descriptorArray, descriptorAssigner); + } + + /** + * This method is to be called by generated code only. It is used to + * update the FileDescriptorProto associated with the descriptor by + * parsing it again with the given ExtensionRegistry. This is needed to + * recognize custom options. + */ + public static void internalUpdateFileDescriptor( + final FileDescriptor descriptor, + final ExtensionRegistry registry) { + ByteString bytes = descriptor.proto.toByteString(); + FileDescriptorProto proto; + try { + proto = FileDescriptorProto.parseFrom(bytes, registry); + } catch (InvalidProtocolBufferException e) { + throw new IllegalArgumentException( + "Failed to parse protocol buffer descriptor for generated code.", e); + } + descriptor.setProto(proto); + } + /** * This class should be used by generated code only. When calling * {@link FileDescriptor#internalBuildGeneratedFileFrom}, the caller @@ -346,22 +416,38 @@ public final class Descriptors { private FileDescriptor(final FileDescriptorProto proto, final FileDescriptor[] dependencies, - final DescriptorPool pool) + final DescriptorPool pool, + boolean allowUnknownDependencies) throws DescriptorValidationException { this.pool = pool; this.proto = proto; this.dependencies = dependencies.clone(); - this.publicDependencies = - new FileDescriptor[proto.getPublicDependencyCount()]; + HashMap nameToFileMap = + new HashMap(); + for (FileDescriptor file : dependencies) { + nameToFileMap.put(file.getName(), file); + } + List publicDependencies = new ArrayList(); for (int i = 0; i < proto.getPublicDependencyCount(); i++) { int index = proto.getPublicDependency(i); - if (index < 0 || index >= this.dependencies.length) { + if (index < 0 || index >= proto.getDependencyCount()) { throw new DescriptorValidationException(this, "Invalid public dependency index."); } - this.publicDependencies[i] = - this.dependencies[proto.getPublicDependency(i)]; + String name = proto.getDependency(index); + FileDescriptor file = nameToFileMap.get(name); + if (file == null) { + if (!allowUnknownDependencies) { + throw new DescriptorValidationException(this, + "Invalid public dependency: " + name); + } + // Ignore unknown dependencies. + } else { + publicDependencies.add(file); + } } + this.publicDependencies = new FileDescriptor[publicDependencies.size()]; + publicDependencies.toArray(this.publicDependencies); pool.addPackage(getPackage(), this); @@ -387,6 +473,27 @@ public final class Descriptors { proto.getExtension(i), this, null, i, true); } } + + /** + * Create a placeholder FileDescriptor for a message Descriptor. + */ + FileDescriptor(String packageName, Descriptor message) + throws DescriptorValidationException { + this.pool = new DescriptorPool(new FileDescriptor[0], true); + this.proto = FileDescriptorProto.newBuilder() + .setName(message.getFullName() + ".placeholder.proto") + .setPackage(packageName).addMessageType(message.toProto()).build(); + this.dependencies = new FileDescriptor[0]; + this.publicDependencies = new FileDescriptor[0]; + + messageTypes = new Descriptor[] {message}; + enumTypes = new EnumDescriptor[0]; + services = new ServiceDescriptor[0]; + extensions = new FieldDescriptor[0]; + + pool.addPackage(packageName, this); + pool.addSymbol(message); + } /** Look up and cross-link all field types, etc. */ private void crossLink() throws DescriptorValidationException { @@ -437,7 +544,7 @@ public final class Descriptors { // ================================================================= /** Describes a message type. */ - public static final class Descriptor implements GenericDescriptor { + public static final class Descriptor extends GenericDescriptor { /** * Get the index of this descriptor within its parent. In other words, * given a {@link FileDescriptor} {@code file}, the following is true: @@ -486,6 +593,11 @@ public final class Descriptors { return Collections.unmodifiableList(Arrays.asList(fields)); } + /** Get a list of this message type's oneofs. */ + public List getOneofs() { + return Collections.unmodifiableList(Arrays.asList(oneofs)); + } + /** Get a list of this message type's extensions. */ public List getExtensions() { return Collections.unmodifiableList(Arrays.asList(extensions)); @@ -512,6 +624,14 @@ public final class Descriptors { return false; } + /** + * Indicates whether the message can be extended. That is, whether it has + * any "extensions x to y" ranges declared on it. + */ + public boolean isExtendable() { + return proto.getExtensionRangeList().size() != 0; + } + /** * Finds a field by name. * @param name The unqualified name of the field (e.g. "foo"). @@ -576,6 +696,33 @@ public final class Descriptors { private final EnumDescriptor[] enumTypes; private final FieldDescriptor[] fields; private final FieldDescriptor[] extensions; + private final OneofDescriptor[] oneofs; + + // Used to create a placeholder when the type cannot be found. + Descriptor(final String fullname) throws DescriptorValidationException { + String name = fullname; + String packageName = ""; + int pos = fullname.lastIndexOf('.'); + if (pos != -1) { + name = fullname.substring(pos + 1); + packageName = fullname.substring(0, pos); + } + this.index = 0; + this.proto = DescriptorProto.newBuilder().setName(name).addExtensionRange( + DescriptorProto.ExtensionRange.newBuilder().setStart(1) + .setEnd(536870912).build()).build(); + this.fullName = fullname; + this.containingType = null; + + this.nestedTypes = new Descriptor[0]; + this.enumTypes = new EnumDescriptor[0]; + this.fields = new FieldDescriptor[0]; + this.extensions = new FieldDescriptor[0]; + this.oneofs = new OneofDescriptor[0]; + + // Create a placeholder FileDescriptor to hold this message. + this.file = new FileDescriptor(packageName, this); + } private Descriptor(final DescriptorProto proto, final FileDescriptor file, @@ -588,6 +735,12 @@ public final class Descriptors { this.file = file; containingType = parent; + oneofs = new OneofDescriptor[proto.getOneofDeclCount()]; + for (int i = 0; i < proto.getOneofDeclCount(); i++) { + oneofs[i] = new OneofDescriptor( + proto.getOneofDecl(i), file, this, i); + } + nestedTypes = new Descriptor[proto.getNestedTypeCount()]; for (int i = 0; i < proto.getNestedTypeCount(); i++) { nestedTypes[i] = new Descriptor( @@ -612,6 +765,17 @@ public final class Descriptors { proto.getExtension(i), file, this, i, true); } + for (int i = 0; i < proto.getOneofDeclCount(); i++) { + oneofs[i].fields = new FieldDescriptor[oneofs[i].getFieldCount()]; + oneofs[i].fieldCount = 0; + } + for (int i = 0; i < proto.getFieldCount(); i++) { + OneofDescriptor oneofDescriptor = fields[i].getContainingOneof(); + if (oneofDescriptor != null) { + oneofDescriptor.fields[oneofDescriptor.fieldCount++] = fields[i]; + } + } + file.pool.addSymbol(this); } @@ -656,7 +820,8 @@ public final class Descriptors { /** Describes a field of a message type. */ public static final class FieldDescriptor - implements GenericDescriptor, Comparable, + extends GenericDescriptor + implements Comparable, FieldSet.FieldDescriptorLite { /** * Get the index of this descriptor within its parent. @@ -700,6 +865,12 @@ public final class Descriptors { public WireFormat.FieldType getLiteType() { return table[type.ordinal()]; } + + /** For internal use only. */ + public boolean needsUtf8Check() { + return (type == Type.STRING) && (getFile().getOptions().getJavaStringCheckUtf8()); + } + // I'm pretty sure values() constructs a new array every time, since there // is nothing stopping the caller from mutating the array. Therefore we // make a static copy here. @@ -761,6 +932,9 @@ public final class Descriptors { */ public Descriptor getContainingType() { return containingType; } + /** Get the field's containing oneof. */ + public OneofDescriptor getContainingOneof() { return containingOneof; } + /** * For extensions defined nested within message types, gets the outer * type. Not valid for non-extension fields. For example, consider @@ -838,6 +1012,7 @@ public final class Descriptors { private Type type; private Descriptor containingType; private Descriptor messageType; + private OneofDescriptor containingOneof; private EnumDescriptor enumType; private Object defaultValue; @@ -946,12 +1121,31 @@ public final class Descriptors { } else { extensionScope = null; } + + if (proto.hasOneofIndex()) { + throw new DescriptorValidationException(this, + "FieldDescriptorProto.oneof_index set for extension field."); + } + containingOneof = null; } else { if (proto.hasExtendee()) { throw new DescriptorValidationException(this, "FieldDescriptorProto.extendee set for non-extension field."); } containingType = parent; + + if (proto.hasOneofIndex()) { + if (proto.getOneofIndex() < 0 || + proto.getOneofIndex() >= parent.toProto().getOneofDeclCount()) { + throw new DescriptorValidationException(this, + "FieldDescriptorProto.oneof_index is out of range for type " + + parent.getName()); + } + containingOneof = parent.getOneofs().get(proto.getOneofIndex()); + containingOneof.fieldCount++; + } else { + containingOneof = null; + } extensionScope = null; } @@ -1161,13 +1355,14 @@ public final class Descriptors { // down-cast and call mergeFrom directly. return ((Message.Builder) to).mergeFrom((Message) from); } + } // ================================================================= /** Describes an enum type. */ - public static final class EnumDescriptor - implements GenericDescriptor, Internal.EnumLiteMap { + public static final class EnumDescriptor extends GenericDescriptor + implements Internal.EnumLiteMap { /** * Get the index of this descriptor within its parent. * @see Descriptors.Descriptor#getIndex() @@ -1278,8 +1473,8 @@ public final class Descriptors { * with the same number after the first become aliases of the first. * However, they still have independent EnumValueDescriptors. */ - public static final class EnumValueDescriptor - implements GenericDescriptor, Internal.EnumLite { + public static final class EnumValueDescriptor extends GenericDescriptor + implements Internal.EnumLite { /** * Get the index of this descriptor within its parent. * @see Descriptors.Descriptor#getIndex() @@ -1294,6 +1489,9 @@ public final class Descriptors { /** Get the value's number. */ public int getNumber() { return proto.getNumber(); } + + @Override + public String toString() { return proto.getName(); } /** * Get the value's fully-qualified name. @@ -1343,7 +1541,7 @@ public final class Descriptors { // ================================================================= /** Describes a service type. */ - public static final class ServiceDescriptor implements GenericDescriptor { + public static final class ServiceDescriptor extends GenericDescriptor { /** * Get the index of this descriptor within its parent. * * @see Descriptors.Descriptor#getIndex() @@ -1433,7 +1631,7 @@ public final class Descriptors { /** * Describes one method within a service type. */ - public static final class MethodDescriptor implements GenericDescriptor { + public static final class MethodDescriptor extends GenericDescriptor { /** * Get the index of this descriptor within its parent. * * @see Descriptors.Descriptor#getIndex() @@ -1537,14 +1735,18 @@ public final class Descriptors { // ================================================================= /** - * All descriptors except {@code FileDescriptor} implement this to make - * {@code DescriptorPool}'s life easier. + * All descriptors implement this to make it easier to implement tools like + * {@code DescriptorPool}.

+ * + * This class is public so that the methods it exposes can be called from + * outside of this package. However, it should only be subclassed from + * nested classes of Descriptors. */ - private interface GenericDescriptor { - Message toProto(); - String getName(); - String getFullName(); - FileDescriptor getFile(); + public abstract static class GenericDescriptor { + public abstract Message toProto(); + public abstract String getName(); + public abstract String getFullName(); + public abstract FileDescriptor getFile(); } /** @@ -1620,8 +1822,10 @@ public final class Descriptors { TYPES_ONLY, AGGREGATES_ONLY, ALL_SYMBOLS } - DescriptorPool(final FileDescriptor[] dependencies) { + DescriptorPool(final FileDescriptor[] dependencies, + boolean allowUnknownDependencies) { this.dependencies = new HashSet(); + this.allowUnknownDependencies = allowUnknownDependencies; for (int i = 0; i < dependencies.length; i++) { this.dependencies.add(dependencies[i]); @@ -1650,6 +1854,7 @@ public final class Descriptors { } private final Set dependencies; + private boolean allowUnknownDependencies; private final Map descriptorsByName = new HashMap(); @@ -1718,9 +1923,11 @@ public final class Descriptors { // TODO(kenton): This could be optimized in a number of ways. GenericDescriptor result; + String fullname; if (name.startsWith(".")) { // Fully-qualified name. - result = findSymbol(name.substring(1), filter); + fullname = name.substring(1); + result = findSymbol(fullname, filter); } else { // If "name" is a compound identifier, we want to search for the // first component of it, then search within it for the rest. @@ -1752,6 +1959,7 @@ public final class Descriptors { // Chop off the last component of the scope. final int dotpos = scopeToTry.lastIndexOf("."); if (dotpos == -1) { + fullname = name; result = findSymbol(name, filter); break; } else { @@ -1771,6 +1979,7 @@ public final class Descriptors { scopeToTry.append(name); result = findSymbol(scopeToTry.toString(), filter); } + fullname = scopeToTry.toString(); break; } @@ -1781,8 +1990,24 @@ public final class Descriptors { } if (result == null) { - throw new DescriptorValidationException(relativeTo, - '\"' + name + "\" is not defined."); + if (allowUnknownDependencies && filter == SearchFilter.TYPES_ONLY) { + logger.warning("The descriptor for message type \"" + name + + "\" can not be found and a placeholder is created for it"); + // We create a dummy message descriptor here regardless of the + // expected type. If the type should be message, this dummy + // descriptor will work well and if the type should be enum, a + // DescriptorValidationException will be thrown latter. In either + // case, the code works as expected: we allow unknown message types + // but not unknwon enum types. + result = new Descriptor(fullname); + // Add the placeholder file as a dependency so we can find the + // placeholder symbol when resolving other references. + this.dependencies.add(result.getFile()); + return result; + } else { + throw new DescriptorValidationException(relativeTo, + '\"' + name + "\" is not defined."); + } } else { return result; } @@ -1826,7 +2051,7 @@ public final class Descriptors { * just as placeholders so that someone cannot define, say, a message type * that has the same name as an existing package. */ - private static final class PackageDescriptor implements GenericDescriptor { + private static final class PackageDescriptor extends GenericDescriptor { public Message toProto() { return file.toProto(); } public String getName() { return name; } public String getFullName() { return fullName; } @@ -1911,7 +2136,7 @@ public final class Descriptors { fieldsByNumber.put(key, old); throw new DescriptorValidationException(field, "Field number " + field.getNumber() + - "has already been used in \"" + + " has already been used in \"" + field.getContainingType().getFullName() + "\" by field \"" + old.getName() + "\"."); } @@ -1967,4 +2192,47 @@ public final class Descriptors { } } } + + /** Describes an oneof of a message type. */ + public static final class OneofDescriptor { + /** Get the index of this descriptor within its parent. */ + public int getIndex() { return index; } + + public String getName() { return proto.getName(); } + + public FileDescriptor getFile() { return file; } + + public String getFullName() { return fullName; } + + public Descriptor getContainingType() { return containingType; } + + public int getFieldCount() { return fieldCount; } + + public FieldDescriptor getField(int index) { + return fields[index]; + } + + private OneofDescriptor(final OneofDescriptorProto proto, + final FileDescriptor file, + final Descriptor parent, + final int index) + throws DescriptorValidationException { + this.proto = proto; + fullName = computeFullName(file, parent, proto.getName()); + this.file = file; + this.index = index; + + containingType = parent; + fieldCount = 0; + } + + private final int index; + private OneofDescriptorProto proto; + private final String fullName; + private final FileDescriptor file; + + private Descriptor containingType; + private int fieldCount; + private FieldDescriptor[] fields; + } } diff --git a/java/src/main/java/com/google/protobuf/DynamicMessage.java b/java/src/main/java/com/google/protobuf/DynamicMessage.java index c0c9fc94..f4aa26cf 100644 --- a/java/src/main/java/com/google/protobuf/DynamicMessage.java +++ b/java/src/main/java/com/google/protobuf/DynamicMessage.java @@ -32,6 +32,7 @@ package com.google.protobuf; import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.FieldDescriptor; +import com.google.protobuf.Descriptors.OneofDescriptor; import java.io.InputStream; import java.io.IOException; @@ -47,16 +48,25 @@ import java.util.Map; public final class DynamicMessage extends AbstractMessage { private final Descriptor type; private final FieldSet fields; + private final FieldDescriptor[] oneofCases; private final UnknownFieldSet unknownFields; private int memoizedSize = -1; /** * Construct a {@code DynamicMessage} using the given {@code FieldSet}. + * oneofCases stores the FieldDescriptor for each oneof to indicate + * which field is set. Caller should make sure the array is immutable. + * + * This contructor is package private and will be used in + * {@code DynamicMutableMessage} to convert a mutable message to an immutable + * message. */ - private DynamicMessage(Descriptor type, FieldSet fields, - UnknownFieldSet unknownFields) { + DynamicMessage(Descriptor type, FieldSet fields, + FieldDescriptor[] oneofCases, + UnknownFieldSet unknownFields) { this.type = type; this.fields = fields; + this.oneofCases = oneofCases; this.unknownFields = unknownFields; } @@ -65,10 +75,14 @@ public final class DynamicMessage extends AbstractMessage { * given type. */ public static DynamicMessage getDefaultInstance(Descriptor type) { + int oneofDeclCount = type.toProto().getOneofDeclCount(); + FieldDescriptor[] oneofCases = new FieldDescriptor[oneofDeclCount]; return new DynamicMessage(type, FieldSet.emptySet(), + oneofCases, UnknownFieldSet.getDefaultInstance()); } + /** Parse a message of the given type from the given input stream. */ public static DynamicMessage parseFrom(Descriptor type, CodedInputStream input) @@ -152,6 +166,20 @@ public final class DynamicMessage extends AbstractMessage { return fields.getAllFields(); } + public boolean hasOneof(OneofDescriptor oneof) { + verifyOneofContainingType(oneof); + FieldDescriptor field = oneofCases[oneof.getIndex()]; + if (field == null) { + return false; + } + return true; + } + + public FieldDescriptor getOneofFieldDescriptor(OneofDescriptor oneof) { + verifyOneofContainingType(oneof); + return oneofCases[oneof.getIndex()]; + } + public boolean hasField(FieldDescriptor field) { verifyContainingType(field); return fields.hasField(field); @@ -186,8 +214,8 @@ public final class DynamicMessage extends AbstractMessage { return unknownFields; } - private static boolean isInitialized(Descriptor type, - FieldSet fields) { + static boolean isInitialized(Descriptor type, + FieldSet fields) { // Check that all required fields are present. for (final FieldDescriptor field : type.getFields()) { if (field.isRequired()) { @@ -270,6 +298,14 @@ public final class DynamicMessage extends AbstractMessage { } } + /** Verifies that the oneof is an oneof of this message. */ + private void verifyOneofContainingType(OneofDescriptor oneof) { + if (oneof.getContainingType() != type) { + throw new IllegalArgumentException( + "OneofDescriptor does not match message type."); + } + } + // ================================================================= /** @@ -278,6 +314,7 @@ public final class DynamicMessage extends AbstractMessage { public static final class Builder extends AbstractMessage.Builder { private final Descriptor type; private FieldSet fields; + private final FieldDescriptor[] oneofCases; private UnknownFieldSet unknownFields; /** Construct a {@code Builder} for the given type. */ @@ -285,6 +322,7 @@ public final class DynamicMessage extends AbstractMessage { this.type = type; this.fields = FieldSet.newFieldSet(); this.unknownFields = UnknownFieldSet.getDefaultInstance(); + this.oneofCases = new FieldDescriptor[type.toProto().getOneofDeclCount()]; } // --------------------------------------------------------------- @@ -313,6 +351,17 @@ public final class DynamicMessage extends AbstractMessage { ensureIsMutable(); fields.mergeFrom(otherDynamicMessage.fields); mergeUnknownFields(otherDynamicMessage.unknownFields); + for (int i = 0; i < oneofCases.length; i++) { + if (oneofCases[i] == null) { + oneofCases[i] = otherDynamicMessage.oneofCases[i]; + } else { + if ((otherDynamicMessage.oneofCases[i] != null) + && (oneofCases[i] != otherDynamicMessage.oneofCases[i])) { + fields.clearField(oneofCases[i]); + oneofCases[i] = otherDynamicMessage.oneofCases[i]; + } + } + } return this; } else { return super.mergeFrom(other); @@ -322,7 +371,8 @@ public final class DynamicMessage extends AbstractMessage { public DynamicMessage build() { if (!isInitialized()) { throw newUninitializedMessageException( - new DynamicMessage(type, fields, unknownFields)); + new DynamicMessage(type, fields, + java.util.Arrays.copyOf(oneofCases, oneofCases.length), unknownFields)); } return buildPartial(); } @@ -335,7 +385,8 @@ public final class DynamicMessage extends AbstractMessage { private DynamicMessage buildParsed() throws InvalidProtocolBufferException { if (!isInitialized()) { throw newUninitializedMessageException( - new DynamicMessage(type, fields, unknownFields)) + new DynamicMessage(type, fields, + java.util.Arrays.copyOf(oneofCases, oneofCases.length), unknownFields)) .asInvalidProtocolBufferException(); } return buildPartial(); @@ -344,7 +395,8 @@ public final class DynamicMessage extends AbstractMessage { public DynamicMessage buildPartial() { fields.makeImmutable(); DynamicMessage result = - new DynamicMessage(type, fields, unknownFields); + new DynamicMessage(type, fields, + java.util.Arrays.copyOf(oneofCases, oneofCases.length), unknownFields); return result; } @@ -353,6 +405,7 @@ public final class DynamicMessage extends AbstractMessage { Builder result = new Builder(type); result.fields.mergeFrom(fields); result.mergeUnknownFields(unknownFields); + System.arraycopy(oneofCases, 0, result.oneofCases, 0 , oneofCases.length); return result; } @@ -383,6 +436,29 @@ public final class DynamicMessage extends AbstractMessage { return new Builder(field.getMessageType()); } + public boolean hasOneof(OneofDescriptor oneof) { + verifyOneofContainingType(oneof); + FieldDescriptor field = oneofCases[oneof.getIndex()]; + if (field == null) { + return false; + } + return true; + } + + public FieldDescriptor getOneofFieldDescriptor(OneofDescriptor oneof) { + verifyOneofContainingType(oneof); + return oneofCases[oneof.getIndex()]; + } + + public Builder clearOneof(OneofDescriptor oneof) { + verifyOneofContainingType(oneof); + FieldDescriptor field = oneofCases[oneof.getIndex()]; + if (field != null) { + clearField(field); + } + return this; + } + public boolean hasField(FieldDescriptor field) { verifyContainingType(field); return fields.hasField(field); @@ -392,7 +468,9 @@ public final class DynamicMessage extends AbstractMessage { verifyContainingType(field); Object result = fields.getField(field); if (result == null) { - if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + if (field.isRepeated()) { + result = Collections.emptyList(); + } else if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { result = getDefaultInstance(field.getMessageType()); } else { result = field.getDefaultValue(); @@ -404,6 +482,15 @@ public final class DynamicMessage extends AbstractMessage { public Builder setField(FieldDescriptor field, Object value) { verifyContainingType(field); ensureIsMutable(); + OneofDescriptor oneofDescriptor = field.getContainingOneof(); + if (oneofDescriptor != null) { + int index = oneofDescriptor.getIndex(); + FieldDescriptor oldField = oneofCases[index]; + if ((oldField != null) && (oldField != field)) { + fields.clearField(oldField); + } + oneofCases[index] = field; + } fields.setField(field, value); return this; } @@ -411,6 +498,13 @@ public final class DynamicMessage extends AbstractMessage { public Builder clearField(FieldDescriptor field) { verifyContainingType(field); ensureIsMutable(); + OneofDescriptor oneofDescriptor = field.getContainingOneof(); + if (oneofDescriptor != null) { + int index = oneofDescriptor.getIndex(); + if (oneofCases[index] == field) { + oneofCases[index] = null; + } + } fields.clearField(field); return this; } @@ -466,6 +560,14 @@ public final class DynamicMessage extends AbstractMessage { } } + /** Verifies that the oneof is an oneof of this message. */ + private void verifyOneofContainingType(OneofDescriptor oneof) { + if (oneof.getContainingType() != type) { + throw new IllegalArgumentException( + "OneofDescriptor does not match message type."); + } + } + private void ensureIsMutable() { if (fields.isImmutable()) { fields = fields.clone(); diff --git a/java/src/main/java/com/google/protobuf/Extension.java b/java/src/main/java/com/google/protobuf/Extension.java new file mode 100644 index 00000000..4364e516 --- /dev/null +++ b/java/src/main/java/com/google/protobuf/Extension.java @@ -0,0 +1,96 @@ +// 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; + +/** + * Interface that generated extensions implement. + * + * @author liujisi@google.com (Jisi Liu) + */ +public abstract class Extension { + /** Returns the field number of the extension. */ + public abstract int getNumber(); + + /** Returns the type of the field. */ + public abstract WireFormat.FieldType getLiteType(); + + /** Returns whether it is a repeated field. */ + public abstract boolean isRepeated(); + + /** Returns the descriptor of the extension. */ + public abstract Descriptors.FieldDescriptor getDescriptor(); + + /** Returns the default value of the extension field. */ + public abstract Type getDefaultValue(); + + /** + * Returns the default instance of the extension field, if it's a message + * extension. + */ + public abstract MessageLite getMessageDefaultInstance(); + + // All the methods below are extension implementation details. + + /** + * The API type that the extension is used for. + */ + protected enum ExtensionType { + IMMUTABLE, + MUTABLE, + PROTO1, + } + + protected ExtensionType getExtensionType() { + // TODO(liujisi): make this abstract after we fix proto1. + return ExtensionType.IMMUTABLE; + } + + /** + * Type of a message extension. + */ + public enum MessageType { + PROTO1, + PROTO2, + } + + /** + * If the extension is a message extension (i.e., getLiteType() == MESSAGE), + * returns the type of the message, otherwise undefined. + */ + public MessageType getMessageType() { + return MessageType.PROTO2; + } + + protected abstract Object fromReflectionType(Object value); + protected abstract Object singularFromReflectionType(Object value); + protected abstract Object toReflectionType(Object value); + protected abstract Object singularToReflectionType(Object value); +} diff --git a/java/src/main/java/com/google/protobuf/ExtensionRegistry.java b/java/src/main/java/com/google/protobuf/ExtensionRegistry.java index d4f6ba9e..2dfef3ca 100644 --- a/java/src/main/java/com/google/protobuf/ExtensionRegistry.java +++ b/java/src/main/java/com/google/protobuf/ExtensionRegistry.java @@ -33,9 +33,12 @@ package com.google.protobuf; import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.FieldDescriptor; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; /** * A table of known extensions, searchable by name or field number. When @@ -90,7 +93,7 @@ import java.util.Map; * * @author kenton@google.com Kenton Varda */ -public final class ExtensionRegistry extends ExtensionRegistryLite { +public class ExtensionRegistry extends ExtensionRegistryLite { /** Construct a new, empty instance. */ public static ExtensionRegistry newInstance() { return new ExtensionRegistry(); @@ -101,6 +104,7 @@ public final class ExtensionRegistry extends ExtensionRegistryLite { return EMPTY; } + /** Returns an unmodifiable view of the registry. */ @Override public ExtensionRegistry getUnmodifiable() { @@ -130,42 +134,127 @@ public final class ExtensionRegistry extends ExtensionRegistryLite { } /** - * Find an extension by fully-qualified field name, in the proto namespace. - * I.e. {@code result.descriptor.fullName()} will match {@code fullName} if - * a match is found. + * Deprecated. Use {@link #findImmutableExtensionByName(String)} instead. + */ + public ExtensionInfo findExtensionByName(final String fullName) { + return findImmutableExtensionByName(fullName); + } + + /** + * Find an extension for immutable APIs by fully-qualified field name, + * in the proto namespace. i.e. {@code result.descriptor.fullName()} will + * match {@code fullName} if a match is found. * * @return Information about the extension if found, or {@code null} * otherwise. */ - public ExtensionInfo findExtensionByName(final String fullName) { - return extensionsByName.get(fullName); + public ExtensionInfo findImmutableExtensionByName(final String fullName) { + return immutableExtensionsByName.get(fullName); + } + + /** + * Find an extension for mutable APIs by fully-qualified field name, + * in the proto namespace. i.e. {@code result.descriptor.fullName()} will + * match {@code fullName} if a match is found. + * + * @return Information about the extension if found, or {@code null} + * otherwise. + */ + public ExtensionInfo findMutableExtensionByName(final String fullName) { + return mutableExtensionsByName.get(fullName); } /** - * Find an extension by containing type and field number. + * Deprecated. Use {@link #findImmutableExtensionByNumber( + * Descriptors.Descriptor, int)} + */ + public ExtensionInfo findExtensionByNumber( + final Descriptor containingType, final int fieldNumber) { + return findImmutableExtensionByNumber(containingType, fieldNumber); + } + + /** + * Find an extension by containing type and field number for immutable APIs. * * @return Information about the extension if found, or {@code null} * otherwise. */ - public ExtensionInfo findExtensionByNumber(final Descriptor containingType, - final int fieldNumber) { - return extensionsByNumber.get( + public ExtensionInfo findImmutableExtensionByNumber( + final Descriptor containingType, final int fieldNumber) { + return immutableExtensionsByNumber.get( new DescriptorIntPair(containingType, fieldNumber)); } + /** + * Find an extension by containing type and field number for mutable APIs. + * + * @return Information about the extension if found, or {@code null} + * otherwise. + */ + public ExtensionInfo findMutableExtensionByNumber( + final Descriptor containingType, final int fieldNumber) { + return mutableExtensionsByNumber.get( + new DescriptorIntPair(containingType, fieldNumber)); + } + + /** + * Find all extensions for mutable APIs by fully-qualified name of + * extended class. Note that this method is more computationally expensive + * than getting a single extension by name or number. + * + * @return Information about the extensions found, or {@code null} if there + * are none. + */ + public Set getAllMutableExtensionsByExtendedType(final String fullName) { + HashSet extensions = new HashSet(); + for (DescriptorIntPair pair : mutableExtensionsByNumber.keySet()) { + if (pair.descriptor.getFullName().equals(fullName)) { + extensions.add(mutableExtensionsByNumber.get(pair)); + } + } + return extensions; + } + + /** + * Find all extensions for immutable APIs by fully-qualified name of + * extended class. Note that this method is more computationally expensive + * than getting a single extension by name or number. + * + * @return Information about the extensions found, or {@code null} if there + * are none. + */ + public Set getAllImmutableExtensionsByExtendedType(final String fullName) { + HashSet extensions = new HashSet(); + for (DescriptorIntPair pair : immutableExtensionsByNumber.keySet()) { + if (pair.descriptor.getFullName().equals(fullName)) { + extensions.add(immutableExtensionsByNumber.get(pair)); + } + } + return extensions; + } + /** Add an extension from a generated file to the registry. */ - public void add(final GeneratedMessage.GeneratedExtension extension) { + public void add(final Extension extension) { + if (extension.getExtensionType() != Extension.ExtensionType.IMMUTABLE && + extension.getExtensionType() != Extension.ExtensionType.MUTABLE) { + // do not support other extension types. ignore + return; + } + add(newExtensionInfo(extension), extension.getExtensionType()); + } + + static ExtensionInfo newExtensionInfo(final Extension extension) { if (extension.getDescriptor().getJavaType() == FieldDescriptor.JavaType.MESSAGE) { if (extension.getMessageDefaultInstance() == null) { throw new IllegalStateException( "Registered message-type extension had null default instance: " + - extension.getDescriptor().getFullName()); + extension.getDescriptor().getFullName()); } - add(new ExtensionInfo(extension.getDescriptor(), - extension.getMessageDefaultInstance())); + return new ExtensionInfo(extension.getDescriptor(), + (Message) extension.getMessageDefaultInstance()); } else { - add(new ExtensionInfo(extension.getDescriptor(), null)); + return new ExtensionInfo(extension.getDescriptor(), null); } } @@ -176,7 +265,9 @@ public final class ExtensionRegistry extends ExtensionRegistryLite { "ExtensionRegistry.add() must be provided a default instance when " + "adding an embedded message extension."); } - add(new ExtensionInfo(type, null)); + ExtensionInfo info = new ExtensionInfo(type, null); + add(info, Extension.ExtensionType.IMMUTABLE); + add(info, Extension.ExtensionType.MUTABLE); } /** Add a message-type extension to the registry by descriptor. */ @@ -186,40 +277,75 @@ public final class ExtensionRegistry extends ExtensionRegistryLite { "ExtensionRegistry.add() provided a default instance for a " + "non-message extension."); } - add(new ExtensionInfo(type, defaultInstance)); + add(new ExtensionInfo(type, defaultInstance), + Extension.ExtensionType.IMMUTABLE); } // ================================================================= // Private stuff. private ExtensionRegistry() { - this.extensionsByName = new HashMap(); - this.extensionsByNumber = new HashMap(); + this.immutableExtensionsByName = new HashMap(); + this.mutableExtensionsByName = new HashMap(); + this.immutableExtensionsByNumber = + new HashMap(); + this.mutableExtensionsByNumber = + new HashMap(); } private ExtensionRegistry(ExtensionRegistry other) { super(other); - this.extensionsByName = Collections.unmodifiableMap(other.extensionsByName); - this.extensionsByNumber = - Collections.unmodifiableMap(other.extensionsByNumber); + this.immutableExtensionsByName = + Collections.unmodifiableMap(other.immutableExtensionsByName); + this.mutableExtensionsByName = + Collections.unmodifiableMap(other.mutableExtensionsByName); + this.immutableExtensionsByNumber = + Collections.unmodifiableMap(other.immutableExtensionsByNumber); + this.mutableExtensionsByNumber = + Collections.unmodifiableMap(other.mutableExtensionsByNumber); } - private final Map extensionsByName; - private final Map extensionsByNumber; + private final Map immutableExtensionsByName; + private final Map mutableExtensionsByName; + private final Map immutableExtensionsByNumber; + private final Map mutableExtensionsByNumber; - private ExtensionRegistry(boolean empty) { + ExtensionRegistry(boolean empty) { super(ExtensionRegistryLite.getEmptyRegistry()); - this.extensionsByName = Collections.emptyMap(); - this.extensionsByNumber = + this.immutableExtensionsByName = + Collections.emptyMap(); + this.mutableExtensionsByName = + Collections.emptyMap(); + this.immutableExtensionsByNumber = Collections.emptyMap(); + this.mutableExtensionsByNumber = + Collections.emptyMap(); } private static final ExtensionRegistry EMPTY = new ExtensionRegistry(true); - private void add(final ExtensionInfo extension) { + private void add( + final ExtensionInfo extension, + final Extension.ExtensionType extensionType) { if (!extension.descriptor.isExtension()) { throw new IllegalArgumentException( - "ExtensionRegistry.add() was given a FieldDescriptor for a regular " + - "(non-extension) field."); + "ExtensionRegistry.add() was given a FieldDescriptor for a regular " + + "(non-extension) field."); + } + + Map extensionsByName; + Map extensionsByNumber; + switch (extensionType) { + case IMMUTABLE: + extensionsByName = immutableExtensionsByName; + extensionsByNumber = immutableExtensionsByNumber; + break; + case MUTABLE: + extensionsByName = mutableExtensionsByName; + extensionsByNumber = mutableExtensionsByNumber; + break; + default: + // Ignore the unknown supported type. + return; } extensionsByName.put(extension.descriptor.getFullName(), extension); diff --git a/java/src/main/java/com/google/protobuf/FieldSet.java b/java/src/main/java/com/google/protobuf/FieldSet.java index 2663694f..01b6a35c 100644 --- a/java/src/main/java/com/google/protobuf/FieldSet.java +++ b/java/src/main/java/com/google/protobuf/FieldSet.java @@ -146,6 +146,7 @@ final class FieldSet entry) { @@ -495,28 +510,26 @@ final class FieldSet builder) { } - public Parser getParserForType() { + public Parser getParserForType() { throw new UnsupportedOperationException( "This is supposed to be overridden by subclasses."); } @@ -153,6 +155,16 @@ public abstract class GeneratedMessage extends AbstractMessage return Collections.unmodifiableMap(getAllFieldsMutable()); } + //@Override (Java 1.6 override semantics, but we must support 1.5) + public boolean hasOneof(final OneofDescriptor oneof) { + return internalGetFieldAccessorTable().getOneof(oneof).has(this); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public FieldDescriptor getOneofFieldDescriptor(final OneofDescriptor oneof) { + return internalGetFieldAccessorTable().getOneof(oneof).get(this); + } + //@Override (Java 1.6 override semantics, but we must support 1.5) public boolean hasField(final FieldDescriptor field) { return internalGetFieldAccessorTable().getField(field).has(this); @@ -193,6 +205,7 @@ public abstract class GeneratedMessage extends AbstractMessage return unknownFields.mergeFieldFrom(tag, input); } + /** * Used by parsing constructors in generated classes. */ @@ -344,6 +357,16 @@ public abstract class GeneratedMessage extends AbstractMessage return internalGetFieldAccessorTable().getField(field).getBuilder(this); } + //@Override (Java 1.6 override semantics, but we must support 1.5) + public boolean hasOneof(final OneofDescriptor oneof) { + return internalGetFieldAccessorTable().getOneof(oneof).has(this); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public FieldDescriptor getOneofFieldDescriptor(final OneofDescriptor oneof) { + return internalGetFieldAccessorTable().getOneof(oneof).get(this); + } + //@Override (Java 1.6 override semantics, but we must support 1.5) public boolean hasField(final FieldDescriptor field) { return internalGetFieldAccessorTable().getField(field).has(this); @@ -373,6 +396,12 @@ public abstract class GeneratedMessage extends AbstractMessage return (BuilderType) this; } + //@Override (Java 1.6 override semantics, but we must support 1.5) + public BuilderType clearOneof(final OneofDescriptor oneof) { + internalGetFieldAccessorTable().getOneof(oneof).clear(this); + return (BuilderType) this; + } + //@Override (Java 1.6 override semantics, but we must support 1.5) public int getRepeatedFieldCount(final FieldDescriptor field) { return internalGetFieldAccessorTable().getField(field) @@ -507,21 +536,24 @@ public abstract class GeneratedMessage extends AbstractMessage public interface ExtendableMessageOrBuilder< MessageType extends ExtendableMessage> extends MessageOrBuilder { + // Re-define for return type covariance. + Message getDefaultInstanceForType(); /** Check if a singular extension is present. */ boolean hasExtension( - GeneratedExtension extension); + Extension extension); /** Get the number of elements in a repeated extension. */ int getExtensionCount( - GeneratedExtension> extension); + Extension> extension); /** Get the value of an extension. */ - Type getExtension(GeneratedExtension extension); + Type getExtension( + Extension extension); /** Get one element of a repeated extension. */ Type getExtension( - GeneratedExtension> extension, + Extension> extension, int index); } @@ -578,7 +610,7 @@ public abstract class GeneratedMessage extends AbstractMessage } private void verifyExtensionContainingType( - final GeneratedExtension extension) { + final Extension extension) { if (extension.getDescriptor().getContainingType() != getDescriptorForType()) { // This can only happen if someone uses unchecked operations. @@ -593,7 +625,7 @@ public abstract class GeneratedMessage extends AbstractMessage /** 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) { + final Extension extension) { verifyExtensionContainingType(extension); return extensions.hasField(extension.getDescriptor()); } @@ -601,7 +633,7 @@ public abstract class GeneratedMessage extends AbstractMessage /** 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) { + final Extension> extension) { verifyExtensionContainingType(extension); final FieldDescriptor descriptor = extension.getDescriptor(); return extensions.getRepeatedFieldCount(descriptor); @@ -611,7 +643,7 @@ public abstract class GeneratedMessage extends AbstractMessage //@Override (Java 1.6 override semantics, but we must support 1.5) @SuppressWarnings("unchecked") public final Type getExtension( - final GeneratedExtension extension) { + final Extension extension) { verifyExtensionContainingType(extension); FieldDescriptor descriptor = extension.getDescriptor(); final Object value = extensions.getField(descriptor); @@ -634,7 +666,7 @@ public abstract class GeneratedMessage extends AbstractMessage //@Override (Java 1.6 override semantics, but we must support 1.5) @SuppressWarnings("unchecked") public final Type getExtension( - final GeneratedExtension> extension, + final Extension> extension, final int index) { verifyExtensionContainingType(extension); FieldDescriptor descriptor = extension.getDescriptor(); @@ -658,11 +690,12 @@ public abstract class GeneratedMessage extends AbstractMessage UnknownFieldSet.Builder unknownFields, ExtensionRegistryLite extensionRegistry, int tag) throws IOException { - return AbstractMessage.Builder.mergeFieldFrom( - input, unknownFields, extensionRegistry, getDescriptorForType(), - null, extensions, tag); + return MessageReflection.mergeFieldFrom( + input, unknownFields, extensionRegistry, getDescriptorForType(), + new MessageReflection.ExtensionAdapter(extensions), tag); } + /** * Used by parsing constructors in generated classes. */ @@ -868,6 +901,11 @@ public abstract class GeneratedMessage extends AbstractMessage super(parent); } + // For immutable message conversion. + void internalSetExtensionSet(FieldSet extensions) { + this.extensions = extensions; + } + @Override public BuilderType clear() { extensions = FieldSet.emptySet(); @@ -890,7 +928,7 @@ public abstract class GeneratedMessage extends AbstractMessage } private void verifyExtensionContainingType( - final GeneratedExtension extension) { + final Extension extension) { if (extension.getDescriptor().getContainingType() != getDescriptorForType()) { // This can only happen if someone uses unchecked operations. @@ -905,7 +943,7 @@ public abstract class GeneratedMessage extends AbstractMessage /** 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) { + final Extension extension) { verifyExtensionContainingType(extension); return extensions.hasField(extension.getDescriptor()); } @@ -913,7 +951,7 @@ public abstract class GeneratedMessage extends AbstractMessage /** 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) { + final Extension> extension) { verifyExtensionContainingType(extension); final FieldDescriptor descriptor = extension.getDescriptor(); return extensions.getRepeatedFieldCount(descriptor); @@ -922,7 +960,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) public final Type getExtension( - final GeneratedExtension extension) { + final Extension extension) { verifyExtensionContainingType(extension); FieldDescriptor descriptor = extension.getDescriptor(); final Object value = extensions.getField(descriptor); @@ -944,7 +982,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) public final Type getExtension( - final GeneratedExtension> extension, + final Extension> extension, final int index) { verifyExtensionContainingType(extension); FieldDescriptor descriptor = extension.getDescriptor(); @@ -954,7 +992,7 @@ public abstract class GeneratedMessage extends AbstractMessage /** Set the value of an extension. */ public final BuilderType setExtension( - final GeneratedExtension extension, + final Extension extension, final Type value) { verifyExtensionContainingType(extension); ensureExtensionsIsMutable(); @@ -966,7 +1004,7 @@ public abstract class GeneratedMessage extends AbstractMessage /** Set the value of one element of a repeated extension. */ public final BuilderType setExtension( - final GeneratedExtension> extension, + final Extension> extension, final int index, final Type value) { verifyExtensionContainingType(extension); ensureExtensionsIsMutable(); @@ -980,7 +1018,7 @@ public abstract class GeneratedMessage extends AbstractMessage /** Append a value to a repeated extension. */ public final BuilderType addExtension( - final GeneratedExtension> extension, + final Extension> extension, final Type value) { verifyExtensionContainingType(extension); ensureExtensionsIsMutable(); @@ -993,7 +1031,7 @@ public abstract class GeneratedMessage extends AbstractMessage /** Clear an extension. */ public final BuilderType clearExtension( - final GeneratedExtension extension) { + final Extension extension) { verifyExtensionContainingType(extension); ensureExtensionsIsMutable(); extensions.clearField(extension.getDescriptor()); @@ -1030,9 +1068,9 @@ public abstract class GeneratedMessage extends AbstractMessage final UnknownFieldSet.Builder unknownFields, final ExtensionRegistryLite extensionRegistry, final int tag) throws IOException { - return AbstractMessage.Builder.mergeFieldFrom( - input, unknownFields, extensionRegistry, getDescriptorForType(), - this, null, tag); + return MessageReflection.mergeFieldFrom( + input, unknownFields, extensionRegistry, getDescriptorForType(), + new MessageReflection.BuilderAdapter(this), tag); } // --------------------------------------------------------------- @@ -1172,7 +1210,7 @@ public abstract class GeneratedMessage extends AbstractMessage * 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 { + static interface ExtensionDescriptorRetriever { FieldDescriptor getDescriptor(); } @@ -1187,15 +1225,16 @@ public abstract class GeneratedMessage extends AbstractMessage // the outer class's descriptor, from which the extension descriptor is // obtained. return new GeneratedExtension( - new ExtensionDescriptorRetriever() { + new CachedDescriptorRetriever() { //@Override (Java 1.6 override semantics, but we must support 1.5) - public FieldDescriptor getDescriptor() { + public FieldDescriptor loadDescriptor() { return scope.getDescriptorForType().getExtensions() .get(descriptorIndex); } }, singularType, - defaultInstance); + defaultInstance, + Extension.ExtensionType.IMMUTABLE); } /** For use by generated code only. */ @@ -1209,7 +1248,87 @@ public abstract class GeneratedMessage extends AbstractMessage return new GeneratedExtension( null, // ExtensionDescriptorRetriever is initialized in internalInit(); singularType, - defaultInstance); + defaultInstance, + Extension.ExtensionType.IMMUTABLE); + } + + private abstract static class CachedDescriptorRetriever + implements ExtensionDescriptorRetriever { + private volatile FieldDescriptor descriptor; + protected abstract FieldDescriptor loadDescriptor(); + + public FieldDescriptor getDescriptor() { + if (descriptor == null) { + synchronized (this) { + if (descriptor == null) { + descriptor = loadDescriptor(); + } + } + } + return descriptor; + } + } + + /** + * Used in proto1 generated code only. + * + * After enabling bridge, we can define proto2 extensions (the extended type + * is a proto2 mutable message) in a proto1 .proto file. For these extensions + * we should generate proto2 GeneratedExtensions. + */ + public static + GeneratedExtension + newMessageScopedGeneratedExtension( + final Message scope, final String name, + 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 CachedDescriptorRetriever() { + protected FieldDescriptor loadDescriptor() { + return scope.getDescriptorForType().findFieldByName(name); + } + }, + singularType, + defaultInstance, + Extension.ExtensionType.MUTABLE); + } + + /** + * Used in proto1 generated code only. + * + * After enabling bridge, we can define proto2 extensions (the extended type + * is a proto2 mutable message) in a proto1 .proto file. For these extensions + * we should generate proto2 GeneratedExtensions. + */ + public static + GeneratedExtension + newFileScopedGeneratedExtension( + final Class singularType, final Message defaultInstance, + final String descriptorOuterClass, final String extensionName) { + // For extensions scoped within a file, we load the descriptor outer + // class and rely on it to get the FileDescriptor which then can be + // used to obtain the extension's FieldDescriptor. + return new GeneratedExtension( + new CachedDescriptorRetriever() { + protected FieldDescriptor loadDescriptor() { + try { + Class clazz = + singularType.getClassLoader().loadClass(descriptorOuterClass); + FileDescriptor file = + (FileDescriptor) clazz.getField("descriptor").get(null); + return file.findExtensionByName(extensionName); + } catch (Exception e) { + throw new RuntimeException( + "Cannot load descriptors: " + descriptorOuterClass + + " is not a valid descriptor class name", e); + } + } + }, + singularType, + defaultInstance, + Extension.ExtensionType.MUTABLE); } /** @@ -1237,8 +1356,9 @@ public abstract class GeneratedMessage extends AbstractMessage * these static singletons as parameters to the extension accessors defined * in {@link ExtendableMessage} and {@link ExtendableBuilder}. */ - public static final class GeneratedExtension< - ContainingType extends Message, Type> { + public static class GeneratedExtension< + ContainingType extends Message, Type> extends + Extension { // TODO(kenton): Find ways to avoid using Java reflection within this // class. Also try to avoid suppressing unchecked warnings. @@ -1254,9 +1374,10 @@ public abstract class GeneratedMessage extends AbstractMessage // 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) { + GeneratedExtension(ExtensionDescriptorRetriever descriptorRetriever, + Class singularType, + Message messageDefaultInstance, + ExtensionType extensionType) { if (Message.class.isAssignableFrom(singularType) && !singularType.isInstance(messageDefaultInstance)) { throw new IllegalArgumentException( @@ -1275,6 +1396,7 @@ public abstract class GeneratedMessage extends AbstractMessage this.enumValueOf = null; this.enumGetValueDescriptor = null; } + this.extensionType = extensionType; } /** For use by generated code only. */ @@ -1295,6 +1417,7 @@ public abstract class GeneratedMessage extends AbstractMessage private final Message messageDefaultInstance; private final Method enumValueOf; private final Method enumGetValueDescriptor; + private final ExtensionType extensionType; public FieldDescriptor getDescriptor() { if (descriptorRetriever == null) { @@ -1312,14 +1435,19 @@ public abstract class GeneratedMessage extends AbstractMessage return messageDefaultInstance; } + protected ExtensionType getExtensionType() { + return extensionType; + } + /** * Convert from the type used by the reflection accessors to the type used * by native accessors. E.g., for enums, the reflection accessors use * EnumValueDescriptors but the native accessors use the generated enum * type. */ + // @Override @SuppressWarnings("unchecked") - private Object fromReflectionType(final Object value) { + protected Object fromReflectionType(final Object value) { FieldDescriptor descriptor = getDescriptor(); if (descriptor.isRepeated()) { if (descriptor.getJavaType() == FieldDescriptor.JavaType.MESSAGE || @@ -1342,21 +1470,16 @@ public abstract class GeneratedMessage extends AbstractMessage * Like {@link #fromReflectionType(Object)}, but if the type is a repeated * type, this converts a single element. */ - private Object singularFromReflectionType(final Object value) { + // @Override + protected Object singularFromReflectionType(final Object value) { FieldDescriptor descriptor = getDescriptor(); switch (descriptor.getJavaType()) { case MESSAGE: if (singularType.isInstance(value)) { return value; } else { - // It seems the copy of the embedded message stored inside the - // extended message is not of the exact type the user was - // expecting. This can happen if a user defines a - // GeneratedExtension manually and gives it a different type. - // This should not happen in normal use. But, to be nice, we'll - // copy the message to whatever type the caller was expecting. return messageDefaultInstance.newBuilderForType() - .mergeFrom((Message) value).build(); + .mergeFrom((Message) value).build(); } case ENUM: return invokeOrDie(enumValueOf, null, (EnumValueDescriptor) value); @@ -1371,8 +1494,9 @@ public abstract class GeneratedMessage extends AbstractMessage * EnumValueDescriptors but the native accessors use the generated enum * type. */ + // @Override @SuppressWarnings("unchecked") - private Object toReflectionType(final Object value) { + protected Object toReflectionType(final Object value) { FieldDescriptor descriptor = getDescriptor(); if (descriptor.isRepeated()) { if (descriptor.getJavaType() == FieldDescriptor.JavaType.ENUM) { @@ -1394,7 +1518,8 @@ public abstract class GeneratedMessage extends AbstractMessage * Like {@link #toReflectionType(Object)}, but if the type is a repeated * type, this converts a single element. */ - private Object singularToReflectionType(final Object value) { + // @Override + protected Object singularToReflectionType(final Object value) { FieldDescriptor descriptor = getDescriptor(); switch (descriptor.getJavaType()) { case ENUM: @@ -1403,6 +1528,34 @@ public abstract class GeneratedMessage extends AbstractMessage return value; } } + + // @Override + public int getNumber() { + return getDescriptor().getNumber(); + } + + // @Override + public WireFormat.FieldType getLiteType() { + return getDescriptor().getLiteType(); + } + + // @Override + public boolean isRepeated() { + return getDescriptor().isRepeated(); + } + + // @Override + @SuppressWarnings("unchecked") + public Type getDefaultValue() { + if (isRepeated()) { + return (Type) Collections.emptyList(); + } + if (getDescriptor().getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + return (Type) messageDefaultInstance; + } + return (Type) singularFromReflectionType( + getDescriptor().getDefaultValue()); + } } // ================================================================= @@ -1477,6 +1630,7 @@ public abstract class GeneratedMessage extends AbstractMessage this.descriptor = descriptor; this.camelCaseNames = camelCaseNames; fields = new FieldAccessor[descriptor.getFields().size()]; + oneofs = new OneofAccessor[descriptor.getOneofs().size()]; initialized = false; } @@ -1493,8 +1647,14 @@ public abstract class GeneratedMessage extends AbstractMessage if (initialized) { return this; } synchronized (this) { if (initialized) { return this; } - for (int i = 0; i < fields.length; i++) { + int fieldsSize = fields.length; + for (int i = 0; i < fieldsSize; i++) { FieldDescriptor field = descriptor.getFields().get(i); + String containingOneofCamelCaseName = null; + if (field.getContainingOneof() != null) { + containingOneofCamelCaseName = + camelCaseNames[fieldsSize + field.getContainingOneof().getIndex()]; + } if (field.isRepeated()) { if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { fields[i] = new RepeatedMessageFieldAccessor( @@ -1509,16 +1669,26 @@ public abstract class GeneratedMessage extends AbstractMessage } else { if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { fields[i] = new SingularMessageFieldAccessor( - field, camelCaseNames[i], messageClass, builderClass); + field, camelCaseNames[i], messageClass, builderClass, + containingOneofCamelCaseName); } else if (field.getJavaType() == FieldDescriptor.JavaType.ENUM) { fields[i] = new SingularEnumFieldAccessor( - field, camelCaseNames[i], messageClass, builderClass); + field, camelCaseNames[i], messageClass, builderClass, + containingOneofCamelCaseName); } else { fields[i] = new SingularFieldAccessor( - field, camelCaseNames[i], messageClass, builderClass); + field, camelCaseNames[i], messageClass, builderClass, + containingOneofCamelCaseName); } } } + + int oneofsSize = oneofs.length; + for (int i = 0; i < oneofsSize; i++) { + oneofs[i] = new OneofAccessor( + descriptor, camelCaseNames[i + fieldsSize], + messageClass, builderClass); + } initialized = true; camelCaseNames = null; return this; @@ -1528,6 +1698,7 @@ public abstract class GeneratedMessage extends AbstractMessage private final Descriptor descriptor; private final FieldAccessor[] fields; private String[] camelCaseNames; + private final OneofAccessor[] oneofs; private volatile boolean initialized; /** Get the FieldAccessor for a particular field. */ @@ -1544,6 +1715,15 @@ public abstract class GeneratedMessage extends AbstractMessage return fields[field.getIndex()]; } + /** Get the OneofAccessor for a particular oneof. */ + private OneofAccessor getOneof(final OneofDescriptor oneof) { + if (oneof.getContainingType() != descriptor) { + throw new IllegalArgumentException( + "OneofDescriptor does not match message type."); + } + return oneofs[oneof.getIndex()]; + } + /** * Abstract interface that provides access to a single field. This is * implemented differently depending on the field type and cardinality. @@ -1566,22 +1746,89 @@ public abstract class GeneratedMessage extends AbstractMessage Message.Builder getBuilder(GeneratedMessage.Builder builder); } + /** OneofAccessor provides access to a single oneof. */ + private static class OneofAccessor { + OneofAccessor( + final Descriptor descriptor, final String camelCaseName, + final Class messageClass, + final Class builderClass) { + this.descriptor = descriptor; + caseMethod = + getMethodOrDie(messageClass, "get" + camelCaseName + "Case"); + caseMethodBuilder = + getMethodOrDie(builderClass, "get" + camelCaseName + "Case"); + clearMethod = getMethodOrDie(builderClass, "clear" + camelCaseName); + } + + private final Descriptor descriptor; + private final Method caseMethod; + private final Method caseMethodBuilder; + private final Method clearMethod; + + public boolean has(final GeneratedMessage message) { + if (((Internal.EnumLite) invokeOrDie(caseMethod, message)).getNumber() == 0) { + return false; + } + return true; + } + + public boolean has(GeneratedMessage.Builder builder) { + if (((Internal.EnumLite) invokeOrDie(caseMethodBuilder, builder)).getNumber() == 0) { + return false; + } + return true; + } + + public FieldDescriptor get(final GeneratedMessage message) { + int fieldNumber = ((Internal.EnumLite) invokeOrDie(caseMethod, message)).getNumber(); + if (fieldNumber > 0) { + return descriptor.findFieldByNumber(fieldNumber); + } + return null; + } + + public FieldDescriptor get(GeneratedMessage.Builder builder) { + int fieldNumber = ((Internal.EnumLite) invokeOrDie(caseMethodBuilder, builder)).getNumber(); + if (fieldNumber > 0) { + return descriptor.findFieldByNumber(fieldNumber); + } + return null; + } + + public void clear(final Builder builder) { + invokeOrDie(clearMethod, builder); + } + } + + private static boolean supportFieldPresence(FileDescriptor file) { + return true; + } + // --------------------------------------------------------------- private static class SingularFieldAccessor implements FieldAccessor { SingularFieldAccessor( final FieldDescriptor descriptor, final String camelCaseName, final Class messageClass, - final Class builderClass) { + final Class builderClass, + final String containingOneofCamelCaseName) { + field = descriptor; + isOneofField = descriptor.getContainingOneof() != null; + hasHasMethod = supportFieldPresence(descriptor.getFile()) + || (!isOneofField && descriptor.getJavaType() == FieldDescriptor.JavaType.MESSAGE); getMethod = getMethodOrDie(messageClass, "get" + camelCaseName); getMethodBuilder = getMethodOrDie(builderClass, "get" + camelCaseName); type = getMethod.getReturnType(); setMethod = getMethodOrDie(builderClass, "set" + camelCaseName, type); hasMethod = - getMethodOrDie(messageClass, "has" + camelCaseName); + hasHasMethod ? getMethodOrDie(messageClass, "has" + camelCaseName) : null; hasMethodBuilder = - getMethodOrDie(builderClass, "has" + camelCaseName); + hasHasMethod ? getMethodOrDie(builderClass, "has" + camelCaseName) : null; clearMethod = getMethodOrDie(builderClass, "clear" + camelCaseName); + caseMethod = isOneofField ? getMethodOrDie( + messageClass, "get" + containingOneofCamelCaseName + "Case") : null; + caseMethodBuilder = isOneofField ? getMethodOrDie( + builderClass, "get" + containingOneofCamelCaseName + "Case") : null; } // Note: We use Java reflection to call public methods rather than @@ -1594,6 +1841,19 @@ public abstract class GeneratedMessage extends AbstractMessage protected final Method hasMethod; protected final Method hasMethodBuilder; protected final Method clearMethod; + protected final Method caseMethod; + protected final Method caseMethodBuilder; + protected final FieldDescriptor field; + protected final boolean isOneofField; + protected final boolean hasHasMethod; + + private int getOneofFieldNumber(final GeneratedMessage message) { + return ((Internal.EnumLite) invokeOrDie(caseMethod, message)).getNumber(); + } + + private int getOneofFieldNumber(final GeneratedMessage.Builder builder) { + return ((Internal.EnumLite) invokeOrDie(caseMethodBuilder, builder)).getNumber(); + } public Object get(final GeneratedMessage message) { return invokeOrDie(getMethod, message); @@ -1623,9 +1883,21 @@ public abstract class GeneratedMessage extends AbstractMessage "addRepeatedField() called on a singular field."); } public boolean has(final GeneratedMessage message) { + if (!hasHasMethod) { + if (isOneofField) { + return getOneofFieldNumber(message) == field.getNumber(); + } + return !get(message).equals(field.getDefaultValue()); + } return (Boolean) invokeOrDie(hasMethod, message); } public boolean has(GeneratedMessage.Builder builder) { + if (!hasHasMethod) { + if (isOneofField) { + return getOneofFieldNumber(builder) == field.getNumber(); + } + return !get(builder).equals(field.getDefaultValue()); + } return (Boolean) invokeOrDie(hasMethodBuilder, builder); } public int getRepeatedCount(final GeneratedMessage message) { @@ -1751,8 +2023,9 @@ public abstract class GeneratedMessage extends AbstractMessage SingularEnumFieldAccessor( final FieldDescriptor descriptor, final String camelCaseName, final Class messageClass, - final Class builderClass) { - super(descriptor, camelCaseName, messageClass, builderClass); + final Class builderClass, + final String containingOneofCamelCaseName) { + super(descriptor, camelCaseName, messageClass, builderClass, containingOneofCamelCaseName); valueOfMethod = getMethodOrDie(type, "valueOf", EnumValueDescriptor.class); @@ -1847,8 +2120,9 @@ public abstract class GeneratedMessage extends AbstractMessage SingularMessageFieldAccessor( final FieldDescriptor descriptor, final String camelCaseName, final Class messageClass, - final Class builderClass) { - super(descriptor, camelCaseName, messageClass, builderClass); + final Class builderClass, + final String containingOneofCamelCaseName) { + super(descriptor, camelCaseName, messageClass, builderClass, containingOneofCamelCaseName); newBuilderMethod = getMethodOrDie(type, "newBuilder"); getBuilderMethodBuilder = diff --git a/java/src/main/java/com/google/protobuf/GeneratedMessageLite.java b/java/src/main/java/com/google/protobuf/GeneratedMessageLite.java index 437e3412..8c70505f 100644 --- a/java/src/main/java/com/google/protobuf/GeneratedMessageLite.java +++ b/java/src/main/java/com/google/protobuf/GeneratedMessageLite.java @@ -35,6 +35,7 @@ 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; import java.util.List; @@ -66,9 +67,10 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite */ protected boolean parseUnknownField( CodedInputStream input, + CodedOutputStream unknownFieldsCodedOutput, ExtensionRegistryLite extensionRegistry, int tag) throws IOException { - return input.skipField(tag); + return input.skipField(tag, unknownFieldsCodedOutput); } /** @@ -86,6 +88,7 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite //@Override (Java 1.6 override semantics, but we must support 1.5) public BuilderType clear() { + unknownFields = ByteString.EMPTY; return (BuilderType) this; } @@ -110,12 +113,25 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite */ protected boolean parseUnknownField( CodedInputStream input, + CodedOutputStream unknownFieldsCodedOutput, ExtensionRegistryLite extensionRegistry, int tag) throws IOException { - return input.skipField(tag); + return input.skipField(tag, unknownFieldsCodedOutput); } + + public final ByteString getUnknownFields() { + return unknownFields; + } + + public final BuilderType setUnknownFields(final ByteString unknownFields) { + this.unknownFields = unknownFields; + return (BuilderType) this; + } + + private ByteString unknownFields = ByteString.EMPTY; } + // ================================================================= // Extensions-related stuff @@ -197,7 +213,7 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite if (value == null) { return extension.defaultValue; } else { - return (Type) value; + return (Type) extension.fromFieldSetType(value); } } @@ -208,7 +224,8 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite final GeneratedExtension> extension, final int index) { verifyExtensionContainingType(extension); - return (Type) extensions.getRepeatedField(extension.descriptor, index); + return (Type) extension.singularFromFieldSetType( + extensions.getRepeatedField(extension.descriptor, index)); } /** Called by subclasses to check if all extensions are initialized. */ @@ -223,16 +240,19 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite @Override protected boolean parseUnknownField( CodedInputStream input, + CodedOutputStream unknownFieldsCodedOutput, ExtensionRegistryLite extensionRegistry, int tag) throws IOException { return GeneratedMessageLite.parseUnknownField( extensions, getDefaultInstanceForType(), input, + unknownFieldsCodedOutput, extensionRegistry, tag); } + /** * Used by parsing constructors in generated classes. */ @@ -314,6 +334,11 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite private FieldSet extensions = FieldSet.emptySet(); private boolean extensionsIsMutable; + // For immutable message conversion. + void internalSetExtensionSet(FieldSet extensions) { + this.extensions = extensions; + } + @Override public BuilderType clear() { extensions.clear(); @@ -375,7 +400,7 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite if (value == null) { return extension.defaultValue; } else { - return (Type) value; + return (Type) extension.fromFieldSetType(value); } } @@ -386,7 +411,8 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite final GeneratedExtension> extension, final int index) { verifyExtensionContainingType(extension); - return (Type) extensions.getRepeatedField(extension.descriptor, index); + return (Type) extension.singularFromFieldSetType( + extensions.getRepeatedField(extension.descriptor, index)); } // This is implemented here only to work around an apparent bug in the @@ -404,7 +430,8 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite final Type value) { verifyExtensionContainingType(extension); ensureExtensionsIsMutable(); - extensions.setField(extension.descriptor, value); + extensions.setField(extension.descriptor, + extension.toFieldSetType(value)); return (BuilderType) this; } @@ -414,7 +441,8 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite final int index, final Type value) { verifyExtensionContainingType(extension); ensureExtensionsIsMutable(); - extensions.setRepeatedField(extension.descriptor, index, value); + extensions.setRepeatedField(extension.descriptor, index, + extension.singularToFieldSetType(value)); return (BuilderType) this; } @@ -424,7 +452,8 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite final Type value) { verifyExtensionContainingType(extension); ensureExtensionsIsMutable(); - extensions.addRepeatedField(extension.descriptor, value); + extensions.addRepeatedField(extension.descriptor, + extension.singularToFieldSetType(value)); return (BuilderType) this; } @@ -449,6 +478,7 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite @Override protected boolean parseUnknownField( CodedInputStream input, + CodedOutputStream unknownFieldsCodedOutput, ExtensionRegistryLite extensionRegistry, int tag) throws IOException { ensureExtensionsIsMutable(); @@ -456,6 +486,7 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite extensions, getDefaultInstanceForType(), input, + unknownFieldsCodedOutput, extensionRegistry, tag); } @@ -477,6 +508,7 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite FieldSet extensions, MessageType defaultInstance, CodedInputStream input, + CodedOutputStream unknownFieldsCodedOutput, ExtensionRegistryLite extensionRegistry, int tag) throws IOException { int wireType = WireFormat.getTagWireType(tag); @@ -505,7 +537,7 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite } if (unknown) { // Unknown field or wrong wire type. Skip. - return input.skipField(tag); + return input.skipField(tag, unknownFieldsCodedOutput); } if (packed) { @@ -521,13 +553,15 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite // enum, drop it (don't even add it to unknownFields). return true; } - extensions.addRepeatedField(extension.descriptor, value); + extensions.addRepeatedField(extension.descriptor, + extension.singularToFieldSetType(value)); } } else { while (input.getBytesUntilLimit() > 0) { Object value = - FieldSet.readPrimitiveField(input, - extension.descriptor.getLiteType()); + FieldSet.readPrimitiveField(input, + extension.descriptor.getLiteType(), + /*checkUtf8=*/ false); extensions.addRepeatedField(extension.descriptor, value); } } @@ -545,7 +579,8 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite } } if (subBuilder == null) { - subBuilder = extension.messageDefaultInstance.newBuilderForType(); + subBuilder = extension.getMessageDefaultInstance() + .newBuilderForType(); } if (extension.descriptor.getLiteType() == WireFormat.FieldType.GROUP) { @@ -562,21 +597,26 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite value = extension.descriptor.getEnumType() .findValueByNumber(rawValue); // If the number isn't recognized as a valid value for this enum, - // drop it. + // write it to unknown fields object. if (value == null) { + unknownFieldsCodedOutput.writeRawVarint32(tag); + unknownFieldsCodedOutput.writeUInt32NoTag(rawValue); return true; } break; default: value = FieldSet.readPrimitiveField(input, - extension.descriptor.getLiteType()); + extension.descriptor.getLiteType(), + /*checkUtf8=*/ false); break; } if (extension.descriptor.isRepeated()) { - extensions.addRepeatedField(extension.descriptor, value); + extensions.addRepeatedField(extension.descriptor, + extension.singularToFieldSetType(value)); } else { - extensions.setField(extension.descriptor, value); + extensions.setField(extension.descriptor, + extension.singularToFieldSetType(value)); } } @@ -594,14 +634,16 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite final MessageLite messageDefaultInstance, final Internal.EnumLiteMap enumTypeMap, final int number, - final WireFormat.FieldType type) { + final WireFormat.FieldType type, + final Class singularType) { return new GeneratedExtension( containingTypeDefaultInstance, defaultValue, messageDefaultInstance, new ExtensionDescriptor(enumTypeMap, number, type, false /* isRepeated */, - false /* isPacked */)); + false /* isPacked */), + singularType); } /** For use by generated code only. */ @@ -613,7 +655,8 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite final Internal.EnumLiteMap enumTypeMap, final int number, final WireFormat.FieldType type, - final boolean isPacked) { + final boolean isPacked, + final Class singularType) { @SuppressWarnings("unchecked") // Subclasses ensure Type is a List Type emptyList = (Type) Collections.emptyList(); return new GeneratedExtension( @@ -621,13 +664,14 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite emptyList, messageDefaultInstance, new ExtensionDescriptor( - enumTypeMap, number, type, true /* isRepeated */, isPacked)); + enumTypeMap, number, type, true /* isRepeated */, isPacked), + singularType); } - private static final class ExtensionDescriptor + static final class ExtensionDescriptor implements FieldSet.FieldDescriptorLite< ExtensionDescriptor> { - private ExtensionDescriptor( + ExtensionDescriptor( final Internal.EnumLiteMap enumTypeMap, final int number, final WireFormat.FieldType type, @@ -640,11 +684,11 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite this.isPacked = isPacked; } - private final Internal.EnumLiteMap enumTypeMap; - private final int number; - private final WireFormat.FieldType type; - private final boolean isRepeated; - private final boolean isPacked; + final Internal.EnumLiteMap enumTypeMap; + final int number; + final WireFormat.FieldType type; + final boolean isRepeated; + final boolean isPacked; public int getNumber() { return number; @@ -676,25 +720,70 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite return ((Builder) to).mergeFrom((GeneratedMessageLite) from); } + public int compareTo(ExtensionDescriptor other) { return number - other.number; } } + // ================================================================= + + /** Calls Class.getMethod and throws a RuntimeException if it fails. */ + @SuppressWarnings("unchecked") + static Method getMethodOrDie(Class clazz, String name, Class... params) { + try { + return clazz.getMethod(name, params); + } catch (NoSuchMethodException e) { + throw new RuntimeException( + "Generated message class \"" + clazz.getName() + + "\" missing method \"" + name + "\".", e); + } + } + + /** Calls invoke and throws a RuntimeException if it fails. */ + static Object invokeOrDie(Method method, Object object, Object... params) { + try { + return method.invoke(object, params); + } catch (IllegalAccessException e) { + throw new RuntimeException( + "Couldn't use Java reflection to implement protocol message " + + "reflection.", e); + } catch (InvocationTargetException e) { + final Throwable cause = e.getCause(); + if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } else if (cause instanceof Error) { + throw (Error) cause; + } else { + throw new RuntimeException( + "Unexpected exception thrown by generated accessor method.", cause); + } + } + } + /** * Lite equivalent to {@link GeneratedMessage.GeneratedExtension}. * * Users should ignore the contents of this class and only use objects of * this type as parameters to extension accessors and ExtensionRegistry.add(). */ - public static final class GeneratedExtension< + public static class GeneratedExtension< ContainingType extends MessageLite, Type> { - private GeneratedExtension( + /** + * Create a new isntance with the given parameters. + * + * The last parameter {@code singularType} is only needed for enum types. + * We store integer values for enum types in a {@link ExtendableMessage} + * and use Java reflection to convert an integer value back into a concrete + * enum object. + */ + GeneratedExtension( final ContainingType containingTypeDefaultInstance, final Type defaultValue, final MessageLite messageDefaultInstance, - final ExtensionDescriptor descriptor) { + final ExtensionDescriptor descriptor, + Class singularType) { // Defensive checks to verify the correct initialization order of // GeneratedExtensions and their related GeneratedMessages. if (containingTypeDefaultInstance == null) { @@ -710,12 +799,24 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite this.defaultValue = defaultValue; this.messageDefaultInstance = messageDefaultInstance; this.descriptor = descriptor; + + // Use Java reflection to invoke the static method {@code valueOf} of + // enum types in order to convert integers to concrete enum objects. + this.singularType = singularType; + if (Internal.EnumLite.class.isAssignableFrom(singularType)) { + this.enumValueOf = getMethodOrDie( + singularType, "valueOf", int.class); + } else { + this.enumValueOf = null; + } } - private final ContainingType containingTypeDefaultInstance; - private final Type defaultValue; - private final MessageLite messageDefaultInstance; - private final ExtensionDescriptor descriptor; + final ContainingType containingTypeDefaultInstance; + final Type defaultValue; + final MessageLite messageDefaultInstance; + final ExtensionDescriptor descriptor; + final Class singularType; + final Method enumValueOf; /** * Default instance of the type being extended, used to identify that type. @@ -729,13 +830,64 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite return descriptor.getNumber(); } + /** - * If the extension is an embedded message, this is the default instance of - * that type. + * If the extension is an embedded message or group, returns the default + * instance of the message. */ public MessageLite getMessageDefaultInstance() { return messageDefaultInstance; } + + @SuppressWarnings("unchecked") + Object fromFieldSetType(final Object value) { + if (descriptor.isRepeated()) { + if (descriptor.getLiteJavaType() == WireFormat.JavaType.ENUM) { + final List result = new ArrayList(); + for (final Object element : (List) value) { + result.add(singularFromFieldSetType(element)); + } + return result; + } else { + return value; + } + } else { + return singularFromFieldSetType(value); + } + } + + Object singularFromFieldSetType(final Object value) { + if (descriptor.getLiteJavaType() == WireFormat.JavaType.ENUM) { + return invokeOrDie(enumValueOf, null, (Integer) value); + } else { + return value; + } + } + + @SuppressWarnings("unchecked") + Object toFieldSetType(final Object value) { + if (descriptor.isRepeated()) { + if (descriptor.getLiteJavaType() == WireFormat.JavaType.ENUM) { + final List result = new ArrayList(); + for (final Object element : (List) value) { + result.add(singularToFieldSetType(element)); + } + return result; + } else { + return value; + } + } else { + return singularToFieldSetType(value); + } + } + + Object singularToFieldSetType(final Object value) { + if (descriptor.getLiteJavaType() == WireFormat.JavaType.ENUM) { + return ((Internal.EnumLite) value).getNumber(); + } else { + return value; + } + } } /** diff --git a/java/src/main/java/com/google/protobuf/Internal.java b/java/src/main/java/com/google/protobuf/Internal.java index 81af2583..5c234c54 100644 --- a/java/src/main/java/com/google/protobuf/Internal.java +++ b/java/src/main/java/com/google/protobuf/Internal.java @@ -30,7 +30,11 @@ package com.google.protobuf; +import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.List; /** * The classes contained within are used internally by the Protocol Buffer @@ -98,6 +102,51 @@ public class Internal { "Java VM does not support a standard character set.", e); } } + /** + * Helper called by generated code to construct default values for bytes + * fields. + *

+ * This is like {@link #bytesDefaultValue}, but returns a byte array. + */ + public static byte[] byteArrayDefaultValue(String bytes) { + try { + return bytes.getBytes("ISO-8859-1"); + } catch (UnsupportedEncodingException e) { + // This should never happen since all JVMs are required to implement + // ISO-8859-1. + throw new IllegalStateException( + "Java VM does not support a standard character set.", e); + } + } + + /** + * Helper called by generated code to construct default values for bytes + * fields. + *

+ * This is like {@link #bytesDefaultValue}, but returns a ByteBuffer. + */ + public static ByteBuffer byteBufferDefaultValue(String bytes) { + return ByteBuffer.wrap(byteArrayDefaultValue(bytes)); + } + + /** + * Create a new ByteBuffer and copy all the content of {@code source} + * ByteBuffer to the new ByteBuffer. The new ByteBuffer's limit and + * capacity will be source.capacity(), and its position will be 0. + * Note that the state of {@code source} ByteBuffer won't be changed. + */ + public static ByteBuffer copyByteBuffer(ByteBuffer source) { + // Make a duplicate of the source ByteBuffer and read data from the + // duplicate. This is to avoid affecting the source ByteBuffer's state. + ByteBuffer temp = source.duplicate(); + // We want to copy all the data in the source ByteBuffer, not just the + // remaining bytes. + temp.clear(); + ByteBuffer result = ByteBuffer.allocate(temp.capacity()); + result.put(temp); + result.clear(); + return result; + } /** * Helper called by generated code to determine if a byte array is a valid @@ -130,6 +179,35 @@ public class Internal { public static boolean isValidUtf8(ByteString byteString) { return byteString.isValidUtf8(); } + + /** + * Like {@link #isValidUtf8(ByteString)} but for byte arrays. + */ + public static boolean isValidUtf8(byte[] byteArray) { + return Utf8.isValidUtf8(byteArray); + } + + /** + * Helper method to get the UTF-8 bytes of a string. + */ + public static byte[] toByteArray(String value) { + try { + return value.getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("UTF-8 not supported?", e); + } + } + + /** + * Helper method to convert a byte array to a string using UTF-8 encoding. + */ + public static String toStringUtf8(byte[] bytes) { + try { + return new String(bytes, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("UTF-8 not supported?", e); + } + } /** * Interface for an enum value or value descriptor, to be used in FieldSet. @@ -150,4 +228,164 @@ public class Internal { public interface EnumLiteMap { T findValueByNumber(int number); } + + /** + * Helper method for implementing {@link MessageLite#hashCode()} for longs. + * @see Long#hashCode() + */ + public static int hashLong(long n) { + return (int) (n ^ (n >>> 32)); + } + + /** + * Helper method for implementing {@link MessageLite#hashCode()} for + * booleans. + * @see Boolean#hashCode() + */ + public static int hashBoolean(boolean b) { + return b ? 1231 : 1237; + } + + /** + * Helper method for implementing {@link MessageLite#hashCode()} for enums. + *

+ * 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. + */ + public static int hashEnum(EnumLite e) { + return e.getNumber(); + } + + /** + * Helper method for implementing {@link MessageLite#hashCode()} for + * enum lists. + */ + public static int hashEnumList(List list) { + int hash = 1; + for (EnumLite e : list) { + hash = 31 * hash + hashEnum(e); + } + return hash; + } + + /** + * Helper method for implementing {@link MessageLite#equals()} for bytes field. + */ + public static boolean equals(List a, List b) { + if (a.size() != b.size()) return false; + for (int i = 0; i < a.size(); ++i) { + if (!Arrays.equals(a.get(i), b.get(i))) { + return false; + } + } + return true; + } + + /** + * Helper method for implementing {@link MessageLite#hashCode()} for bytes field. + */ + public static int hashCode(List list) { + int hash = 1; + for (byte[] bytes : list) { + hash = 31 * hash + hashCode(bytes); + } + return hash; + } + + /** + * Helper method for implementing {@link MessageLite#hashCode()} for bytes field. + */ + public static int hashCode(byte[] bytes) { + // The hash code for a byte array should be the same as the hash code for a + // ByteString with the same content. This is to ensure that the generated + // hashCode() method will return the same value as the pure reflection + // based hashCode() method. + return LiteralByteString.hashCode(bytes); + } + + /** + * Helper method for implementing {@link MessageLite#equals()} for bytes + * field. + */ + public static boolean equalsByteBuffer(ByteBuffer a, ByteBuffer b) { + if (a.capacity() != b.capacity()) { + return false; + } + // ByteBuffer.equals() will only compare the remaining bytes, but we want to + // compare all the content. + return a.duplicate().clear().equals(b.duplicate().clear()); + } + + /** + * Helper method for implementing {@link MessageLite#equals()} for bytes + * field. + */ + public static boolean equalsByteBuffer( + List a, List b) { + if (a.size() != b.size()) { + return false; + } + for (int i = 0; i < a.size(); ++i) { + if (!equalsByteBuffer(a.get(i), b.get(i))) { + return false; + } + } + return true; + } + + /** + * Helper method for implementing {@link MessageLite#hashCode()} for bytes + * field. + */ + public static int hashCodeByteBuffer(List list) { + int hash = 1; + for (ByteBuffer bytes : list) { + hash = 31 * hash + hashCodeByteBuffer(bytes); + } + return hash; + } + + private static final int DEFAULT_BUFFER_SIZE = 4096; + + /** + * Helper method for implementing {@link MessageLite#hashCode()} for bytes + * field. + */ + public static int hashCodeByteBuffer(ByteBuffer bytes) { + if (bytes.hasArray()) { + // Fast path. + int h = LiteralByteString.hashCode(bytes.capacity(), bytes.array(), + bytes.arrayOffset(), bytes.capacity()); + return h == 0 ? 1 : h; + } else { + // Read the data into a temporary byte array before calculating the + // hash value. + final int bufferSize = bytes.capacity() > DEFAULT_BUFFER_SIZE + ? DEFAULT_BUFFER_SIZE : bytes.capacity(); + final byte[] buffer = new byte[bufferSize]; + final ByteBuffer duplicated = bytes.duplicate(); + duplicated.clear(); + int h = bytes.capacity(); + while (duplicated.remaining() > 0) { + final int length = duplicated.remaining() <= bufferSize ? + duplicated.remaining() : bufferSize; + duplicated.get(buffer, 0, length); + h = LiteralByteString.hashCode(h, buffer, 0, length); + } + return h == 0 ? 1 : h; + } + } + + /** + * An empty byte array constant used in generated code. + */ + public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + + /** + * An empty byte array constant used in generated code. + */ + public static final ByteBuffer EMPTY_BYTE_BUFFER = + ByteBuffer.wrap(EMPTY_BYTE_ARRAY); + } diff --git a/java/src/main/java/com/google/protobuf/InvalidProtocolBufferException.java b/java/src/main/java/com/google/protobuf/InvalidProtocolBufferException.java index 72d7ff7d..ae320beb 100644 --- a/java/src/main/java/com/google/protobuf/InvalidProtocolBufferException.java +++ b/java/src/main/java/com/google/protobuf/InvalidProtocolBufferException.java @@ -111,4 +111,12 @@ public class InvalidProtocolBufferException extends IOException { "Protocol message was too large. May be malicious. " + "Use CodedInputStream.setSizeLimit() to increase the size limit."); } + + static InvalidProtocolBufferException parseFailure() { + return new InvalidProtocolBufferException("Failed to parse the message."); + } + + static InvalidProtocolBufferException invalidUtf8() { + return new InvalidProtocolBufferException("Protocol message had invalid UTF-8."); + } } diff --git a/java/src/main/java/com/google/protobuf/LazyField.java b/java/src/main/java/com/google/protobuf/LazyField.java index c4f9201c..0cb65d6b 100644 --- a/java/src/main/java/com/google/protobuf/LazyField.java +++ b/java/src/main/java/com/google/protobuf/LazyField.java @@ -30,7 +30,6 @@ package com.google.protobuf; -import java.io.IOException; import java.util.Iterator; import java.util.Map.Entry; @@ -38,110 +37,49 @@ import java.util.Map.Entry; * LazyField encapsulates the logic of lazily parsing message fields. It stores * the message in a ByteString initially and then parse it on-demand. * - * LazyField is thread-compatible e.g. concurrent read are safe, however, - * synchronizations are needed under read/write situations. - * - * Now LazyField is only used to lazily load MessageSet. - * TODO(xiangl): Use LazyField to lazily load all messages. + * Most of key methods are implemented in {@link LazyFieldLite} but this class + * can contain default instance of the message to provide {@code hashCode()}, + * {@code euqals()} and {@code toString()}. * * @author xiangl@google.com (Xiang Li) */ -class LazyField { - - final private MessageLite defaultInstance; - final private ExtensionRegistryLite extensionRegistry; +public class LazyField extends LazyFieldLite { - // Mutable because it is initialized lazily. - private ByteString bytes; - private volatile MessageLite value; - private volatile boolean isDirty = false; + /** + * Carry a message's default instance which is used by {@code hashCode()}, {@code euqals()} and + * {@code toString()}. + */ + private final MessageLite defaultInstance; public LazyField(MessageLite defaultInstance, ExtensionRegistryLite extensionRegistry, ByteString bytes) { - this.defaultInstance = defaultInstance; - this.extensionRegistry = extensionRegistry; - this.bytes = bytes; - } + super(extensionRegistry, bytes); - public MessageLite getValue() { - ensureInitialized(); - return value; - } - - /** - * 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.value = value; - bytes = null; - isDirty = true; - return originalValue; + this.defaultInstance = defaultInstance; } - /** - * Due to the optional field can be duplicated at the end of serialized - * bytes, which will make the serialized size changed after LazyField - * parsed. Be careful when using this method. - */ - public int getSerializedSize() { - if (isDirty) { - return value.getSerializedSize(); - } - return bytes.size(); + @Override + public boolean containsDefaultInstance() { + return super.containsDefaultInstance() || value == defaultInstance; } - public ByteString toByteString() { - if (!isDirty) { - return bytes; - } - synchronized (this) { - if (!isDirty) { - return bytes; - } - bytes = value.toByteString(); - isDirty = false; - return bytes; - } + public MessageLite getValue() { + return getValue(defaultInstance); } @Override public int hashCode() { - ensureInitialized(); - return value.hashCode(); + return getValue().hashCode(); } @Override public boolean equals(Object obj) { - ensureInitialized(); - return value.equals(obj); + return getValue().equals(obj); } @Override public String toString() { - ensureInitialized(); - return value.toString(); - } - - private void ensureInitialized() { - if (value != null) { - return; - } - synchronized (this) { - if (value != null) { - return; - } - try { - if (bytes != null) { - value = defaultInstance.getParserForType() - .parseFrom(bytes, extensionRegistry); - } - } catch (IOException e) { - // TODO(xiangl): Refactory the API to support the exception thrown from - // lazily load messages. - } - } + return getValue().toString(); } // ==================================================== @@ -157,10 +95,12 @@ class LazyField { this.entry = entry; } + // @Override public K getKey() { return entry.getKey(); } + // @Override public Object getValue() { LazyField field = entry.getValue(); if (field == null) { @@ -173,6 +113,7 @@ class LazyField { return entry.getValue(); } + // @Override public Object setValue(Object value) { if (!(value instanceof MessageLite)) { throw new IllegalArgumentException( @@ -190,11 +131,13 @@ class LazyField { this.iterator = iterator; } + // @Override public boolean hasNext() { return iterator.hasNext(); } @SuppressWarnings("unchecked") + // @Override public Entry next() { Entry entry = iterator.next(); if (entry.getValue() instanceof LazyField) { @@ -203,6 +146,7 @@ class LazyField { return (Entry) entry; } + // @Override public void remove() { iterator.remove(); } diff --git a/java/src/main/java/com/google/protobuf/LazyStringArrayList.java b/java/src/main/java/com/google/protobuf/LazyStringArrayList.java index a5f0bd90..5599c7fb 100644 --- a/java/src/main/java/com/google/protobuf/LazyStringArrayList.java +++ b/java/src/main/java/com/google/protobuf/LazyStringArrayList.java @@ -30,6 +30,7 @@ package com.google.protobuf; +import java.util.Arrays; import java.util.List; import java.util.AbstractList; import java.util.ArrayList; @@ -39,9 +40,9 @@ import java.util.RandomAccess; /** * 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. + * element is one of String, ByteString, or byte[]. 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, @@ -64,8 +65,8 @@ import java.util.RandomAccess; public class LazyStringArrayList extends AbstractList implements LazyStringList, RandomAccess { - public final static LazyStringList EMPTY = new UnmodifiableLazyStringList( - new LazyStringArrayList()); + public static final LazyStringList EMPTY = + new LazyStringArrayList().getUnmodifiableView(); private final List list; @@ -87,13 +88,20 @@ public class LazyStringArrayList extends AbstractList Object o = list.get(index); if (o instanceof String) { return (String) o; - } else { + } else if (o instanceof ByteString) { ByteString bs = (ByteString) o; String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { list.set(index, s); } return s; + } else { + byte[] ba = (byte[]) o; + String s = Internal.toStringUtf8(ba); + if (Internal.isValidUtf8(ba)) { + list.set(index, s); + } + return s; } } @@ -134,6 +142,20 @@ public class LazyStringArrayList extends AbstractList return ret; } + // @Override + public boolean addAllByteString(Collection values) { + boolean ret = list.addAll(values); + modCount++; + return ret; + } + + // @Override + public boolean addAllByteArray(Collection c) { + boolean ret = list.addAll(c); + modCount++; + return ret; + } + @Override public String remove(int index) { Object o = list.remove(index); @@ -141,6 +163,7 @@ public class LazyStringArrayList extends AbstractList return asString(o); } + @Override public void clear() { list.clear(); modCount++; @@ -151,28 +174,194 @@ public class LazyStringArrayList extends AbstractList list.add(element); modCount++; } + + // @Override + public void add(byte[] element) { + list.add(element); + modCount++; + } // @Override public ByteString getByteString(int index) { Object o = list.get(index); - if (o instanceof String) { - ByteString b = ByteString.copyFromUtf8((String) o); + ByteString b = asByteString(o); + if (b != o) { + list.set(index, b); + } + return b; + } + + // @Override + public byte[] getByteArray(int index) { + Object o = list.get(index); + byte[] b = asByteArray(o); + if (b != o) { list.set(index, b); - return b; - } else { - return (ByteString) o; } + return b; + } + + // @Override + public void set(int index, ByteString s) { + list.set(index, s); + } + + // @Override + public void set(int index, byte[] s) { + list.set(index, s); } - private String asString(Object o) { + + private static String asString(Object o) { if (o instanceof String) { return (String) o; - } else { + } else if (o instanceof ByteString) { return ((ByteString) o).toStringUtf8(); + } else { + return Internal.toStringUtf8((byte[]) o); + } + } + + private static ByteString asByteString(Object o) { + if (o instanceof ByteString) { + return (ByteString) o; + } else if (o instanceof String) { + return ByteString.copyFromUtf8((String) o); + } else { + return ByteString.copyFrom((byte[]) o); + } + } + + private static byte[] asByteArray(Object o) { + if (o instanceof byte[]) { + return (byte[]) o; + } else if (o instanceof String) { + return Internal.toByteArray((String) o); + } else { + return ((ByteString) o).toByteArray(); } } + // @Override public List getUnderlyingElements() { return Collections.unmodifiableList(list); } + + // @Override + public void mergeFrom(LazyStringList other) { + for (Object o : other.getUnderlyingElements()) { + if (o instanceof byte[]) { + byte[] b = (byte[]) o; + // Byte array's content is mutable so they should be copied rather than + // shared when merging from one message to another. + list.add(Arrays.copyOf(b, b.length)); + } else { + list.add(o); + } + } + } + + private static class ByteArrayListView extends AbstractList + implements RandomAccess { + private final List list; + + ByteArrayListView(List list) { + this.list = list; + } + + @Override + public byte[] get(int index) { + Object o = list.get(index); + byte[] b = asByteArray(o); + if (b != o) { + list.set(index, b); + } + return b; + } + + @Override + public int size() { + return list.size(); + } + + @Override + public byte[] set(int index, byte[] s) { + Object o = list.set(index, s); + modCount++; + return asByteArray(o); + } + + @Override + public void add(int index, byte[] s) { + list.add(index, s); + modCount++; + } + + @Override + public byte[] remove(int index) { + Object o = list.remove(index); + modCount++; + return asByteArray(o); + } + } + + // @Override + public List asByteArrayList() { + return new ByteArrayListView(list); + } + + private static class ByteStringListView extends AbstractList + implements RandomAccess { + private final List list; + + ByteStringListView(List list) { + this.list = list; + } + + @Override + public ByteString get(int index) { + Object o = list.get(index); + ByteString b = asByteString(o); + if (b != o) { + list.set(index, b); + } + return b; + } + + @Override + public int size() { + return list.size(); + } + + @Override + public ByteString set(int index, ByteString s) { + Object o = list.set(index, s); + modCount++; + return asByteString(o); + } + + @Override + public void add(int index, ByteString s) { + list.add(index, s); + modCount++; + } + + @Override + public ByteString remove(int index) { + Object o = list.remove(index); + modCount++; + return asByteString(o); + } + } + + // @Override + public List asByteStringList() { + return new ByteStringListView(list); + } + + // @Override + public LazyStringList getUnmodifiableView() { + return new UnmodifiableLazyStringList(this); + } + } diff --git a/java/src/main/java/com/google/protobuf/LazyStringList.java b/java/src/main/java/com/google/protobuf/LazyStringList.java index 630932fe..7418c925 100644 --- a/java/src/main/java/com/google/protobuf/LazyStringList.java +++ b/java/src/main/java/com/google/protobuf/LazyStringList.java @@ -30,25 +30,22 @@ package com.google.protobuf; +import java.util.Collection; import java.util.List; /** * An interface extending {@code List} that also provides access to the - * items of the list as UTF8-encoded ByteString objects. This is used by the - * protocol buffer implementation to support lazily converting bytes parsed - * over the wire to String objects until needed and also increases the + * items of the list as UTF8-encoded ByteString or byte[] objects. This is + * used by the protocol buffer implementation to support lazily converting bytes + * parsed over the wire to String objects until needed and also increases the * efficiency of serialization if the String was never requested as the - * ByteString is already cached. - *

- * This only adds additional methods that are required for the use in the - * protocol buffer code in order to be able successfully round trip byte arrays - * through parsing and serialization without conversion to strings. It's not - * attempting to support the functionality of say {@code List}, hence - * why only these two very specific methods are added. + * ByteString or byte[] is already cached. The ByteString methods are used in + * immutable API only and byte[] methods used in mutable API only for they use + * different representations for string/bytes fields. * * @author jonp@google.com (Jon Perlow) */ -public interface LazyStringList extends List { +public interface LazyStringList extends ProtocolStringList { /** * Returns the element at the specified position in this list as a ByteString. @@ -59,6 +56,16 @@ public interface LazyStringList extends List { * ({@code index < 0 || index >= size()}) */ ByteString getByteString(int index); + + /** + * Returns the element at the specified position in this list as byte[]. + * + * @param index index of the element to return + * @return the element at the specified position in this list + * @throws IndexOutOfBoundsException if the index is out of range + * ({@code index < 0 || index >= size()}) + */ + byte[] getByteArray(int index); /** * Appends the specified element to the end of this list (optional @@ -71,11 +78,86 @@ public interface LazyStringList extends List { void add(ByteString element); /** - * Returns an unmodifiable List of the underlying elements, each of - * which is either a {@code String} or its equivalent UTF-8 encoded - * {@code ByteString}. It is an error for the caller to modify the returned + * Appends the specified element to the end of this list (optional + * operation). + * + * @param element element to be appended to this list + * @throws UnsupportedOperationException if the add operation + * is not supported by this list + */ + void add(byte[] element); + + /** + * Replaces the element at the specified position in this list with the + * specified element (optional operation). + * + * @param index index of the element to replace + * @param element the element to be stored at the specified position + * @throws UnsupportedOperationException if the set operation + * is not supported by this list + * IndexOutOfBoundsException if the index is out of range + * ({@code index < 0 || index >= size()}) + */ + void set(int index, ByteString element); + + /** + * Replaces the element at the specified position in this list with the + * specified element (optional operation). + * + * @param index index of the element to replace + * @param element the element to be stored at the specified position + * @throws UnsupportedOperationException if the set operation + * is not supported by this list + * IndexOutOfBoundsException if the index is out of range + * ({@code index < 0 || index >= size()}) + */ + void set(int index, byte[] element); + + /** + * Appends all elements in the specified ByteString collection to the end of + * this list. + * + * @param c collection whose elements are to be added to this list + * @return true if this list changed as a result of the call + * @throws UnsupportedOperationException if the addAllByteString + * operation is not supported by this list + */ + boolean addAllByteString(Collection c); + + /** + * Appends all elements in the specified byte[] collection to the end of + * this list. + * + * @param c collection whose elements are to be added to this list + * @return true if this list changed as a result of the call + * @throws UnsupportedOperationException if the addAllByteArray + * operation is not supported by this list + */ + boolean addAllByteArray(Collection c); + + /** + * Returns an unmodifiable List of the underlying elements, each of which is + * either a {@code String} or its equivalent UTF-8 encoded {@code ByteString} + * or byte[]. It is an error for the caller to modify the returned * List, and attempting to do so will result in an * {@link UnsupportedOperationException}. */ List getUnderlyingElements(); + + /** + * Merges all elements from another LazyStringList into this one. This method + * differs from {@link #addAll(Collection)} on that underlying byte arrays are + * copied instead of reference shared. Immutable API doesn't need to use this + * method as byte[] is not used there at all. + */ + void mergeFrom(LazyStringList other); + + /** + * Returns a mutable view of this list. Changes to the view will be made into + * the original list. This method is used in mutable API only. + */ + List asByteArrayList(); + + /** Returns an unmodifiable view of the list. */ + LazyStringList getUnmodifiableView(); } diff --git a/java/src/main/java/com/google/protobuf/LiteralByteString.java b/java/src/main/java/com/google/protobuf/LiteralByteString.java index 93c53dce..543da52b 100644 --- a/java/src/main/java/com/google/protobuf/LiteralByteString.java +++ b/java/src/main/java/com/google/protobuf/LiteralByteString.java @@ -142,6 +142,13 @@ class LiteralByteString extends ByteString { outputStream.write(toByteArray()); } + @Override + void writeToInternal(OutputStream outputStream, int sourceOffset, + int numberToWrite) throws IOException { + outputStream.write(bytes, getOffsetIntoBytes() + sourceOffset, + numberToWrite); + } + @Override public String toString(String charsetName) throws UnsupportedEncodingException { @@ -261,13 +268,20 @@ class LiteralByteString extends ByteString { @Override protected int partialHash(int h, int offset, int length) { - byte[] thisBytes = bytes; - for (int i = getOffsetIntoBytes() + offset, limit = i + length; i < limit; - i++) { - h = h * 31 + thisBytes[i]; + return hashCode(h, bytes, getOffsetIntoBytes() + offset, length); + } + + static int hashCode(int h, byte[] bytes, int offset, int length) { + for (int i = offset; i < offset + length; i++) { + h = h * 31 + bytes[i]; } return h; } + + static int hashCode(byte[] bytes) { + int h = hashCode(bytes.length, bytes, 0, bytes.length); + return h == 0 ? 1 : h; + } // ================================================================= // Input stream @@ -282,8 +296,7 @@ class LiteralByteString extends ByteString { public CodedInputStream newCodedInput() { // We trust CodedInputStream not to modify the bytes, or to give anyone // else access to them. - return CodedInputStream - .newInstance(bytes, getOffsetIntoBytes(), size()); // No copy + return CodedInputStream.newInstance(this); } // ================================================================= diff --git a/java/src/main/java/com/google/protobuf/Message.java b/java/src/main/java/com/google/protobuf/Message.java index 2b881413..19fc3b42 100644 --- a/java/src/main/java/com/google/protobuf/Message.java +++ b/java/src/main/java/com/google/protobuf/Message.java @@ -53,6 +53,7 @@ public interface Message extends MessageLite, MessageOrBuilder { // (From MessageLite, re-declared here only for return type covariance.) Parser getParserForType(); + // ----------------------------------------------------------------- // Comparison and hashing @@ -179,6 +180,12 @@ public interface Message extends MessageLite, MessageOrBuilder { */ Builder clearField(Descriptors.FieldDescriptor field); + /** + * Clears the oneof. This is exactly equivalent to calling the generated + * "clear" accessor method corresponding to the oneof. + */ + Builder clearOneof(Descriptors.OneofDescriptor oneof); + /** * Sets an element of a repeated field to the given value. The value must * be of the correct type for this field, i.e. the same type that diff --git a/java/src/main/java/com/google/protobuf/MessageLite.java b/java/src/main/java/com/google/protobuf/MessageLite.java index e5b9a47b..86be04ed 100644 --- a/java/src/main/java/com/google/protobuf/MessageLite.java +++ b/java/src/main/java/com/google/protobuf/MessageLite.java @@ -128,6 +128,7 @@ public interface MessageLite extends MessageLiteOrBuilder { */ void writeDelimitedTo(OutputStream output) throws IOException; + // ================================================================= // Builders diff --git a/java/src/main/java/com/google/protobuf/MessageOrBuilder.java b/java/src/main/java/com/google/protobuf/MessageOrBuilder.java index bf62d45e..886f5b44 100644 --- a/java/src/main/java/com/google/protobuf/MessageOrBuilder.java +++ b/java/src/main/java/com/google/protobuf/MessageOrBuilder.java @@ -76,7 +76,7 @@ public interface MessageOrBuilder extends MessageLiteOrBuilder { * Returns a collection of all the fields in this message which are set * and their corresponding values. A singular ("required" or "optional") * field is set iff hasField() returns true for that field. A "repeated" - * field is set iff getRepeatedFieldSize() is greater than zero. The + * field is set iff getRepeatedFieldCount() is greater than zero. The * values are exactly what would be returned by calling * {@link #getField(Descriptors.FieldDescriptor)} for each field. The map * is guaranteed to be a sorted map, so iterating over it will return fields @@ -88,6 +88,20 @@ public interface MessageOrBuilder extends MessageLiteOrBuilder { */ Map getAllFields(); + /** + * Returns true if the given oneof is set. + * @throws IllegalArgumentException if + * {@code oneof.getContainingType() != getDescriptorForType()}. + */ + boolean hasOneof(Descriptors.OneofDescriptor oneof); + + /** + * Obtains the FieldDescriptor if the given oneof is set. Returns null + * if no field is set. + */ + Descriptors.FieldDescriptor getOneofFieldDescriptor( + Descriptors.OneofDescriptor oneof); + /** * Returns true if the given field is set. This is exactly equivalent to * calling the generated "has" accessor method corresponding to the field. diff --git a/java/src/main/java/com/google/protobuf/MessageReflection.java b/java/src/main/java/com/google/protobuf/MessageReflection.java new file mode 100644 index 00000000..021b9d58 --- /dev/null +++ b/java/src/main/java/com/google/protobuf/MessageReflection.java @@ -0,0 +1,931 @@ +// 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 com.google.protobuf.Descriptors.FieldDescriptor; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +/** + * Reflection utility methods shared by both mutable and immutable messages. + * + * @author liujisi@google.com (Pherl Liu) + */ +class MessageReflection { + + static void writeMessageTo(Message message, 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 : + message.getDescriptorForType().getFields()) { + if (field.isRequired() && !fields.containsKey(field)) { + fields.put(field, message.getField(field)); + } + } + } + for (final Map.Entry entry : + fields.entrySet()) { + final Descriptors.FieldDescriptor field = entry.getKey(); + final Object value = entry.getValue(); + if (isMessageSet && field.isExtension() && + field.getType() == Descriptors.FieldDescriptor.Type.MESSAGE && + !field.isRepeated()) { + output.writeMessageSetExtension(field.getNumber(), (Message) value); + } else { + FieldSet.writeField(field, value, output); + } + } + + final UnknownFieldSet unknownFields = message.getUnknownFields(); + if (isMessageSet) { + unknownFields.writeAsMessageSetTo(output); + } else { + unknownFields.writeTo(output); + } + } + + static int getSerializedSize(Message message) { + int size = 0; + final boolean isMessageSet = + message.getDescriptorForType().getOptions().getMessageSetWireFormat(); + + for (final Map.Entry entry : + message.getAllFields().entrySet()) { + final Descriptors.FieldDescriptor field = entry.getKey(); + final Object value = entry.getValue(); + if (isMessageSet && field.isExtension() && + field.getType() == Descriptors.FieldDescriptor.Type.MESSAGE && + !field.isRepeated()) { + size += CodedOutputStream.computeMessageSetExtensionSize( + field.getNumber(), (Message) value); + } else { + size += FieldSet.computeFieldSize(field, value); + } + } + + final UnknownFieldSet unknownFields = message.getUnknownFields(); + if (isMessageSet) { + size += unknownFields.getSerializedSizeAsMessageSet(); + } else { + size += unknownFields.getSerializedSize(); + } + return size; + } + + static String delimitWithCommas(List parts) { + StringBuilder result = new StringBuilder(); + for (String part : parts) { + if (result.length() > 0) { + result.append(", "); + } + result.append(part); + } + return result.toString(); + } + + @SuppressWarnings("unchecked") + static boolean isInitialized(MessageOrBuilder message) { + // Check that all required fields are present. + for (final Descriptors.FieldDescriptor field : message + .getDescriptorForType() + .getFields()) { + if (field.isRequired()) { + if (!message.hasField(field)) { + return false; + } + } + } + + // Check that embedded messages are initialized. + for (final Map.Entry entry : + message.getAllFields().entrySet()) { + final Descriptors.FieldDescriptor field = entry.getKey(); + if (field.getJavaType() == Descriptors.FieldDescriptor.JavaType.MESSAGE) { + if (field.isRepeated()) { + for (final Message element + : (List) entry.getValue()) { + if (!element.isInitialized()) { + return false; + } + } + } else { + if (!((Message) entry.getValue()).isInitialized()) { + return false; + } + } + } + } + + return true; + } + + private static String subMessagePrefix(final String prefix, + final Descriptors.FieldDescriptor field, + final int index) { + final StringBuilder result = new StringBuilder(prefix); + if (field.isExtension()) { + result.append('(') + .append(field.getFullName()) + .append(')'); + } else { + result.append(field.getName()); + } + if (index != -1) { + result.append('[') + .append(index) + .append(']'); + } + result.append('.'); + return result.toString(); + } + + private static void findMissingFields(final MessageOrBuilder message, + final String prefix, + final List results) { + for (final Descriptors.FieldDescriptor field : + message.getDescriptorForType().getFields()) { + if (field.isRequired() && !message.hasField(field)) { + results.add(prefix + field.getName()); + } + } + + for (final Map.Entry entry : + message.getAllFields().entrySet()) { + final Descriptors.FieldDescriptor field = entry.getKey(); + final Object value = entry.getValue(); + + if (field.getJavaType() == Descriptors.FieldDescriptor.JavaType.MESSAGE) { + if (field.isRepeated()) { + int i = 0; + for (final Object element : (List) value) { + findMissingFields((MessageOrBuilder) element, + subMessagePrefix(prefix, field, i++), + results); + } + } else { + if (message.hasField(field)) { + findMissingFields((MessageOrBuilder) value, + subMessagePrefix(prefix, field, -1), + results); + } + } + } + } + } + + /** + * Populates {@code this.missingFields} with the full "path" of each missing + * required field in the given message. + */ + static List findMissingFields( + final MessageOrBuilder message) { + final List results = new ArrayList(); + findMissingFields(message, "", results); + return results; + } + + static interface MergeTarget { + enum ContainerType { + MESSAGE, EXTENSION_SET + } + + /** + * Returns the descriptor for the target. + */ + public Descriptors.Descriptor getDescriptorForType(); + + public ContainerType getContainerType(); + + public ExtensionRegistry.ExtensionInfo findExtensionByName( + ExtensionRegistry registry, String name); + + public ExtensionRegistry.ExtensionInfo findExtensionByNumber( + ExtensionRegistry registry, Descriptors.Descriptor containingType, + int fieldNumber); + + /** + * 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. + */ + public Object getField(Descriptors.FieldDescriptor field); + + /** + * Returns true if the given field is set. This is exactly equivalent to + * calling the generated "has" accessor method corresponding to the field. + * + * @throws IllegalArgumentException The field is a repeated field, or {@code + * field.getContainingType() != getDescriptorForType()}. + */ + boolean hasField(Descriptors.FieldDescriptor field); + + /** + * Sets a field to the given value. The value must be of the correct type + * for this field, i.e. the same type that + * {@link Message#getField(Descriptors.FieldDescriptor)} + * would return. + */ + MergeTarget setField(Descriptors.FieldDescriptor field, Object value); + + /** + * Clears the field. This is exactly equivalent to calling the generated + * "clear" accessor method corresponding to the field. + */ + MergeTarget clearField(Descriptors.FieldDescriptor field); + + /** + * Sets an element of a repeated field to the given value. The value must + * be of the correct type for this field, i.e. the same type that {@link + * Message#getRepeatedField(Descriptors.FieldDescriptor, int)} would return. + * + * @throws IllegalArgumentException The field is not a repeated field, or + * {@code field.getContainingType() != + * getDescriptorForType()}. + */ + MergeTarget setRepeatedField(Descriptors.FieldDescriptor field, + int index, Object value); + + /** + * Like {@code setRepeatedField}, but appends the value as a new element. + * + * @throws IllegalArgumentException The field is not a repeated field, or + * {@code field.getContainingType() != + * getDescriptorForType()}. + */ + MergeTarget addRepeatedField(Descriptors.FieldDescriptor field, + Object value); + + /** + * Returns true if the given oneof is set. + * + * @throws IllegalArgumentException if + * {@code oneof.getContainingType() != getDescriptorForType()}. + */ + boolean hasOneof(Descriptors.OneofDescriptor oneof); + + /** + * Clears the oneof. This is exactly equivalent to calling the generated + * "clear" accessor method corresponding to the oneof. + */ + MergeTarget clearOneof(Descriptors.OneofDescriptor oneof); + + /** + * Obtains the FieldDescriptor if the given oneof is set. Returns null + * if no field is set. + */ + Descriptors.FieldDescriptor getOneofFieldDescriptor(Descriptors.OneofDescriptor oneof); + + /** + * Parse the input stream into a sub field group defined based on either + * FieldDescriptor or the default instance. + */ + Object parseGroup(CodedInputStream input, ExtensionRegistryLite registry, + Descriptors.FieldDescriptor descriptor, Message defaultInstance) + throws IOException; + + /** + * Parse the input stream into a sub field message defined based on either + * FieldDescriptor or the default instance. + */ + Object parseMessage(CodedInputStream input, ExtensionRegistryLite registry, + Descriptors.FieldDescriptor descriptor, Message defaultInstance) + throws IOException; + + /** + * Parse from a ByteString into a sub field message defined based on either + * FieldDescriptor or the default instance. There isn't a varint indicating + * the length of the message at the beginning of the input ByteString. + */ + Object parseMessageFromBytes( + 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. + */ + Object readPrimitiveField( + CodedInputStream input, WireFormat.FieldType type, + boolean checkUtf8) throws IOException; + + /** + * Returns a new merge target for a sub-field. When defaultInstance is + * provided, it indicates the descriptor is for an extension type, and + * implementations should create a new instance from the defaultInstance + * prototype directly. + */ + MergeTarget newMergeTargetForField( + Descriptors.FieldDescriptor descriptor, + Message defaultInstance); + + /** + * Finishes the merge and returns the underlying object. + */ + Object finish(); + } + + static class BuilderAdapter implements MergeTarget { + + private final Message.Builder builder; + + public Descriptors.Descriptor getDescriptorForType() { + return builder.getDescriptorForType(); + } + + public BuilderAdapter(Message.Builder builder) { + this.builder = builder; + } + + public Object getField(Descriptors.FieldDescriptor field) { + return builder.getField(field); + } + + @Override + public boolean hasField(Descriptors.FieldDescriptor field) { + return builder.hasField(field); + } + + public MergeTarget setField(Descriptors.FieldDescriptor field, + Object value) { + builder.setField(field, value); + return this; + } + + public MergeTarget clearField(Descriptors.FieldDescriptor field) { + builder.clearField(field); + return this; + } + + public MergeTarget setRepeatedField( + Descriptors.FieldDescriptor field, int index, Object value) { + builder.setRepeatedField(field, index, value); + return this; + } + + public MergeTarget addRepeatedField( + Descriptors.FieldDescriptor field, Object value) { + builder.addRepeatedField(field, value); + return this; + } + + @Override + public boolean hasOneof(Descriptors.OneofDescriptor oneof) { + return builder.hasOneof(oneof); + } + + @Override + public MergeTarget clearOneof(Descriptors.OneofDescriptor oneof) { + builder.clearOneof(oneof); + return this; + } + + @Override + public Descriptors.FieldDescriptor getOneofFieldDescriptor(Descriptors.OneofDescriptor oneof) { + return builder.getOneofFieldDescriptor(oneof); + } + + public ContainerType getContainerType() { + return ContainerType.MESSAGE; + } + + public ExtensionRegistry.ExtensionInfo findExtensionByName( + ExtensionRegistry registry, String name) { + return registry.findImmutableExtensionByName(name); + } + + public ExtensionRegistry.ExtensionInfo findExtensionByNumber( + ExtensionRegistry registry, Descriptors.Descriptor containingType, + int fieldNumber) { + return registry.findImmutableExtensionByNumber(containingType, + fieldNumber); + } + + public Object parseGroup(CodedInputStream input, + ExtensionRegistryLite extensionRegistry, + Descriptors.FieldDescriptor field, Message defaultInstance) + throws IOException { + Message.Builder subBuilder; + // When default instance is not null. The field is an extension field. + if (defaultInstance != null) { + subBuilder = defaultInstance.newBuilderForType(); + } else { + subBuilder = builder.newBuilderForField(field); + } + if (!field.isRepeated()) { + Message originalMessage = (Message) getField(field); + if (originalMessage != null) { + subBuilder.mergeFrom(originalMessage); + } + } + input.readGroup(field.getNumber(), subBuilder, extensionRegistry); + return subBuilder.buildPartial(); + } + + public Object parseMessage(CodedInputStream input, + ExtensionRegistryLite extensionRegistry, + Descriptors.FieldDescriptor field, Message defaultInstance) + throws IOException { + Message.Builder subBuilder; + // When default instance is not null. The field is an extension field. + if (defaultInstance != null) { + subBuilder = defaultInstance.newBuilderForType(); + } else { + subBuilder = builder.newBuilderForField(field); + } + if (!field.isRepeated()) { + Message originalMessage = (Message) getField(field); + if (originalMessage != null) { + subBuilder.mergeFrom(originalMessage); + } + } + input.readMessage(subBuilder, extensionRegistry); + return subBuilder.buildPartial(); + } + + public Object parseMessageFromBytes(ByteString bytes, + ExtensionRegistryLite extensionRegistry, + Descriptors.FieldDescriptor field, Message defaultInstance) + throws IOException { + Message.Builder subBuilder; + // When default instance is not null. The field is an extension field. + if (defaultInstance != null) { + subBuilder = defaultInstance.newBuilderForType(); + } else { + subBuilder = builder.newBuilderForField(field); + } + if (!field.isRepeated()) { + Message originalMessage = (Message) getField(field); + if (originalMessage != null) { + subBuilder.mergeFrom(originalMessage); + } + } + subBuilder.mergeFrom(bytes, extensionRegistry); + return subBuilder.buildPartial(); + } + + public MergeTarget newMergeTargetForField(Descriptors.FieldDescriptor field, + Message defaultInstance) { + if (defaultInstance != null) { + return new BuilderAdapter( + defaultInstance.newBuilderForType()); + } else { + 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 Object finish() { + return builder.buildPartial(); + } + } + + + static class ExtensionAdapter implements MergeTarget { + + private final FieldSet extensions; + + ExtensionAdapter(FieldSet extensions) { + this.extensions = extensions; + } + + public Descriptors.Descriptor getDescriptorForType() { + throw new UnsupportedOperationException( + "getDescriptorForType() called on FieldSet object"); + } + + public Object getField(Descriptors.FieldDescriptor field) { + return extensions.getField(field); + } + + public boolean hasField(Descriptors.FieldDescriptor field) { + return extensions.hasField(field); + } + + public MergeTarget setField(Descriptors.FieldDescriptor field, + Object value) { + extensions.setField(field, value); + return this; + } + + public MergeTarget clearField(Descriptors.FieldDescriptor field) { + extensions.clearField(field); + return this; + } + + public MergeTarget setRepeatedField( + Descriptors.FieldDescriptor field, int index, Object value) { + extensions.setRepeatedField(field, index, value); + return this; + } + + public MergeTarget addRepeatedField( + Descriptors.FieldDescriptor field, Object value) { + extensions.addRepeatedField(field, value); + return this; + } + + @Override + public boolean hasOneof(Descriptors.OneofDescriptor oneof) { + return false; + } + + @Override + public MergeTarget clearOneof(Descriptors.OneofDescriptor oneof) { + // Nothing to clear. + return this; + } + + @Override + public Descriptors.FieldDescriptor getOneofFieldDescriptor(Descriptors.OneofDescriptor oneof) { + return null; + } + + public ContainerType getContainerType() { + return ContainerType.EXTENSION_SET; + } + + public ExtensionRegistry.ExtensionInfo findExtensionByName( + ExtensionRegistry registry, String name) { + return registry.findImmutableExtensionByName(name); + } + + public ExtensionRegistry.ExtensionInfo findExtensionByNumber( + ExtensionRegistry registry, Descriptors.Descriptor containingType, + int fieldNumber) { + return registry.findImmutableExtensionByNumber(containingType, + fieldNumber); + } + + public Object parseGroup(CodedInputStream input, + ExtensionRegistryLite registry, Descriptors.FieldDescriptor field, + Message defaultInstance) throws IOException { + Message.Builder subBuilder = + defaultInstance.newBuilderForType(); + if (!field.isRepeated()) { + Message originalMessage = (Message) getField(field); + if (originalMessage != null) { + subBuilder.mergeFrom(originalMessage); + } + } + input.readGroup(field.getNumber(), subBuilder, registry); + return subBuilder.buildPartial(); + } + + public Object parseMessage(CodedInputStream input, + ExtensionRegistryLite registry, Descriptors.FieldDescriptor field, + Message defaultInstance) throws IOException { + Message.Builder subBuilder = + defaultInstance.newBuilderForType(); + if (!field.isRepeated()) { + Message originalMessage = (Message) getField(field); + if (originalMessage != null) { + subBuilder.mergeFrom(originalMessage); + } + } + input.readMessage(subBuilder, registry); + return subBuilder.buildPartial(); + } + + public Object parseMessageFromBytes(ByteString bytes, + ExtensionRegistryLite registry, Descriptors.FieldDescriptor field, + Message defaultInstance) throws IOException { + Message.Builder subBuilder = defaultInstance.newBuilderForType(); + if (!field.isRepeated()) { + Message originalMessage = (Message) getField(field); + if (originalMessage != null) { + subBuilder.mergeFrom(originalMessage); + } + } + subBuilder.mergeFrom(bytes, registry); + return subBuilder.buildPartial(); + } + + public MergeTarget newMergeTargetForField( + Descriptors.FieldDescriptor descriptor, Message defaultInstance) { + 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 Object finish() { + throw new UnsupportedOperationException( + "finish() called on FieldSet object"); + } + } + + /** + * Parses a single field into MergeTarget. The target can be Message.Builder, + * FieldSet or MutableMessage. + * + * Package-private because it is used by GeneratedMessage.ExtendableMessage. + * + * @param tag The tag, which should have already been read. + * @return {@code true} unless the tag is an end-group tag. + */ + static boolean mergeFieldFrom( + CodedInputStream input, + UnknownFieldSet.Builder unknownFields, + ExtensionRegistryLite extensionRegistry, + Descriptors.Descriptor type, + MergeTarget target, + int tag) throws IOException { + if (type.getOptions().getMessageSetWireFormat() && + tag == WireFormat.MESSAGE_SET_ITEM_TAG) { + mergeMessageSetExtensionFromCodedStream( + input, unknownFields, extensionRegistry, type, target); + return true; + } + + final int wireType = WireFormat.getTagWireType(tag); + final int fieldNumber = WireFormat.getTagFieldNumber(tag); + + final Descriptors.FieldDescriptor field; + Message defaultInstance = null; + + if (type.isExtensionNumber(fieldNumber)) { + // extensionRegistry may be either ExtensionRegistry or + // ExtensionRegistryLite. Since the type we are parsing is a full + // message, only a full ExtensionRegistry could possibly contain + // extensions of it. Otherwise we will treat the registry as if it + // were empty. + if (extensionRegistry instanceof ExtensionRegistry) { + final ExtensionRegistry.ExtensionInfo extension = + target.findExtensionByNumber((ExtensionRegistry) extensionRegistry, + type, fieldNumber); + if (extension == null) { + field = null; + } else { + field = extension.descriptor; + defaultInstance = extension.defaultInstance; + if (defaultInstance == null && + field.getJavaType() + == Descriptors.FieldDescriptor.JavaType.MESSAGE) { + throw new IllegalStateException( + "Message-typed extension lacked default instance: " + + field.getFullName()); + } + } + } else { + field = null; + } + } else if (target.getContainerType() == MergeTarget.ContainerType.MESSAGE) { + field = type.findFieldByNumber(fieldNumber); + } else { + field = null; + } + + boolean unknown = false; + boolean packed = false; + if (field == null) { + unknown = true; // Unknown field. + } else if (wireType == FieldSet.getWireFormatForFieldType( + field.getLiteType(), + false /* isPacked */)) { + packed = false; + } else if (field.isPackable() && + wireType == FieldSet.getWireFormatForFieldType( + field.getLiteType(), + true /* isPacked */)) { + packed = true; + } else { + unknown = true; // Unknown wire type. + } + + if (unknown) { // Unknown field or wrong wire type. Skip. + return unknownFields.mergeFieldFrom(tag, input); + } + + if (packed) { + final int length = input.readRawVarint32(); + final int limit = input.pushLimit(length); + if (field.getLiteType() == WireFormat.FieldType.ENUM) { + while (input.getBytesUntilLimit() > 0) { + final int rawValue = input.readEnum(); + final Object value = field.getEnumType().findValueByNumber(rawValue); + if (value == null) { + // If the number isn't recognized as a valid value for this + // enum, drop it (don't even add it to unknownFields). + return true; + } + target.addRepeatedField(field, value); + } + } else { + while (input.getBytesUntilLimit() > 0) { + final Object value = + target.readPrimitiveField(input, field.getLiteType(), field.needsUtf8Check()); + target.addRepeatedField(field, value); + } + } + input.popLimit(limit); + } else { + final Object value; + switch (field.getType()) { + case GROUP: { + value = target + .parseGroup(input, extensionRegistry, field, defaultInstance); + break; + } + case MESSAGE: { + value = target + .parseMessage(input, extensionRegistry, field, defaultInstance); + break; + } + case ENUM: + final int rawValue = input.readEnum(); + value = field.getEnumType().findValueByNumber(rawValue); + // If the number isn't recognized as a valid value for this enum, + // drop it. + if (value == null) { + unknownFields.mergeVarintField(fieldNumber, rawValue); + return true; + } + break; + default: + value = target.readPrimitiveField(input, field.getLiteType(), field.needsUtf8Check()); + break; + } + + if (field.isRepeated()) { + target.addRepeatedField(field, value); + } else { + target.setField(field, value); + } + } + + return true; + } + + /** + * Called by {@code #mergeFieldFrom()} to parse a MessageSet extension into + * MergeTarget. + */ + private static void mergeMessageSetExtensionFromCodedStream( + CodedInputStream input, + UnknownFieldSet.Builder unknownFields, + ExtensionRegistryLite extensionRegistry, + Descriptors.Descriptor type, + MergeTarget target) throws IOException { + + // The wire format for MessageSet is: + // message MessageSet { + // repeated group Item = 1 { + // required int32 typeId = 2; + // required bytes message = 3; + // } + // } + // "typeId" is the extension's field number. The extension can only be + // a message type, where "message" contains the encoded bytes of that + // message. + // + // In practice, we will probably never see a MessageSet item in which + // the message appears before the type ID, or where either field does not + // appear exactly once. However, in theory such cases are valid, so we + // should be prepared to accept them. + + int typeId = 0; + ByteString rawBytes = null; // If we encounter "message" before "typeId" + ExtensionRegistry.ExtensionInfo extension = null; + + // Read bytes from input, if we get it's type first then parse it eagerly, + // otherwise we store the raw bytes in a local variable. + while (true) { + final int tag = input.readTag(); + if (tag == 0) { + break; + } + + if (tag == WireFormat.MESSAGE_SET_TYPE_ID_TAG) { + typeId = input.readUInt32(); + if (typeId != 0) { + // extensionRegistry may be either ExtensionRegistry or + // ExtensionRegistryLite. Since the type we are parsing is a full + // message, only a full ExtensionRegistry could possibly contain + // extensions of it. Otherwise we will treat the registry as if it + // were empty. + if (extensionRegistry instanceof ExtensionRegistry) { + extension = target.findExtensionByNumber( + (ExtensionRegistry) extensionRegistry, type, typeId); + } + } + + } else if (tag == WireFormat.MESSAGE_SET_MESSAGE_TAG) { + if (typeId != 0) { + if (extension != null && + ExtensionRegistryLite.isEagerlyParseMessageSets()) { + // We already know the type, so we can parse directly from the + // input with no copying. Hooray! + eagerlyMergeMessageSetExtension( + input, extension, extensionRegistry, target); + rawBytes = null; + continue; + } + } + // We haven't seen a type ID yet or we want parse message lazily. + rawBytes = input.readBytes(); + + } else { // Unknown tag. Skip it. + if (!input.skipField(tag)) { + break; // End of group + } + } + } + input.checkLastTagWas(WireFormat.MESSAGE_SET_ITEM_END_TAG); + + // Process the raw bytes. + if (rawBytes != null && typeId != 0) { // Zero is not a valid type ID. + if (extension != null) { // We known the type + mergeMessageSetExtensionFromBytes( + rawBytes, extension, extensionRegistry, target); + } else { // We don't know how to parse this. Ignore it. + if (rawBytes != null) { + unknownFields.mergeField(typeId, UnknownFieldSet.Field.newBuilder() + .addLengthDelimited(rawBytes).build()); + } + } + } + } + + private static void mergeMessageSetExtensionFromBytes( + ByteString rawBytes, + ExtensionRegistry.ExtensionInfo extension, + ExtensionRegistryLite extensionRegistry, + MergeTarget target) throws IOException { + + Descriptors.FieldDescriptor field = extension.descriptor; + boolean hasOriginalValue = target.hasField(field); + + if (hasOriginalValue || ExtensionRegistryLite.isEagerlyParseMessageSets()) { + // If the field already exists, we just parse the field. + Object value = target.parseMessageFromBytes( + rawBytes, extensionRegistry,field, extension.defaultInstance); + target.setField(field, value); + } else { + // Use LazyField to load MessageSet lazily. + LazyField lazyField = new LazyField( + extension.defaultInstance, extensionRegistry, rawBytes); + target.setField(field, lazyField); + } + } + + private static void eagerlyMergeMessageSetExtension( + CodedInputStream input, + ExtensionRegistry.ExtensionInfo extension, + ExtensionRegistryLite extensionRegistry, + MergeTarget target) throws IOException { + Descriptors.FieldDescriptor field = extension.descriptor; + Object value = target.parseMessage(input, extensionRegistry, field, + extension.defaultInstance); + target.setField(field, value); + } +} diff --git a/java/src/main/java/com/google/protobuf/Parser.java b/java/src/main/java/com/google/protobuf/Parser.java index 7d8e8217..5fe969b1 100644 --- a/java/src/main/java/com/google/protobuf/Parser.java +++ b/java/src/main/java/com/google/protobuf/Parser.java @@ -35,6 +35,8 @@ import java.io.InputStream; /** * Abstract interface for parsing Protocol Messages. * + * The implementation should be stateless and thread-safe. + * * @author liujisi@google.com (Pherl Liu) */ public interface Parser { diff --git a/java/src/main/java/com/google/protobuf/ProtocolStringList.java b/java/src/main/java/com/google/protobuf/ProtocolStringList.java new file mode 100644 index 00000000..41a1f90b --- /dev/null +++ b/java/src/main/java/com/google/protobuf/ProtocolStringList.java @@ -0,0 +1,48 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// http://code.google.com/p/protobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package com.google.protobuf; + +import java.util.List; + +/** + * An interface extending {@code List} used for repeated string fields + * to provide optional access to the data as a list of ByteStrings. The + * underlying implementation stores values as either ByteStrings or Strings + * (see {@link LazyStringArrayList}) depending on how the value was initialized + * or last read, and it is often more efficient to deal with lists of + * ByteStrings when handling protos that have been deserialized from bytes. + */ +public interface ProtocolStringList extends List { + + /** Returns a view of the data as a list of ByteStrings. */ + List asByteStringList(); + +} diff --git a/java/src/main/java/com/google/protobuf/RopeByteString.java b/java/src/main/java/com/google/protobuf/RopeByteString.java index 46997823..a011df45 100644 --- a/java/src/main/java/com/google/protobuf/RopeByteString.java +++ b/java/src/main/java/com/google/protobuf/RopeByteString.java @@ -401,6 +401,20 @@ class RopeByteString extends ByteString { right.writeTo(outputStream); } + @Override + void writeToInternal(OutputStream out, int sourceOffset, + int numberToWrite) throws IOException { + if (sourceOffset + numberToWrite <= leftLength) { + left.writeToInternal(out, sourceOffset, numberToWrite); + } else if (sourceOffset >= leftLength) { + right.writeToInternal(out, sourceOffset - leftLength, numberToWrite); + } else { + int numberToWriteInLeft = leftLength - sourceOffset; + left.writeToInternal(out, sourceOffset, numberToWriteInLeft); + right.writeToInternal(out, 0, numberToWrite - numberToWriteInLeft); + } + } + @Override public String toString(String charsetName) throws UnsupportedEncodingException { diff --git a/java/src/main/java/com/google/protobuf/RpcUtil.java b/java/src/main/java/com/google/protobuf/RpcUtil.java index b1b959a8..856f0a51 100644 --- a/java/src/main/java/com/google/protobuf/RpcUtil.java +++ b/java/src/main/java/com/google/protobuf/RpcUtil.java @@ -91,9 +91,8 @@ public final class RpcUtil { @SuppressWarnings("unchecked") private static Type copyAsType( final Type typeDefaultInstance, final Message source) { - return (Type)typeDefaultInstance.newBuilderForType() - .mergeFrom(source) - .build(); + return (Type) typeDefaultInstance + .newBuilderForType().mergeFrom(source).build(); } /** diff --git a/java/src/main/java/com/google/protobuf/TextFormat.java b/java/src/main/java/com/google/protobuf/TextFormat.java index ed462899..97da09bb 100644 --- a/java/src/main/java/com/google/protobuf/TextFormat.java +++ b/java/src/main/java/com/google/protobuf/TextFormat.java @@ -31,17 +31,18 @@ package com.google.protobuf; import com.google.protobuf.Descriptors.Descriptor; -import com.google.protobuf.Descriptors.FieldDescriptor; import com.google.protobuf.Descriptors.EnumDescriptor; import com.google.protobuf.Descriptors.EnumValueDescriptor; +import com.google.protobuf.Descriptors.FieldDescriptor; import java.io.IOException; -import java.nio.CharBuffer; import java.math.BigInteger; +import java.nio.CharBuffer; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -55,6 +56,9 @@ import java.util.regex.Pattern; public final class TextFormat { private TextFormat() {} + private static final Logger logger = + Logger.getLogger(TextFormat.class.getName()); + private static final Printer DEFAULT_PRINTER = new Printer(); private static final Printer SINGLE_LINE_PRINTER = (new Printer()).setSingleLineMode(true); @@ -66,8 +70,9 @@ public final class TextFormat { * the parameter output. (This representation is the new version of the * classic "ProtocolPrinter" output from the original Protocol Buffer system) */ - public static void print(final MessageOrBuilder message, final Appendable output) - throws IOException { + public static void print( + final MessageOrBuilder message, final Appendable output) + throws IOException { DEFAULT_PRINTER.print(message, new TextGenerator(output)); } @@ -266,7 +271,8 @@ public final class TextFormat { return this; } - private void print(final MessageOrBuilder message, final TextGenerator generator) + private void print( + final MessageOrBuilder message, final TextGenerator generator) throws IOException { for (Map.Entry field : message.getAllFields().entrySet()) { @@ -385,13 +391,17 @@ public final class TextFormat { generator.print("\""); generator.print(escapeNonAscii ? escapeText((String) value) : - (String) value); + escapeDoubleQuotesAndBackslashes((String) value)); generator.print("\""); break; case BYTES: generator.print("\""); - generator.print(escapeBytes((ByteString) value)); + if (value instanceof ByteString) { + generator.print(escapeBytes((ByteString) value)); + } else { + generator.print(escapeBytes((byte[]) value)); + } generator.print("\""); break; @@ -455,16 +465,16 @@ public final class TextFormat { } /** Convert an unsigned 32-bit integer to a string. */ - private static String unsignedToString(final int value) { + public static String unsignedToString(final int value) { if (value >= 0) { return Integer.toString(value); } else { - return Long.toString(((long) value) & 0x00000000FFFFFFFFL); + return Long.toString(value & 0x00000000FFFFFFFFL); } } /** Convert an unsigned 64-bit integer to a string. */ - private static String unsignedToString(final long value) { + public static String unsignedToString(final long value) { if (value >= 0) { return Long.toString(value); } else { @@ -518,17 +528,16 @@ public final class TextFormat { for (int i = 0; i < size; i++) { if (text.charAt(i) == '\n') { - write(text.subSequence(pos, size), i - pos + 1); + write(text.subSequence(pos, i + 1)); pos = i + 1; atStartOfLine = true; } } - write(text.subSequence(pos, size), size - pos); + write(text.subSequence(pos, size)); } - private void write(final CharSequence data, final int size) - throws IOException { - if (size == 0) { + private void write(final CharSequence data) throws IOException { + if (data.length() == 0) { return; } if (atStartOfLine) { @@ -704,6 +713,14 @@ public final class TextFormat { c == '-' || c == '+'; } + /** + * Returns {@code true} if the current token's text is equal to that + * specified. + */ + public boolean lookingAt(String text) { + return currentToken.equals(text); + } + /** * If the next token is an identifier, consume it and return its value. * Otherwise, throw a {@link ParseException}. @@ -717,7 +734,8 @@ public final class TextFormat { (c == '_') || (c == '.')) { // OK } else { - throw parseException("Expected identifier."); + throw parseException( + "Expected identifier. Found '" + currentToken + "'"); } } @@ -726,6 +744,19 @@ public final class TextFormat { return result; } + /** + * If the next token is an identifier, consume it and return {@code true}. + * Otherwise, return {@code false} without doing anything. + */ + public boolean tryConsumeIdentifier() { + try { + consumeIdentifier(); + return true; + } catch (ParseException e) { + return false; + } + } + /** * If the next token is a 32-bit signed integer, consume it and return its * value. Otherwise, throw a {@link ParseException}. @@ -768,6 +799,19 @@ public final class TextFormat { } } + /** + * If the next token is a 64-bit signed integer, consume it and return + * {@code true}. Otherwise, return {@code false} without doing anything. + */ + public boolean tryConsumeInt64() { + try { + consumeInt64(); + return true; + } catch (ParseException e) { + return false; + } + } + /** * If the next token is a 64-bit unsigned integer, consume it and return its * value. Otherwise, throw a {@link ParseException}. @@ -782,6 +826,19 @@ public final class TextFormat { } } + /** + * If the next token is a 64-bit unsigned integer, consume it and return + * {@code true}. Otherwise, return {@code false} without doing anything. + */ + public boolean tryConsumeUInt64() { + try { + consumeUInt64(); + return true; + } catch (ParseException e) { + return false; + } + } + /** * If the next token is a double, consume it and return its value. * Otherwise, throw a {@link ParseException}. @@ -807,6 +864,19 @@ public final class TextFormat { } } + /** + * If the next token is a double, consume it and return {@code true}. + * Otherwise, return {@code false} without doing anything. + */ + public boolean tryConsumeDouble() { + try { + consumeDouble(); + return true; + } catch (ParseException e) { + return false; + } + } + /** * If the next token is a float, consume it and return its value. * Otherwise, throw a {@link ParseException}. @@ -832,6 +902,19 @@ public final class TextFormat { } } + /** + * If the next token is a float, consume it and return {@code true}. + * Otherwise, return {@code false} without doing anything. + */ + public boolean tryConsumeFloat() { + try { + consumeFloat(); + return true; + } catch (ParseException e) { + return false; + } + } + /** * If the next token is a boolean, consume it and return its value. * Otherwise, throw a {@link ParseException}. @@ -860,6 +943,19 @@ public final class TextFormat { return consumeByteString().toStringUtf8(); } + /** + * If the next token is a string, consume it and return true. Otherwise, + * return false. + */ + public boolean tryConsumeString() { + try { + consumeString(); + return true; + } catch (ParseException e) { + return false; + } + } + /** * If the next token is a string, consume it, unescape it as a * {@link ByteString}, and return it. Otherwise, throw a @@ -880,7 +976,8 @@ public final class TextFormat { * multiple adjacent tokens which are automatically concatenated, like in * C or Python. */ - private void consumeByteString(List list) throws ParseException { + private void consumeByteString(List list) + throws ParseException { final char quote = currentToken.length() > 0 ? currentToken.charAt(0) : '\0'; if (quote != '\"' && quote != '\'') { @@ -988,6 +1085,16 @@ public final class TextFormat { } } + private static final Parser PARSER = Parser.newBuilder().build(); + + /** + * Return a {@link Parser} instance which can parse text-format + * messages. The returned instance is thread-safe. + */ + public static Parser getParser() { + return PARSER; + } + /** * Parse a text-format message from {@code input} and merge the contents * into {@code builder}. @@ -995,7 +1102,7 @@ public final class TextFormat { public static void merge(final Readable input, final Message.Builder builder) throws IOException { - merge(input, ExtensionRegistry.getEmptyRegistry(), builder); + PARSER.merge(input, builder); } /** @@ -1005,7 +1112,7 @@ public final class TextFormat { public static void merge(final CharSequence input, final Message.Builder builder) throws ParseException { - merge(input, ExtensionRegistry.getEmptyRegistry(), builder); + PARSER.merge(input, builder); } /** @@ -1017,35 +1124,9 @@ public final class TextFormat { final ExtensionRegistry extensionRegistry, final Message.Builder builder) throws IOException { - // Read the entire input to a String then parse that. - - // If StreamTokenizer were not quite so crippled, or if there were a kind - // of Reader that could read in chunks that match some particular regex, - // or if we wanted to write a custom Reader to tokenize our stream, then - // we would not have to read to one big String. Alas, none of these is - // the case. Oh well. - - merge(toStringBuilder(input), extensionRegistry, builder); + PARSER.merge(input, extensionRegistry, builder); } - private static final int BUFFER_SIZE = 4096; - - // TODO(chrisn): See if working around java.io.Reader#read(CharBuffer) - // overhead is worthwhile - private static StringBuilder toStringBuilder(final Readable input) - throws IOException { - final StringBuilder text = new StringBuilder(); - final CharBuffer buffer = CharBuffer.allocate(BUFFER_SIZE); - while (true) { - final int n = input.read(buffer); - if (n == -1) { - break; - } - buffer.flip(); - text.append(buffer, 0, n); - } - return text; - } /** * Parse a text-format message from {@code input} and merge the contents @@ -1056,187 +1137,481 @@ public final class TextFormat { final ExtensionRegistry extensionRegistry, final Message.Builder builder) throws ParseException { - final Tokenizer tokenizer = new Tokenizer(input); - - while (!tokenizer.atEnd()) { - mergeField(tokenizer, extensionRegistry, builder); - } + PARSER.merge(input, extensionRegistry, builder); } + /** - * Parse a single field from {@code tokenizer} and merge it into - * {@code builder}. + * Parser for text-format proto2 instances. This class is thread-safe. + * The implementation largely follows google/protobuf/text_format.cc. + * + *

Use {@link TextFormat#getParser()} to obtain the default parser, or + * {@link Builder} to control the parser behavior. */ - private static void mergeField(final Tokenizer tokenizer, - final ExtensionRegistry extensionRegistry, - final Message.Builder builder) - throws ParseException { - FieldDescriptor field; - final Descriptor type = builder.getDescriptorForType(); - ExtensionRegistry.ExtensionInfo extension = null; + public static class Parser { + /** + * Determines if repeated values for non-repeated fields and + * oneofs are permitted. For example, given required/optional field "foo" + * and a oneof containing "baz" and "qux": + *

  • + *
      "foo: 1 foo: 2" + *
        "baz: 1 qux: 2" + *
          merging "foo: 2" into a proto in which foo is already set, or + *
            merging "qux: 2" into a proto in which baz is already set. + * + */ + public enum SingularOverwritePolicy { + /** The last value is retained. */ + ALLOW_SINGULAR_OVERWRITES, + /** An error is issued. */ + FORBID_SINGULAR_OVERWRITES + } + + private final boolean allowUnknownFields; + private final SingularOverwritePolicy singularOverwritePolicy; + + private Parser(boolean allowUnknownFields, + SingularOverwritePolicy singularOverwritePolicy) { + this.allowUnknownFields = allowUnknownFields; + this.singularOverwritePolicy = singularOverwritePolicy; + } - if (tokenizer.tryConsume("[")) { - // An extension. - final StringBuilder name = - new StringBuilder(tokenizer.consumeIdentifier()); - while (tokenizer.tryConsume(".")) { - name.append('.'); - name.append(tokenizer.consumeIdentifier()); + /** + * Returns a new instance of {@link Builder}. + */ + public static Builder newBuilder() { + return new Builder(); + } + + /** + * Builder that can be used to obtain new instances of {@link Parser}. + */ + public static class Builder { + private boolean allowUnknownFields = false; + private SingularOverwritePolicy singularOverwritePolicy = + SingularOverwritePolicy.ALLOW_SINGULAR_OVERWRITES; + + /** + * Set whether this parser will allow unknown fields. By default, an + * exception is thrown if an unknown field is encountered. If this is + * set, the parser will only log a warning. + * + *

            Use of this parameter is discouraged. See: + * https://sites.google.com/a/google.com/protocol-buffers/migration/ + * proto2-faq#How_do_I_ignore_unknown_fields_w + * for more details. + */ + public Builder setAllowUnknownFields(boolean allowUnknownFields) { + this.allowUnknownFields = allowUnknownFields; + return this; } - extension = extensionRegistry.findExtensionByName(name.toString()); + /** + * Sets parser behavior when a non-repeated field appears more than once. + */ + public Builder setSingularOverwritePolicy(SingularOverwritePolicy p) { + this.singularOverwritePolicy = p; + return this; + } - if (extension == null) { - throw tokenizer.parseExceptionPreviousToken( - "Extension \"" + name + "\" not found in the ExtensionRegistry."); - } else if (extension.descriptor.getContainingType() != type) { - throw tokenizer.parseExceptionPreviousToken( - "Extension \"" + name + "\" does not extend message type \"" + - type.getFullName() + "\"."); + public Parser build() { + return new Parser(allowUnknownFields, singularOverwritePolicy); } + } - tokenizer.consume("]"); + /** + * Parse a text-format message from {@code input} and merge the contents + * into {@code builder}. + */ + public void merge(final Readable input, + final Message.Builder builder) + throws IOException { + merge(input, ExtensionRegistry.getEmptyRegistry(), builder); + } - field = extension.descriptor; - } else { - final String name = tokenizer.consumeIdentifier(); - field = type.findFieldByName(name); + /** + * Parse a text-format message from {@code input} and merge the contents + * into {@code builder}. + */ + public void merge(final CharSequence input, + final Message.Builder builder) + throws ParseException { + merge(input, ExtensionRegistry.getEmptyRegistry(), builder); + } - // Group names are expected to be capitalized as they appear in the - // .proto file, which actually matches their type names, not their field - // names. - if (field == null) { - // Explicitly specify US locale so that this code does not break when - // executing in Turkey. - final String lowerName = name.toLowerCase(Locale.US); - field = type.findFieldByName(lowerName); - // If the case-insensitive match worked but the field is NOT a group, - if (field != null && field.getType() != FieldDescriptor.Type.GROUP) { - field = null; + /** + * Parse a text-format message from {@code input} and merge the contents + * into {@code builder}. Extensions will be recognized if they are + * registered in {@code extensionRegistry}. + */ + public void merge(final Readable input, + final ExtensionRegistry extensionRegistry, + final Message.Builder builder) + throws IOException { + // Read the entire input to a String then parse that. + + // If StreamTokenizer were not quite so crippled, or if there were a kind + // of Reader that could read in chunks that match some particular regex, + // or if we wanted to write a custom Reader to tokenize our stream, then + // we would not have to read to one big String. Alas, none of these is + // the case. Oh well. + + merge(toStringBuilder(input), extensionRegistry, builder); + } + + + private static final int BUFFER_SIZE = 4096; + + // TODO(chrisn): See if working around java.io.Reader#read(CharBuffer) + // overhead is worthwhile + private static StringBuilder toStringBuilder(final Readable input) + throws IOException { + final StringBuilder text = new StringBuilder(); + final CharBuffer buffer = CharBuffer.allocate(BUFFER_SIZE); + while (true) { + final int n = input.read(buffer); + if (n == -1) { + break; } + buffer.flip(); + text.append(buffer, 0, n); } - // Again, special-case group names as described above. - if (field != null && field.getType() == FieldDescriptor.Type.GROUP && - !field.getMessageType().getName().equals(name)) { - field = null; - } + return text; + } - if (field == null) { - throw tokenizer.parseExceptionPreviousToken( - "Message type \"" + type.getFullName() + - "\" has no field named \"" + name + "\"."); + /** + * Parse a text-format message from {@code input} and merge the contents + * into {@code builder}. Extensions will be recognized if they are + * registered in {@code extensionRegistry}. + */ + public void merge(final CharSequence input, + final ExtensionRegistry extensionRegistry, + final Message.Builder builder) + throws ParseException { + final Tokenizer tokenizer = new Tokenizer(input); + MessageReflection.BuilderAdapter target = + new MessageReflection.BuilderAdapter(builder); + + while (!tokenizer.atEnd()) { + mergeField(tokenizer, extensionRegistry, target); } } - Object value = null; - if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { - tokenizer.tryConsume(":"); // optional + /** + * Parse a single field from {@code tokenizer} and merge it into + * {@code builder}. + */ + private void mergeField(final Tokenizer tokenizer, + final ExtensionRegistry extensionRegistry, + final MessageReflection.MergeTarget target) + throws ParseException { + FieldDescriptor field = null; + final Descriptor type = target.getDescriptorForType(); + ExtensionRegistry.ExtensionInfo extension = null; + + if (tokenizer.tryConsume("[")) { + // An extension. + final StringBuilder name = + new StringBuilder(tokenizer.consumeIdentifier()); + while (tokenizer.tryConsume(".")) { + name.append('.'); + name.append(tokenizer.consumeIdentifier()); + } - final String endToken; - if (tokenizer.tryConsume("<")) { - endToken = ">"; - } else { - tokenizer.consume("{"); - endToken = "}"; - } + extension = target.findExtensionByName( + extensionRegistry, name.toString()); + + if (extension == null) { + if (!allowUnknownFields) { + throw tokenizer.parseExceptionPreviousToken( + "Extension \"" + name + "\" not found in the ExtensionRegistry."); + } else { + logger.warning( + "Extension \"" + name + "\" not found in the ExtensionRegistry."); + } + } else { + if (extension.descriptor.getContainingType() != type) { + throw tokenizer.parseExceptionPreviousToken( + "Extension \"" + name + "\" does not extend message type \"" + + type.getFullName() + "\"."); + } + field = extension.descriptor; + } - final Message.Builder subBuilder; - if (extension == null) { - subBuilder = builder.newBuilderForField(field); + tokenizer.consume("]"); } else { - subBuilder = extension.defaultInstance.newBuilderForType(); - } + final String name = tokenizer.consumeIdentifier(); + field = type.findFieldByName(name); + + // Group names are expected to be capitalized as they appear in the + // .proto file, which actually matches their type names, not their field + // names. + if (field == null) { + // Explicitly specify US locale so that this code does not break when + // executing in Turkey. + final String lowerName = name.toLowerCase(Locale.US); + field = type.findFieldByName(lowerName); + // If the case-insensitive match worked but the field is NOT a group, + if (field != null && field.getType() != FieldDescriptor.Type.GROUP) { + field = null; + } + } + // Again, special-case group names as described above. + if (field != null && field.getType() == FieldDescriptor.Type.GROUP && + !field.getMessageType().getName().equals(name)) { + field = null; + } - while (!tokenizer.tryConsume(endToken)) { - if (tokenizer.atEnd()) { - throw tokenizer.parseException( - "Expected \"" + endToken + "\"."); + if (field == null) { + if (!allowUnknownFields) { + throw tokenizer.parseExceptionPreviousToken( + "Message type \"" + type.getFullName() + + "\" has no field named \"" + name + "\"."); + } else { + logger.warning( + "Message type \"" + type.getFullName() + + "\" has no field named \"" + name + "\"."); + } } - mergeField(tokenizer, extensionRegistry, subBuilder); } - value = subBuilder.buildPartial(); + // Skips unknown fields. + if (field == null) { + // Try to guess the type of this field. + // If this field is not a message, there should be a ":" between the + // field name and the field value and also the field value should not + // start with "{" or "<" which indicates the begining 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("<")) { + skipFieldValue(tokenizer); + } else { + skipFieldMessage(tokenizer); + } + return; + } - } else { - tokenizer.consume(":"); + // Handle potential ':'. + if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + tokenizer.tryConsume(":"); // optional + } else { + tokenizer.consume(":"); // required + } + // Support specifying repeated field values as a comma-separated list. + // Ex."foo: [1, 2, 3]" + if (field.isRepeated() && tokenizer.tryConsume("[")) { + while (true) { + consumeFieldValue(tokenizer, extensionRegistry, target, field, extension); + if (tokenizer.tryConsume("]")) { + // End of list. + break; + } + tokenizer.consume(","); + } + } else { + consumeFieldValue(tokenizer, extensionRegistry, target, field, extension); + } + } - switch (field.getType()) { - case INT32: - case SINT32: - case SFIXED32: - value = tokenizer.consumeInt32(); - break; + /** + * Parse a single field value from {@code tokenizer} and merge it into + * {@code builder}. + */ + private void consumeFieldValue( + final Tokenizer tokenizer, + final ExtensionRegistry extensionRegistry, + final MessageReflection.MergeTarget target, + final FieldDescriptor field, + final ExtensionRegistry.ExtensionInfo extension) + throws ParseException { + Object value = null; - case INT64: - case SINT64: - case SFIXED64: - value = tokenizer.consumeInt64(); - break; + if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + final String endToken; + if (tokenizer.tryConsume("<")) { + endToken = ">"; + } else { + tokenizer.consume("{"); + endToken = "}"; + } - case UINT32: - case FIXED32: - value = tokenizer.consumeUInt32(); - break; + final MessageReflection.MergeTarget subField; + subField = target.newMergeTargetForField(field, + (extension == null) ? null : extension.defaultInstance); - case UINT64: - case FIXED64: - value = tokenizer.consumeUInt64(); - break; + while (!tokenizer.tryConsume(endToken)) { + if (tokenizer.atEnd()) { + throw tokenizer.parseException( + "Expected \"" + endToken + "\"."); + } + mergeField(tokenizer, extensionRegistry, subField); + } - case FLOAT: - value = tokenizer.consumeFloat(); - break; + value = subField.finish(); - case DOUBLE: - value = tokenizer.consumeDouble(); - break; + } else { + switch (field.getType()) { + case INT32: + case SINT32: + case SFIXED32: + value = tokenizer.consumeInt32(); + break; + + case INT64: + case SINT64: + case SFIXED64: + value = tokenizer.consumeInt64(); + break; + + case UINT32: + case FIXED32: + value = tokenizer.consumeUInt32(); + break; + + case UINT64: + case FIXED64: + value = tokenizer.consumeUInt64(); + break; + + case FLOAT: + value = tokenizer.consumeFloat(); + break; + + case DOUBLE: + value = tokenizer.consumeDouble(); + break; + + case BOOL: + value = tokenizer.consumeBoolean(); + break; + + case STRING: + value = tokenizer.consumeString(); + break; + + case BYTES: + value = tokenizer.consumeByteString(); + break; + + case ENUM: + final EnumDescriptor enumType = field.getEnumType(); + + if (tokenizer.lookingAtInteger()) { + final int number = tokenizer.consumeInt32(); + value = enumType.findValueByNumber(number); + if (value == null) { + throw tokenizer.parseExceptionPreviousToken( + "Enum type \"" + enumType.getFullName() + + "\" has no value with number " + number + '.'); + } + } else { + final String id = tokenizer.consumeIdentifier(); + value = enumType.findValueByName(id); + if (value == null) { + throw tokenizer.parseExceptionPreviousToken( + "Enum type \"" + enumType.getFullName() + + "\" has no value named \"" + id + "\"."); + } + } - case BOOL: - value = tokenizer.consumeBoolean(); - break; + break; - case STRING: - value = tokenizer.consumeString(); - break; + case MESSAGE: + case GROUP: + throw new RuntimeException("Can't get here."); + } + } - case BYTES: - value = tokenizer.consumeByteString(); - break; + if (field.isRepeated()) { + target.addRepeatedField(field, value); + } else if ((singularOverwritePolicy + == SingularOverwritePolicy.FORBID_SINGULAR_OVERWRITES) + && target.hasField(field)) { + throw tokenizer.parseExceptionPreviousToken("Non-repeated field \"" + + field.getFullName() + "\" cannot be overwritten."); + } else if ((singularOverwritePolicy + == SingularOverwritePolicy.FORBID_SINGULAR_OVERWRITES) + && field.getContainingOneof() != null + && target.hasOneof(field.getContainingOneof())) { + Descriptors.OneofDescriptor oneof = field.getContainingOneof(); + throw tokenizer.parseExceptionPreviousToken("Field \"" + + field.getFullName() + "\" is specified along with field \"" + + target.getOneofFieldDescriptor(oneof).getFullName() + + "\", another member of oneof \"" + oneof.getName() + "\"."); + } else { + target.setField(field, value); + } + } - case ENUM: - final EnumDescriptor enumType = field.getEnumType(); - - if (tokenizer.lookingAtInteger()) { - final int number = tokenizer.consumeInt32(); - value = enumType.findValueByNumber(number); - if (value == null) { - throw tokenizer.parseExceptionPreviousToken( - "Enum type \"" + enumType.getFullName() + - "\" has no value with number " + number + '.'); - } - } else { - final String id = tokenizer.consumeIdentifier(); - value = enumType.findValueByName(id); - if (value == null) { - throw tokenizer.parseExceptionPreviousToken( - "Enum type \"" + enumType.getFullName() + - "\" has no value named \"" + id + "\"."); - } - } + /** + * Skips the next field including the field's name and value. + */ + private void skipField(Tokenizer tokenizer) throws ParseException { + if (tokenizer.tryConsume("[")) { + // Extension name. + do { + tokenizer.consumeIdentifier(); + } while (tokenizer.tryConsume(".")); + tokenizer.consume("]"); + } else { + tokenizer.consumeIdentifier(); + } - break; + // Try to guess the type of this field. + // If this field is not a message, there should be a ":" between the + // field name and the field value and also the field value should not + // start with "{" or "<" which indicates the begining 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("{")) { + skipFieldValue(tokenizer); + } else { + skipFieldMessage(tokenizer); + } + // For historical reasons, fields may optionally be separated by commas or + // semicolons. + if (!tokenizer.tryConsume(";")) { + tokenizer.tryConsume(","); + } + } - case MESSAGE: - case GROUP: - throw new RuntimeException("Can't get here."); + /** + * Skips the whole body of a message including the beginning delimeter and + * the ending delimeter. + */ + private void skipFieldMessage(Tokenizer tokenizer) throws ParseException { + final String delimiter; + if (tokenizer.tryConsume("<")) { + delimiter = ">"; + } else { + tokenizer.consume("{"); + delimiter = "}"; } + while (!tokenizer.lookingAt(">") && !tokenizer.lookingAt("}")) { + skipField(tokenizer); + } + tokenizer.consume(delimiter); } - if (field.isRepeated()) { - builder.addRepeatedField(field, value); - } else { - builder.setField(field, value); + /** + * Skips a field value. + */ + private void skipFieldValue(Tokenizer tokenizer) throws ParseException { + if (tokenizer.tryConsumeString()) { + while (tokenizer.tryConsumeString()) {} + return; + } + 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); + } } } @@ -1246,6 +1621,11 @@ public final class TextFormat { // Some of these methods are package-private because Descriptors.java uses // them. + private interface ByteSequence { + int size(); + byte byteAt(int offset); + } + /** * Escapes bytes in the format used in protocol buffer text format, which * is the same as the format used for C string literals. All bytes @@ -1254,7 +1634,7 @@ 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) { + private 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); @@ -1288,6 +1668,39 @@ public final class TextFormat { return builder.toString(); } + /** + * Escapes bytes in the format used in protocol buffer text format, which + * is the same as the format used for C string literals. All bytes + * that are not printable 7-bit ASCII characters are escaped, as well as + * backslash, single-quote, and double-quote characters. Characters for + * which no defined short-hand escape sequence is defined will be escaped + * using 3-digit octal sequences. + */ + static String escapeBytes(final ByteString input) { + return escapeBytes(new ByteSequence() { + public int size() { + return input.size(); + } + public byte byteAt(int offset) { + return input.byteAt(offset); + } + }); + } + + /** + * Like {@link #escapeBytes(ByteString)}, but used for byte array. + */ + static String escapeBytes(final byte[] input) { + return escapeBytes(new ByteSequence() { + public int size() { + return input.length; + } + public byte byteAt(int offset) { + return input[offset]; + } + }); + } + /** * Un-escape a byte sequence as escaped using * {@link #escapeBytes(ByteString)}. Two-digit hex escapes (starting with @@ -1393,6 +1806,13 @@ public final class TextFormat { return escapeBytes(ByteString.copyFromUtf8(input)); } + /** + * Escape double quotes and backslashes in a String for unicode output of a message. + */ + public static String escapeDoubleQuotesAndBackslashes(final String input) { + return input.replace("\\", "\\\\").replace("\"", "\\\""); + } + /** * Un-escape a text string as escaped using {@link #escapeText(String)}. * Two-digit hex escapes (starting with "\x") are also recognized. diff --git a/java/src/main/java/com/google/protobuf/UnknownFieldSet.java b/java/src/main/java/com/google/protobuf/UnknownFieldSet.java index 45e2e6e4..81432b40 100644 --- a/java/src/main/java/com/google/protobuf/UnknownFieldSet.java +++ b/java/src/main/java/com/google/protobuf/UnknownFieldSet.java @@ -91,6 +91,7 @@ public final class UnknownFieldSet implements MessageLite { } private Map fields; + @Override public boolean equals(final Object other) { if (this == other) { @@ -367,6 +368,22 @@ public final class UnknownFieldSet implements MessageLite { reinitialize(); return this; } + + /** Clear fields from the set with a given field number. */ + public Builder clearField(final int number) { + if (number == 0) { + throw new IllegalArgumentException("Zero is not a valid field number."); + } + if (lastField != null && lastFieldNumber == number) { + // Discard this. + lastField = null; + lastFieldNumber = 0; + } + if (fields.containsKey(number)) { + fields.remove(number); + } + return this; + } /** * Merge the fields from {@code other} into this set. If a field number diff --git a/java/src/main/java/com/google/protobuf/UnmodifiableLazyStringList.java b/java/src/main/java/com/google/protobuf/UnmodifiableLazyStringList.java index 591bca54..6f9905fc 100644 --- a/java/src/main/java/com/google/protobuf/UnmodifiableLazyStringList.java +++ b/java/src/main/java/com/google/protobuf/UnmodifiableLazyStringList.java @@ -31,10 +31,12 @@ package com.google.protobuf; import java.util.AbstractList; -import java.util.RandomAccess; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.ListIterator; -import java.util.Iterator; +import java.util.RandomAccess; /** * An implementation of {@link LazyStringList} that wraps another @@ -72,6 +74,36 @@ public class UnmodifiableLazyStringList extends AbstractList } //@Override (Java 1.6 override semantics, but we must support 1.5) + public void set(int index, ByteString element) { + throw new UnsupportedOperationException(); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public boolean addAllByteString(Collection element) { + throw new UnsupportedOperationException(); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public byte[] getByteArray(int index) { + return list.getByteArray(index); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public void add(byte[] element) { + throw new UnsupportedOperationException(); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public void set(int index, byte[] element) { + throw new UnsupportedOperationException(); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public boolean addAllByteArray(Collection element) { + throw new UnsupportedOperationException(); + } + + @Override public ListIterator listIterator(final int index) { return new ListIterator() { ListIterator iter = list.listIterator(index); @@ -145,8 +177,29 @@ public class UnmodifiableLazyStringList extends AbstractList }; } + //@Override (Java 1.6 override semantics, but we must support 1.5) public List getUnderlyingElements() { // The returned value is already unmodifiable. return list.getUnderlyingElements(); } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public void mergeFrom(LazyStringList other) { + throw new UnsupportedOperationException(); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public List asByteArrayList() { + return Collections.unmodifiableList(list.asByteArrayList()); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public List asByteStringList() { + return Collections.unmodifiableList(list.asByteStringList()); + } + + //@Override (Java 1.6 override semantics, but we must support 1.5) + public LazyStringList getUnmodifiableView() { + return this; + } } diff --git a/java/src/test/java/com/google/protobuf/AbstractMessageTest.java b/java/src/test/java/com/google/protobuf/AbstractMessageTest.java index 3d05cb7d..fcbf019d 100644 --- a/java/src/test/java/com/google/protobuf/AbstractMessageTest.java +++ b/java/src/test/java/com/google/protobuf/AbstractMessageTest.java @@ -506,4 +506,22 @@ public class AbstractMessageTest extends TestCase { String.format("%s should have a different hash code from %s", m1, m2), m1.hashCode() == m2.hashCode()); } + + public void testCheckByteStringIsUtf8OnUtf8() { + ByteString byteString = ByteString.copyFromUtf8("some text"); + AbstractMessageLite.checkByteStringIsUtf8(byteString); + // No exception thrown. + } + + public void testCheckByteStringIsUtf8OnNonUtf8() { + ByteString byteString = + ByteString.copyFrom(new byte[]{(byte) 0x80}); // A lone continuation byte. + try { + AbstractMessageLite.checkByteStringIsUtf8(byteString); + fail("Expected AbstractMessageLite.checkByteStringIsUtf8 to throw IllegalArgumentException"); + } catch (IllegalArgumentException exception) { + assertEquals("Byte string is not UTF-8.", exception.getMessage()); + } + } + } diff --git a/java/src/test/java/com/google/protobuf/ByteStringTest.java b/java/src/test/java/com/google/protobuf/ByteStringTest.java index 7a1d6823..88d7e779 100644 --- a/java/src/test/java/com/google/protobuf/ByteStringTest.java +++ b/java/src/test/java/com/google/protobuf/ByteStringTest.java @@ -35,6 +35,7 @@ import com.google.protobuf.ByteString.Output; import junit.framework.TestCase; import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -676,6 +677,21 @@ public class ByteStringTest extends TestCase { assertTrue(ByteString.EMPTY.startsWith(ByteString.EMPTY)); } + public void testEndsWith() { + byte[] bytes = getTestBytes(1000, 1234L); + ByteString string = ByteString.copyFrom(bytes); + ByteString prefix = ByteString.copyFrom(bytes, 0, 500); + ByteString suffix = ByteString.copyFrom(bytes, 400, 600); + assertTrue(string.endsWith(ByteString.EMPTY)); + assertTrue(string.endsWith(string)); + assertTrue(string.endsWith(suffix)); + assertFalse(string.endsWith(prefix)); + assertFalse(suffix.endsWith(prefix)); + assertFalse(prefix.endsWith(suffix)); + assertFalse(ByteString.EMPTY.endsWith(suffix)); + assertTrue(ByteString.EMPTY.endsWith(ByteString.EMPTY)); + } + static List makeConcretePieces(byte[] referenceBytes) { List pieces = new ArrayList(); // Starting length should be small enough that we'll do some concatenating by @@ -689,4 +705,55 @@ public class ByteStringTest extends TestCase { } return pieces; } + + private byte[] substringUsingWriteTo( + ByteString data, int offset, int length) throws IOException { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + data.writeTo(output, offset, length); + return output.toByteArray(); + } + + public void testWriteToOutputStream() throws Exception { + // Choose a size large enough so when two ByteStrings are concatenated they + // won't be merged into one byte array due to some optimizations. + final int dataSize = ByteString.CONCATENATE_BY_COPY_SIZE + 1; + byte[] data1 = new byte[dataSize]; + for (int i = 0; i < data1.length; i++) { + data1[i] = (byte) 1; + } + data1[1] = (byte) 11; + // Test LiteralByteString.writeTo(OutputStream,int,int) + LiteralByteString left = new LiteralByteString(data1); + byte[] result = substringUsingWriteTo(left, 1, 1); + assertEquals(1, result.length); + assertEquals((byte) 11, result[0]); + + byte[] data2 = new byte[dataSize]; + for (int i = 0; i < data1.length; i++) { + data2[i] = (byte) 2; + } + LiteralByteString right = new LiteralByteString(data2); + // Concatenate two ByteStrings to create a RopeByteString. + ByteString root = left.concat(right); + // Make sure we are actually testing a RopeByteString with a simple tree + // structure. + assertEquals(1, root.getTreeDepth()); + // Write parts of the left node. + result = substringUsingWriteTo(root, 0, dataSize); + assertEquals(dataSize, result.length); + assertEquals((byte) 1, result[0]); + assertEquals((byte) 1, result[dataSize - 1]); + // Write parts of the right node. + result = substringUsingWriteTo(root, dataSize, dataSize); + assertEquals(dataSize, result.length); + assertEquals((byte) 2, result[0]); + assertEquals((byte) 2, result[dataSize - 1]); + // Write a segment of bytes that runs across both nodes. + result = substringUsingWriteTo(root, dataSize / 2, dataSize); + assertEquals(dataSize, result.length); + assertEquals((byte) 1, result[0]); + assertEquals((byte) 1, result[dataSize - dataSize / 2 - 1]); + assertEquals((byte) 2, result[dataSize - dataSize / 2]); + assertEquals((byte) 2, result[dataSize - 1]); + } } diff --git a/java/src/test/java/com/google/protobuf/CheckUtf8Test.java b/java/src/test/java/com/google/protobuf/CheckUtf8Test.java new file mode 100644 index 00000000..97bf1c7a --- /dev/null +++ b/java/src/test/java/com/google/protobuf/CheckUtf8Test.java @@ -0,0 +1,141 @@ +// 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 proto2_test_check_utf8.TestCheckUtf8.BytesWrapper; +import proto2_test_check_utf8.TestCheckUtf8.StringWrapper; +import proto2_test_check_utf8_size.TestCheckUtf8Size.BytesWrapperSize; +import proto2_test_check_utf8_size.TestCheckUtf8Size.StringWrapperSize; +import junit.framework.TestCase; + +/** + * Test that protos generated with file option java_string_check_utf8 do in + * fact perform appropriate UTF-8 checks. + * + * @author jbaum@google.com (Jacob Butcher) + */ +public class CheckUtf8Test extends TestCase { + + private static final String UTF8_BYTE_STRING_TEXT = "some text"; + private static final ByteString UTF8_BYTE_STRING = + ByteString.copyFromUtf8(UTF8_BYTE_STRING_TEXT); + private static final ByteString NON_UTF8_BYTE_STRING = + ByteString.copyFrom(new byte[]{(byte) 0x80}); // A lone continuation byte. + + public void testBuildRequiredStringWithGoodUtf8() throws Exception { + assertEquals(UTF8_BYTE_STRING_TEXT, + StringWrapper.newBuilder().setReqBytes(UTF8_BYTE_STRING).getReq()); + } + + public void testParseRequiredStringWithGoodUtf8() throws Exception { + ByteString serialized = + BytesWrapper.newBuilder().setReq(UTF8_BYTE_STRING).build().toByteString(); + assertEquals(UTF8_BYTE_STRING_TEXT, + StringWrapper.PARSER.parseFrom(serialized).getReq()); + } + + public void testBuildRequiredStringWithBadUtf8() throws Exception { + try { + StringWrapper.newBuilder().setReqBytes(NON_UTF8_BYTE_STRING); + fail("Expected IllegalArgumentException for non UTF-8 byte string."); + } catch (IllegalArgumentException exception) { + assertEquals("Byte string is not UTF-8.", exception.getMessage()); + } + } + + public void testBuildOptionalStringWithBadUtf8() throws Exception { + try { + StringWrapper.newBuilder().setOptBytes(NON_UTF8_BYTE_STRING); + fail("Expected IllegalArgumentException for non UTF-8 byte string."); + } catch (IllegalArgumentException exception) { + assertEquals("Byte string is not UTF-8.", exception.getMessage()); + } + } + + public void testBuildRepeatedStringWithBadUtf8() throws Exception { + try { + StringWrapper.newBuilder().addRepBytes(NON_UTF8_BYTE_STRING); + fail("Expected IllegalArgumentException for non UTF-8 byte string."); + } catch (IllegalArgumentException exception) { + assertEquals("Byte string is not UTF-8.", exception.getMessage()); + } + } + + public void testParseRequiredStringWithBadUtf8() throws Exception { + ByteString serialized = + BytesWrapper.newBuilder().setReq(NON_UTF8_BYTE_STRING).build().toByteString(); + try { + StringWrapper.PARSER.parseFrom(serialized); + fail("Expected InvalidProtocolBufferException for non UTF-8 byte string."); + } catch (InvalidProtocolBufferException exception) { + assertEquals("Protocol message had invalid UTF-8.", exception.getMessage()); + } + } + + public void testBuildRequiredStringWithBadUtf8Size() throws Exception { + try { + StringWrapperSize.newBuilder().setReqBytes(NON_UTF8_BYTE_STRING); + fail("Expected IllegalArgumentException for non UTF-8 byte string."); + } catch (IllegalArgumentException exception) { + assertEquals("Byte string is not UTF-8.", exception.getMessage()); + } + } + + public void testBuildOptionalStringWithBadUtf8Size() throws Exception { + try { + StringWrapperSize.newBuilder().setOptBytes(NON_UTF8_BYTE_STRING); + fail("Expected IllegalArgumentException for non UTF-8 byte string."); + } catch (IllegalArgumentException exception) { + assertEquals("Byte string is not UTF-8.", exception.getMessage()); + } + } + + public void testBuildRepeatedStringWithBadUtf8Size() throws Exception { + try { + StringWrapperSize.newBuilder().addRepBytes(NON_UTF8_BYTE_STRING); + fail("Expected IllegalArgumentException for non UTF-8 byte string."); + } catch (IllegalArgumentException exception) { + assertEquals("Byte string is not UTF-8.", exception.getMessage()); + } + } + + public void testParseRequiredStringWithBadUtf8Size() throws Exception { + ByteString serialized = + BytesWrapperSize.newBuilder().setReq(NON_UTF8_BYTE_STRING).build().toByteString(); + try { + StringWrapperSize.PARSER.parseFrom(serialized); + fail("Expected InvalidProtocolBufferException for non UTF-8 byte string."); + } catch (InvalidProtocolBufferException exception) { + assertEquals("Protocol message had invalid UTF-8.", exception.getMessage()); + } + } + +} diff --git a/java/src/test/java/com/google/protobuf/CodedInputStreamTest.java b/java/src/test/java/com/google/protobuf/CodedInputStreamTest.java index 83f7f8da..3b50e497 100644 --- a/java/src/test/java/com/google/protobuf/CodedInputStreamTest.java +++ b/java/src/test/java/com/google/protobuf/CodedInputStreamTest.java @@ -30,15 +30,20 @@ package com.google.protobuf; +import protobuf_unittest.UnittestProto.BoolMessage; +import protobuf_unittest.UnittestProto.Int32Message; +import protobuf_unittest.UnittestProto.Int64Message; import protobuf_unittest.UnittestProto.TestAllTypes; import protobuf_unittest.UnittestProto.TestRecursiveMessage; import junit.framework.TestCase; import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.FilterInputStream; -import java.io.InputStream; import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; /** * Unit test for {@link CodedInputStream}. @@ -85,28 +90,54 @@ public class CodedInputStreamTest extends TestCase { } } + private void assertDataConsumed(byte[] data, CodedInputStream input) + throws IOException { + assertEquals(data.length, input.getTotalBytesRead()); + assertTrue(input.isAtEnd()); + } + /** * Parses the given bytes using readRawVarint32() and readRawVarint64() and * checks that the result matches the given value. */ private void assertReadVarint(byte[] data, long value) throws Exception { CodedInputStream input = CodedInputStream.newInstance(data); - assertEquals((int)value, input.readRawVarint32()); + assertEquals((int) value, input.readRawVarint32()); + assertDataConsumed(data, input); input = CodedInputStream.newInstance(data); assertEquals(value, input.readRawVarint64()); - assertTrue(input.isAtEnd()); + assertDataConsumed(data, input); + + input = CodedInputStream.newInstance(data); + assertEquals(value, input.readRawVarint64SlowPath()); + assertDataConsumed(data, input); + + input = CodedInputStream.newInstance(data); + assertTrue(input.skipField(WireFormat.WIRETYPE_VARINT)); + assertDataConsumed(data, input); // Try different block sizes. for (int blockSize = 1; blockSize <= 16; blockSize *= 2) { input = CodedInputStream.newInstance( new SmallBlockInputStream(data, blockSize)); - assertEquals((int)value, input.readRawVarint32()); + assertEquals((int) value, input.readRawVarint32()); + assertDataConsumed(data, input); input = CodedInputStream.newInstance( new SmallBlockInputStream(data, blockSize)); assertEquals(value, input.readRawVarint64()); - assertTrue(input.isAtEnd()); + assertDataConsumed(data, input); + + input = CodedInputStream.newInstance( + new SmallBlockInputStream(data, blockSize)); + assertEquals(value, input.readRawVarint64SlowPath()); + assertDataConsumed(data, input); + + input = CodedInputStream.newInstance( + new SmallBlockInputStream(data, blockSize)); + assertTrue(input.skipField(WireFormat.WIRETYPE_VARINT)); + assertDataConsumed(data, input); } // Try reading direct from an InputStream. We want to verify that it @@ -115,7 +146,7 @@ public class CodedInputStreamTest extends TestCase { byte[] longerData = new byte[data.length + 1]; System.arraycopy(data, 0, longerData, 0, data.length); InputStream rawInput = new ByteArrayInputStream(longerData); - assertEquals((int)value, CodedInputStream.readRawVarint32(rawInput)); + assertEquals((int) value, CodedInputStream.readRawVarint32(rawInput)); assertEquals(1, rawInput.available()); } @@ -143,6 +174,14 @@ public class CodedInputStreamTest extends TestCase { assertEquals(expected.getMessage(), e.getMessage()); } + input = CodedInputStream.newInstance(data); + try { + input.readRawVarint64SlowPath(); + fail("Should have thrown an exception."); + } catch (InvalidProtocolBufferException e) { + assertEquals(expected.getMessage(), e.getMessage()); + } + // Make sure we get the same error when reading direct from an InputStream. try { CodedInputStream.readRawVarint32(new ByteArrayInputStream(data)); @@ -311,6 +350,7 @@ public class CodedInputStreamTest extends TestCase { } } + /** * Test that a bug in skipRawBytes() has been fixed: if the skip skips * exactly up to a limit, this should not break things. @@ -350,7 +390,7 @@ public class CodedInputStreamTest extends TestCase { // Allocate and initialize a 1MB blob. byte[] blob = new byte[1 << 20]; for (int i = 0; i < blob.length; i++) { - blob[i] = (byte)i; + blob[i] = (byte) i; } // Make a message containing it. @@ -437,16 +477,23 @@ public class CodedInputStreamTest extends TestCase { } } + private void checkSizeLimitExceeded(InvalidProtocolBufferException e) { + assertEquals( + InvalidProtocolBufferException.sizeLimitExceeded().getMessage(), + e.getMessage()); + } + public void testSizeLimit() throws Exception { CodedInputStream input = CodedInputStream.newInstance( - TestUtil.getAllSet().toByteString().newInput()); + new SmallBlockInputStream( + TestUtil.getAllSet().toByteString().newInput(), 16)); input.setSizeLimit(16); try { TestAllTypes.parseFrom(input); fail("Should have thrown an exception!"); - } catch (InvalidProtocolBufferException e) { - // success. + } catch (InvalidProtocolBufferException expected) { + checkSizeLimitExceeded(expected); } } @@ -460,8 +507,8 @@ public class CodedInputStreamTest extends TestCase { try { input.readRawByte(); fail("Should have thrown an exception!"); - } catch (InvalidProtocolBufferException e) { - // success. + } catch (InvalidProtocolBufferException expected) { + checkSizeLimitExceeded(expected); } input.resetSizeCounter(); @@ -469,28 +516,50 @@ public class CodedInputStreamTest extends TestCase { input.readRawByte(); // No exception thrown. input.resetSizeCounter(); assertEquals(0, input.getTotalBytesRead()); + input.readRawBytes(16); + assertEquals(16, input.getTotalBytesRead()); + input.resetSizeCounter(); try { - input.readRawBytes(16); // Hits limit again. + input.readRawBytes(17); // Hits limit again. fail("Should have thrown an exception!"); - } catch (InvalidProtocolBufferException e) { - // success. + } catch (InvalidProtocolBufferException expected) { + checkSizeLimitExceeded(expected); + } + } + + public void testSizeLimitMultipleMessages() throws Exception { + byte[] bytes = new byte[256]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = (byte) i; + } + CodedInputStream input = CodedInputStream.newInstance( + new SmallBlockInputStream(bytes, 7)); + input.setSizeLimit(16); + for (int i = 0; i < 256 / 16; i++) { + byte[] message = input.readRawBytes(16); + for (int j = 0; j < message.length; j++) { + assertEquals(i * 16 + j, message[j] & 0xff); + } + assertEquals(16, input.getTotalBytesRead()); + input.resetSizeCounter(); + assertEquals(0, input.getTotalBytesRead()); } } /** - * Tests that if we read an string that contains invalid UTF-8, no exception + * Tests that if we readString invalid UTF-8 bytes, no exception * is thrown. Instead, the invalid bytes are replaced with the Unicode * "replacement character" U+FFFD. */ - public void testReadInvalidUtf8() throws Exception { + public void testReadStringInvalidUtf8() throws Exception { ByteString.Output rawOutput = ByteString.newOutput(); CodedOutputStream output = CodedOutputStream.newInstance(rawOutput); int tag = WireFormat.makeTag(1, WireFormat.WIRETYPE_LENGTH_DELIMITED); output.writeRawVarint32(tag); output.writeRawVarint32(1); - output.writeRawBytes(new byte[] { (byte)0x80 }); + output.writeRawBytes(new byte[] { (byte) 0x80 }); output.flush(); CodedInputStream input = rawOutput.toByteString().newCodedInput(); @@ -499,13 +568,37 @@ public class CodedInputStreamTest extends TestCase { assertEquals(0xfffd, text.charAt(0)); } + /** + * Tests that if we readStringRequireUtf8 invalid UTF-8 bytes, an + * InvalidProtocolBufferException is thrown. + */ + public void testReadStringRequireUtf8InvalidUtf8() throws Exception { + ByteString.Output rawOutput = ByteString.newOutput(); + CodedOutputStream output = CodedOutputStream.newInstance(rawOutput); + + int tag = WireFormat.makeTag(1, WireFormat.WIRETYPE_LENGTH_DELIMITED); + output.writeRawVarint32(tag); + output.writeRawVarint32(1); + output.writeRawBytes(new byte[] { (byte) 0x80 }); + output.flush(); + + CodedInputStream input = rawOutput.toByteString().newCodedInput(); + assertEquals(tag, input.readTag()); + try { + input.readStringRequireUtf8(); + fail("Expected invalid UTF-8 exception."); + } catch (InvalidProtocolBufferException exception) { + assertEquals("Protocol message had invalid UTF-8.", exception.getMessage()); + } + } + public void testReadFromSlice() throws Exception { byte[] bytes = bytes(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); CodedInputStream in = CodedInputStream.newInstance(bytes, 3, 5); assertEquals(0, in.getTotalBytesRead()); for (int i = 3; i < 8; i++) { assertEquals(i, in.readRawByte()); - assertEquals(i-2, in.getTotalBytesRead()); + assertEquals(i - 2, in.getTotalBytesRead()); } // eof assertEquals(0, in.readTag()); @@ -525,4 +618,152 @@ public class CodedInputStreamTest extends TestCase { } } } + + public void testReadByteArray() throws Exception { + ByteString.Output rawOutput = ByteString.newOutput(); + CodedOutputStream output = CodedOutputStream.newInstance(rawOutput); + // Zero-sized bytes field. + output.writeRawVarint32(0); + // One one-byte bytes field + output.writeRawVarint32(1); + output.writeRawBytes(new byte[] { (byte) 23 }); + // Another one-byte bytes field + output.writeRawVarint32(1); + output.writeRawBytes(new byte[] { (byte) 45 }); + // A bytes field large enough that won't fit into the 4K buffer. + final int bytesLength = 16 * 1024; + byte[] bytes = new byte[bytesLength]; + bytes[0] = (byte) 67; + bytes[bytesLength - 1] = (byte) 89; + output.writeRawVarint32(bytesLength); + output.writeRawBytes(bytes); + + output.flush(); + CodedInputStream inputStream = rawOutput.toByteString().newCodedInput(); + + byte[] result = inputStream.readByteArray(); + assertEquals(0, result.length); + result = inputStream.readByteArray(); + assertEquals(1, result.length); + assertEquals((byte) 23, result[0]); + result = inputStream.readByteArray(); + assertEquals(1, result.length); + assertEquals((byte) 45, result[0]); + result = inputStream.readByteArray(); + assertEquals(bytesLength, result.length); + assertEquals((byte) 67, result[0]); + assertEquals((byte) 89, result[bytesLength - 1]); + } + + public void testReadByteBuffer() throws Exception { + ByteString.Output rawOutput = ByteString.newOutput(); + CodedOutputStream output = CodedOutputStream.newInstance(rawOutput); + // Zero-sized bytes field. + output.writeRawVarint32(0); + // One one-byte bytes field + output.writeRawVarint32(1); + output.writeRawBytes(new byte[]{(byte) 23}); + // Another one-byte bytes field + output.writeRawVarint32(1); + output.writeRawBytes(new byte[]{(byte) 45}); + // A bytes field large enough that won't fit into the 4K buffer. + final int bytesLength = 16 * 1024; + byte[] bytes = new byte[bytesLength]; + bytes[0] = (byte) 67; + bytes[bytesLength - 1] = (byte) 89; + output.writeRawVarint32(bytesLength); + output.writeRawBytes(bytes); + + output.flush(); + CodedInputStream inputStream = rawOutput.toByteString().newCodedInput(); + + ByteBuffer result = inputStream.readByteBuffer(); + assertEquals(0, result.capacity()); + result = inputStream.readByteBuffer(); + assertEquals(1, result.capacity()); + assertEquals((byte) 23, result.get()); + result = inputStream.readByteBuffer(); + assertEquals(1, result.capacity()); + assertEquals((byte) 45, result.get()); + result = inputStream.readByteBuffer(); + assertEquals(bytesLength, result.capacity()); + assertEquals((byte) 67, result.get()); + result.position(bytesLength - 1); + assertEquals((byte) 89, result.get()); + } + + public void testReadByteBufferAliasing() throws Exception { + ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream(); + CodedOutputStream output = CodedOutputStream.newInstance(byteArrayStream); + // Zero-sized bytes field. + output.writeRawVarint32(0); + // One one-byte bytes field + output.writeRawVarint32(1); + output.writeRawBytes(new byte[]{(byte) 23}); + // Another one-byte bytes field + output.writeRawVarint32(1); + output.writeRawBytes(new byte[]{(byte) 45}); + // A bytes field large enough that won't fit into the 4K buffer. + final int bytesLength = 16 * 1024; + byte[] bytes = new byte[bytesLength]; + bytes[0] = (byte) 67; + bytes[bytesLength - 1] = (byte) 89; + output.writeRawVarint32(bytesLength); + output.writeRawBytes(bytes); + output.flush(); + byte[] data = byteArrayStream.toByteArray(); + + // Without aliasing + CodedInputStream inputStream = CodedInputStream.newInstance(data); + ByteBuffer result = inputStream.readByteBuffer(); + assertEquals(0, result.capacity()); + result = inputStream.readByteBuffer(); + assertTrue(result.array() != data); + assertEquals(1, result.capacity()); + assertEquals((byte) 23, result.get()); + result = inputStream.readByteBuffer(); + assertTrue(result.array() != data); + assertEquals(1, result.capacity()); + assertEquals((byte) 45, result.get()); + result = inputStream.readByteBuffer(); + assertTrue(result.array() != data); + assertEquals(bytesLength, result.capacity()); + assertEquals((byte) 67, result.get()); + result.position(bytesLength - 1); + assertEquals((byte) 89, result.get()); + + // Enable aliasing + inputStream = CodedInputStream.newInstance(data); + inputStream.enableAliasing(true); + result = inputStream.readByteBuffer(); + assertEquals(0, result.capacity()); + result = inputStream.readByteBuffer(); + assertTrue(result.array() == data); + assertEquals(1, result.capacity()); + assertEquals((byte) 23, result.get()); + result = inputStream.readByteBuffer(); + assertTrue(result.array() == data); + assertEquals(1, result.capacity()); + assertEquals((byte) 45, result.get()); + result = inputStream.readByteBuffer(); + assertTrue(result.array() == data); + assertEquals(bytesLength, result.capacity()); + assertEquals((byte) 67, result.get()); + result.position(bytesLength - 1); + assertEquals((byte) 89, result.get()); + } + + public void testCompatibleTypes() throws Exception { + long data = 0x100000000L; + Int64Message message = Int64Message.newBuilder().setData(data).build(); + ByteString serialized = message.toByteString(); + + // Test int64(long) is compatible with bool(boolean) + BoolMessage msg2 = BoolMessage.parseFrom(serialized); + assertTrue(msg2.getData()); + + // Test int64(long) is compatible with int32(int) + Int32Message msg3 = Int32Message.parseFrom(serialized); + assertEquals((int) data, msg3.getData()); + } } diff --git a/java/src/test/java/com/google/protobuf/CodedOutputStreamTest.java b/java/src/test/java/com/google/protobuf/CodedOutputStreamTest.java index 86255029..da70be42 100644 --- a/java/src/test/java/com/google/protobuf/CodedOutputStreamTest.java +++ b/java/src/test/java/com/google/protobuf/CodedOutputStreamTest.java @@ -38,6 +38,7 @@ import protobuf_unittest.UnittestProto.TestSparseEnum; import junit.framework.TestCase; import java.io.ByteArrayOutputStream; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; @@ -314,4 +315,87 @@ public class CodedOutputStreamTest extends TestCase { SparseEnumMessage message2 = SparseEnumMessage.parseFrom(rawBytes); assertEquals(TestSparseEnum.SPARSE_E, message2.getSparseEnum()); } + + /** Test getTotalBytesWritten() */ + public void testGetTotalBytesWritten() throws Exception { + final int BUFFER_SIZE = 4 * 1024; + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(BUFFER_SIZE); + CodedOutputStream codedStream = CodedOutputStream.newInstance(outputStream); + byte[] value = "abcde".getBytes("UTF-8"); + for (int i = 0; i < 1024; ++i) { + codedStream.writeRawBytes(value, 0, value.length); + } + // Make sure we have written more bytes than the buffer could hold. This is + // to make the test complete. + assertTrue(codedStream.getTotalBytesWritten() > BUFFER_SIZE); + assertEquals(value.length * 1024, codedStream.getTotalBytesWritten()); + } + + public void testWriteToByteBuffer() throws Exception { + final int bufferSize = 16 * 1024; + ByteBuffer buffer = ByteBuffer.allocate(bufferSize); + CodedOutputStream codedStream = CodedOutputStream.newInstance(buffer); + // Write raw bytes into the ByteBuffer. + final int length1 = 5000; + for (int i = 0; i < length1; i++) { + codedStream.writeRawByte((byte) 1); + } + final int length2 = 8 * 1024; + byte[] data = new byte[length2]; + for (int i = 0; i < length2; i++) { + data[i] = (byte) 2; + } + codedStream.writeRawBytes(data); + final int length3 = bufferSize - length1 - length2; + for (int i = 0; i < length3; i++) { + codedStream.writeRawByte((byte) 3); + } + codedStream.flush(); + + // Check that data is correctly written to the ByteBuffer. + assertEquals(0, buffer.remaining()); + buffer.flip(); + for (int i = 0; i < length1; i++) { + assertEquals((byte) 1, buffer.get()); + } + for (int i = 0; i < length2; i++) { + assertEquals((byte) 2, buffer.get()); + } + for (int i = 0; i < length3; i++) { + assertEquals((byte) 3, buffer.get()); + } + } + + public void testWriteByteBuffer() throws Exception { + byte[] value = "abcde".getBytes("UTF-8"); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + CodedOutputStream codedStream = CodedOutputStream.newInstance(outputStream); + ByteBuffer byteBuffer = ByteBuffer.wrap(value, 0, 1); + // This will actually write 5 bytes into the CodedOutputStream as the + // ByteBuffer's capacity() is 5. + codedStream.writeRawBytes(byteBuffer); + // The above call shouldn't affect the ByteBuffer's state. + assertEquals(0, byteBuffer.position()); + assertEquals(1, byteBuffer.limit()); + + // The correct way to write part of an array using ByteBuffer. + codedStream.writeRawBytes(ByteBuffer.wrap(value, 2, 1).slice()); + + codedStream.flush(); + byte[] result = outputStream.toByteArray(); + assertEquals(6, result.length); + for (int i = 0; i < 5; i++) { + assertEquals(value[i], result[i]); + } + assertEquals(value[2], result[5]); + } + + public void testWriteByteArrayWithOffsets() throws Exception { + byte[] fullArray = bytes(0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88); + byte[] destination = new byte[4]; + CodedOutputStream codedStream = CodedOutputStream.newInstance(destination); + codedStream.writeByteArrayNoTag(fullArray, 2, 2); + assertEqualBytes(bytes(0x02, 0x33, 0x44, 0x00), destination); + assertEquals(3, codedStream.getTotalBytesWritten()); + } } diff --git a/java/src/test/java/com/google/protobuf/DescriptorsTest.java b/java/src/test/java/com/google/protobuf/DescriptorsTest.java index 9c310919..30e01493 100644 --- a/java/src/test/java/com/google/protobuf/DescriptorsTest.java +++ b/java/src/test/java/com/google/protobuf/DescriptorsTest.java @@ -39,6 +39,7 @@ import com.google.protobuf.Descriptors.DescriptorValidationException; import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.FieldDescriptor; +import com.google.protobuf.Descriptors.OneofDescriptor; import com.google.protobuf.Descriptors.EnumDescriptor; import com.google.protobuf.Descriptors.EnumValueDescriptor; import com.google.protobuf.Descriptors.ServiceDescriptor; @@ -53,10 +54,13 @@ import protobuf_unittest.UnittestProto.ForeignMessage; import protobuf_unittest.UnittestProto.TestAllTypes; import protobuf_unittest.UnittestProto.TestAllExtensions; import protobuf_unittest.UnittestProto.TestExtremeDefaultValues; +import protobuf_unittest.UnittestProto.TestMultipleExtensionRanges; import protobuf_unittest.UnittestProto.TestRequired; import protobuf_unittest.UnittestProto.TestService; import protobuf_unittest.UnittestCustomOptions; +import protobuf_unittest.TestCustomOptions; + import junit.framework.TestCase; @@ -308,6 +312,7 @@ public class DescriptorsTest extends TestCase { EnumValueDescriptor value = ForeignEnum.FOREIGN_FOO.getValueDescriptor(); assertEquals(value, enumType.getValues().get(0)); assertEquals("FOREIGN_FOO", value.getName()); + assertEquals("FOREIGN_FOO", value.toString()); assertEquals(4, value.getNumber()); assertEquals(value, enumType.findValueByName("FOREIGN_FOO")); assertEquals(value, enumType.findValueByNumber(4)); @@ -324,7 +329,6 @@ public class DescriptorsTest extends TestCase { assertEquals("protobuf_unittest.TestService", service.getFullName()); assertEquals(UnittestProto.getDescriptor(), service.getFile()); - assertEquals(2, service.getMethods().size()); MethodDescriptor fooMethod = service.getMethods().get(0); assertEquals("Foo", fooMethod.getName()); @@ -351,8 +355,12 @@ public class DescriptorsTest extends TestCase { public void testCustomOptions() throws Exception { + // Get the descriptor indirectly from a dependent proto class. This is to + // ensure that when a proto class is loaded, custom options defined in its + // dependencies are also properly initialized. Descriptor descriptor = - UnittestCustomOptions.TestMessageWithCustomOptions.getDescriptor(); + TestCustomOptions.TestMessageWithCustomOptionsContainer.getDescriptor() + .findFieldByName("field").getMessageType(); assertTrue( descriptor.getOptions().hasExtension(UnittestCustomOptions.messageOpt1)); @@ -511,9 +519,35 @@ public class DescriptorsTest extends TestCase { assertTrue(barFound); } + public void testDependencyOrder() throws Exception { + FileDescriptorProto fooProto = FileDescriptorProto.newBuilder() + .setName("foo.proto").build(); + FileDescriptorProto barProto = FileDescriptorProto.newBuilder() + .setName("bar.proto") + .addDependency("foo.proto") + .build(); + FileDescriptorProto bazProto = FileDescriptorProto.newBuilder() + .setName("baz.proto") + .addDependency("foo.proto") + .addDependency("bar.proto") + .addPublicDependency(0) + .addPublicDependency(1) + .build(); + FileDescriptor fooFile = Descriptors.FileDescriptor.buildFrom(fooProto, + new FileDescriptor[0]); + FileDescriptor barFile = Descriptors.FileDescriptor.buildFrom(barProto, + new FileDescriptor[] {fooFile}); + + // Items in the FileDescriptor array can be in any order. + Descriptors.FileDescriptor.buildFrom(bazProto, + new FileDescriptor[] {fooFile, barFile}); + Descriptors.FileDescriptor.buildFrom(bazProto, + new FileDescriptor[] {barFile, fooFile}); + } + public void testInvalidPublicDependency() throws Exception { FileDescriptorProto fooProto = FileDescriptorProto.newBuilder() - .setName("foo.proto") .build(); + .setName("foo.proto").build(); FileDescriptorProto barProto = FileDescriptorProto.newBuilder() .setName("boo.proto") .addDependency("foo.proto") @@ -645,4 +679,30 @@ public class DescriptorsTest extends TestCase { "a.b.c.d.bar.shared")); } } + + public void testOneofDescriptor() throws Exception { + Descriptor messageType = TestAllTypes.getDescriptor(); + FieldDescriptor field = + messageType.findFieldByName("oneof_nested_message"); + OneofDescriptor oneofDescriptor = field.getContainingOneof(); + assertNotNull(oneofDescriptor); + assertSame(oneofDescriptor, messageType.getOneofs().get(0)); + assertEquals("oneof_field", oneofDescriptor.getName()); + + assertEquals(4, oneofDescriptor.getFieldCount()); + assertSame(oneofDescriptor.getField(1), field); + } + + public void testMessageDescriptorExtensions() throws Exception { + assertFalse(TestAllTypes.getDescriptor().isExtendable()); + assertTrue(TestAllExtensions.getDescriptor().isExtendable()); + assertTrue(TestMultipleExtensionRanges.getDescriptor().isExtendable()); + + assertFalse(TestAllTypes.getDescriptor().isExtensionNumber(3)); + assertTrue(TestAllExtensions.getDescriptor().isExtensionNumber(3)); + assertTrue(TestMultipleExtensionRanges.getDescriptor().isExtensionNumber(42)); + assertFalse(TestMultipleExtensionRanges.getDescriptor().isExtensionNumber(43)); + assertFalse(TestMultipleExtensionRanges.getDescriptor().isExtensionNumber(4142)); + assertTrue(TestMultipleExtensionRanges.getDescriptor().isExtensionNumber(4143)); + } } diff --git a/java/src/test/java/com/google/protobuf/DynamicMessageTest.java b/java/src/test/java/com/google/protobuf/DynamicMessageTest.java index 990e8ca6..ee3769ce 100644 --- a/java/src/test/java/com/google/protobuf/DynamicMessageTest.java +++ b/java/src/test/java/com/google/protobuf/DynamicMessageTest.java @@ -30,6 +30,9 @@ package com.google.protobuf; +import com.google.protobuf.Descriptors.FieldDescriptor; +import com.google.protobuf.Descriptors.OneofDescriptor; + import protobuf_unittest.UnittestProto.TestAllExtensions; import protobuf_unittest.UnittestProto.TestAllTypes; import protobuf_unittest.UnittestProto.TestEmptyMessage; @@ -241,6 +244,19 @@ public class DynamicMessageTest extends TestCase { DynamicMessage copy = DynamicMessage.newBuilder(message).build(); reflectionTester.assertAllFieldsSetViaReflection(copy); + + // Test oneof behavior + FieldDescriptor bytesField = + TestAllTypes.getDescriptor().findFieldByName("oneof_bytes"); + FieldDescriptor uint32Field = + TestAllTypes.getDescriptor().findFieldByName("oneof_uint32"); + assertTrue(copy.hasField(bytesField)); + assertFalse(copy.hasField(uint32Field)); + DynamicMessage copy2 = + DynamicMessage.newBuilder(message).setField(uint32Field, 123).build(); + assertFalse(copy2.hasField(bytesField)); + assertTrue(copy2.hasField(uint32Field)); + assertEquals(123, copy2.getField(uint32Field)); } public void testToBuilder() throws Exception { @@ -261,4 +277,34 @@ public class DynamicMessageTest extends TestCase { assertEquals(Arrays.asList(unknownFieldVal), derived.getUnknownFields().getField(unknownFieldNum).getVarintList()); } + + public void testDynamicOneofMessage() throws Exception { + DynamicMessage.Builder builder = + DynamicMessage.newBuilder(TestAllTypes.getDescriptor()); + OneofDescriptor oneof = TestAllTypes.getDescriptor().getOneofs().get(0); + assertFalse(builder.hasOneof(oneof)); + assertSame(null, builder.getOneofFieldDescriptor(oneof)); + + reflectionTester.setAllFieldsViaReflection(builder); + assertTrue(builder.hasOneof(oneof)); + FieldDescriptor field = oneof.getField(3); + assertSame(field, builder.getOneofFieldDescriptor(oneof)); + + DynamicMessage message = builder.buildPartial(); + assertTrue(message.hasOneof(oneof)); + + DynamicMessage.Builder mergedBuilder = + DynamicMessage.newBuilder(TestAllTypes.getDescriptor()); + FieldDescriptor mergedField = oneof.getField(0); + mergedBuilder.setField(mergedField, 123); + assertTrue(mergedBuilder.hasField(mergedField)); + mergedBuilder.mergeFrom(message); + assertTrue(mergedBuilder.hasField(field)); + assertFalse(mergedBuilder.hasField(mergedField)); + + builder.clearOneof(oneof); + assertSame(null, builder.getOneofFieldDescriptor(oneof)); + message = builder.build(); + assertSame(null, message.getOneofFieldDescriptor(oneof)); + } } diff --git a/java/src/test/java/com/google/protobuf/GeneratedMessageTest.java b/java/src/test/java/com/google/protobuf/GeneratedMessageTest.java index bf9db75b..eb9e6322 100644 --- a/java/src/test/java/com/google/protobuf/GeneratedMessageTest.java +++ b/java/src/test/java/com/google/protobuf/GeneratedMessageTest.java @@ -45,6 +45,9 @@ import protobuf_unittest.NonNestedExtension.MyNonNestedExtension; import protobuf_unittest.NonNestedExtensionLite; import protobuf_unittest.NonNestedExtensionLite.MessageLiteToBeExtended; import protobuf_unittest.NonNestedExtensionLite.MyNonNestedExtensionLite; +import protobuf_unittest.OuterClassNameTest2OuterClass; +import protobuf_unittest.OuterClassNameTest3OuterClass; +import protobuf_unittest.OuterClassNameTestOuterClass; import protobuf_unittest.ServiceWithNoOuter; import protobuf_unittest.UnittestOptimizeFor.TestOptimizedForSize; import protobuf_unittest.UnittestOptimizeFor.TestOptionalOptimizedForSize; @@ -58,6 +61,7 @@ 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; import protobuf_unittest.UnittestProto.TestPackedTypes; import protobuf_unittest.UnittestProto.TestUnpackedTypes; @@ -69,6 +73,7 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.Arrays; import java.util.Collections; +import java.util.Iterator; import java.util.List; /** @@ -399,6 +404,44 @@ public class GeneratedMessageTest extends TestCase { // We expect this exception. } } + + public void testRepeatedAppendIterateOnlyOnce() throws Exception { + // Create a Iterable that can only be iterated once. + Iterable stringIterable = new Iterable() { + private boolean called = false; + @Override + public Iterator iterator() { + if (called) { + throw new IllegalStateException(); + } + called = true; + return Arrays.asList("one", "two", "three").iterator(); + } + }; + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + builder.addAllRepeatedString(stringIterable); + assertEquals(3, builder.getRepeatedStringCount()); + assertEquals("one", builder.getRepeatedString(0)); + assertEquals("two", builder.getRepeatedString(1)); + assertEquals("three", builder.getRepeatedString(2)); + + try { + builder.addAllRepeatedString(stringIterable); + fail("Exception was not thrown"); + } catch (IllegalStateException e) { + // We expect this exception. + } + } + + public void testMergeFromOtherRejectsNull() throws Exception { + try { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + builder.mergeFrom((TestAllTypes) null); + fail("Exception was not thrown"); + } catch (NullPointerException e) { + // We expect this exception. + } + } public void testSettingForeignMessageUsingBuilder() throws Exception { TestAllTypes message = TestAllTypes.newBuilder() @@ -496,6 +539,34 @@ public class GeneratedMessageTest extends TestCase { TestAllTypes.newBuilder().build()); } + public void testReflectionGetOneof() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + reflectionTester.setAllFieldsViaReflection(builder); + Descriptors.OneofDescriptor oneof = + TestAllTypes.getDescriptor().getOneofs().get(0); + Descriptors.FieldDescriptor field = + TestAllTypes.getDescriptor().findFieldByName("oneof_bytes"); + assertSame(field, builder.getOneofFieldDescriptor(oneof)); + + TestAllTypes message = builder.build(); + assertSame(field, message.getOneofFieldDescriptor(oneof)); + } + + public void testReflectionClearOneof() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + reflectionTester.setAllFieldsViaReflection(builder); + Descriptors.OneofDescriptor oneof = + TestAllTypes.getDescriptor().getOneofs().get(0); + Descriptors.FieldDescriptor field = + TestAllTypes.getDescriptor().findFieldByName("oneof_bytes"); + + assertTrue(builder.hasOneof(oneof)); + assertTrue(builder.hasField(field)); + builder.clearOneof(oneof); + assertFalse(builder.hasOneof(oneof)); + assertFalse(builder.hasField(field)); + } + public void testEnumInterface() throws Exception { assertTrue(TestAllTypes.getDefaultInstance().getDefaultNestedEnum() instanceof ProtocolMessageEnum); @@ -697,6 +768,15 @@ public class GeneratedMessageTest extends TestCase { // ================================================================= // multiple_files_test + // Test that custom options of an file level enum are properly initialized. + // This test needs to be put before any other access to MultipleFilesTestProto + // or messages defined in multiple_files_test.proto because the class loading + // order affects initialization process of custom options. + public void testEnumValueOptionsInMultipleFilesMode() throws Exception { + assertEquals(12345, EnumWithNoOuter.FOO.getValueDescriptor().getOptions() + .getExtension(MultipleFilesTestProto.enumValueOption).intValue()); + } + public void testMultipleFilesOption() throws Exception { // We mostly just want to check that things compile. MessageWithNoOuter message = @@ -795,7 +875,7 @@ public class GeneratedMessageTest extends TestCase { UnittestProto.TestRecursiveMessage message = UnittestProto.TestRecursiveMessage.getDefaultInstance(); assertTrue(message != null); - assertTrue(message.getA() != null); + assertNotNull(message.getA()); assertTrue(message.getA() == message); } @@ -1143,4 +1223,293 @@ public class GeneratedMessageTest extends TestCase { // We expect this exception. } } + + // Test that when the default outer class name conflicts with another type + // defined in the proto the compiler will append a suffix to avoid the + // conflict. + public void testConflictingOuterClassName() { + // We just need to make sure we can refer to the outer class with the + // expected name. There is nothing else to test. + OuterClassNameTestOuterClass.OuterClassNameTest message = + OuterClassNameTestOuterClass.OuterClassNameTest.newBuilder().build(); + assertTrue(message.getDescriptorForType() == + OuterClassNameTestOuterClass.OuterClassNameTest.getDescriptor()); + + OuterClassNameTest2OuterClass.TestMessage2.NestedMessage.OuterClassNameTest2 + message2 = OuterClassNameTest2OuterClass.TestMessage2.NestedMessage + .OuterClassNameTest2.newBuilder().build(); + assertEquals(0, message2.getSerializedSize()); + + OuterClassNameTest3OuterClass.TestMessage3.NestedMessage.OuterClassNameTest3 + enumValue = OuterClassNameTest3OuterClass.TestMessage3.NestedMessage + .OuterClassNameTest3.DUMMY_VALUE; + assertEquals(1, enumValue.getNumber()); + } + + // ================================================================= + // oneof generated code test + public void testOneofEnumCase() throws Exception { + TestOneof2 message = TestOneof2.newBuilder() + .setFooInt(123).setFooString("foo").setFooCord("bar").build(); + TestUtil.assertAtMostOneFieldSetOneof(message); + } + + public void testClearOneof() throws Exception { + TestOneof2.Builder builder = TestOneof2.newBuilder().setFooInt(123); + assertEquals(TestOneof2.FooCase.FOO_INT, builder.getFooCase()); + builder.clearFoo(); + assertEquals(TestOneof2.FooCase.FOO_NOT_SET, builder.getFooCase()); + } + + public void testSetOneofClearsOthers() throws Exception { + TestOneof2.Builder builder = TestOneof2.newBuilder(); + TestOneof2 message = + builder.setFooInt(123).setFooString("foo").buildPartial(); + assertTrue(message.hasFooString()); + TestUtil.assertAtMostOneFieldSetOneof(message); + + message = builder.setFooCord("bar").buildPartial(); + assertTrue(message.hasFooCord()); + TestUtil.assertAtMostOneFieldSetOneof(message); + + message = builder.setFooStringPiece("baz").buildPartial(); + assertTrue(message.hasFooStringPiece()); + TestUtil.assertAtMostOneFieldSetOneof(message); + + message = builder.setFooBytes(TestUtil.toBytes("qux")).buildPartial(); + assertTrue(message.hasFooBytes()); + TestUtil.assertAtMostOneFieldSetOneof(message); + + message = builder.setFooEnum(TestOneof2.NestedEnum.FOO).buildPartial(); + assertTrue(message.hasFooEnum()); + TestUtil.assertAtMostOneFieldSetOneof(message); + + message = builder.setFooMessage( + TestOneof2.NestedMessage.newBuilder().setQuxInt(234).build()).buildPartial(); + assertTrue(message.hasFooMessage()); + TestUtil.assertAtMostOneFieldSetOneof(message); + + message = builder.setFooInt(123).buildPartial(); + assertTrue(message.hasFooInt()); + TestUtil.assertAtMostOneFieldSetOneof(message); + } + + public void testOneofTypes() throws Exception { + // Primitive + { + TestOneof2.Builder builder = TestOneof2.newBuilder(); + assertEquals(builder.getFooInt(), 0); + assertFalse(builder.hasFooInt()); + assertTrue(builder.setFooInt(123).hasFooInt()); + assertEquals(builder.getFooInt(), 123); + TestOneof2 message = builder.buildPartial(); + assertTrue(message.hasFooInt()); + assertEquals(message.getFooInt(), 123); + + assertFalse(builder.clearFooInt().hasFooInt()); + TestOneof2 message2 = builder.build(); + assertFalse(message2.hasFooInt()); + assertEquals(message2.getFooInt(), 0); + } + + // Enum + { + TestOneof2.Builder builder = TestOneof2.newBuilder(); + assertEquals(builder.getFooEnum(), TestOneof2.NestedEnum.FOO); + assertTrue(builder.setFooEnum(TestOneof2.NestedEnum.BAR).hasFooEnum()); + assertEquals(builder.getFooEnum(), TestOneof2.NestedEnum.BAR); + TestOneof2 message = builder.buildPartial(); + assertTrue(message.hasFooEnum()); + assertEquals(message.getFooEnum(), TestOneof2.NestedEnum.BAR); + + assertFalse(builder.clearFooEnum().hasFooEnum()); + TestOneof2 message2 = builder.build(); + assertFalse(message2.hasFooEnum()); + assertEquals(message2.getFooEnum(), TestOneof2.NestedEnum.FOO); + } + + // String + { + TestOneof2.Builder builder = TestOneof2.newBuilder(); + assertEquals(builder.getFooString(), ""); + builder.setFooString("foo"); + assertTrue(builder.hasFooString()); + assertEquals(builder.getFooString(), "foo"); + TestOneof2 message = builder.buildPartial(); + assertTrue(message.hasFooString()); + assertEquals(message.getFooString(), "foo"); + assertEquals(message.getFooStringBytes(), TestUtil.toBytes("foo")); + + assertFalse(builder.clearFooString().hasFooString()); + TestOneof2 message2 = builder.buildPartial(); + assertFalse(message2.hasFooString()); + assertEquals(message2.getFooString(), ""); + assertEquals(message2.getFooStringBytes(), TestUtil.toBytes("")); + + // Get method should not change the oneof value. + builder.setFooInt(123); + assertEquals(builder.getFooString(), ""); + assertEquals(builder.getFooStringBytes(), TestUtil.toBytes("")); + assertEquals(123, builder.getFooInt()); + + message = builder.build(); + assertEquals(message.getFooString(), ""); + assertEquals(message.getFooStringBytes(), TestUtil.toBytes("")); + assertEquals(123, message.getFooInt()); + } + + // Cord + { + TestOneof2.Builder builder = TestOneof2.newBuilder(); + assertEquals(builder.getFooCord(), ""); + builder.setFooCord("foo"); + assertTrue(builder.hasFooCord()); + assertEquals(builder.getFooCord(), "foo"); + TestOneof2 message = builder.buildPartial(); + assertTrue(message.hasFooCord()); + assertEquals(message.getFooCord(), "foo"); + assertEquals(message.getFooCordBytes(), TestUtil.toBytes("foo")); + + assertFalse(builder.clearFooCord().hasFooCord()); + TestOneof2 message2 = builder.build(); + assertFalse(message2.hasFooCord()); + assertEquals(message2.getFooCord(), ""); + assertEquals(message2.getFooCordBytes(), TestUtil.toBytes("")); + } + + // StringPiece + { + TestOneof2.Builder builder = TestOneof2.newBuilder(); + assertEquals(builder.getFooStringPiece(), ""); + builder.setFooStringPiece("foo"); + assertTrue(builder.hasFooStringPiece()); + assertEquals(builder.getFooStringPiece(), "foo"); + TestOneof2 message = builder.buildPartial(); + assertTrue(message.hasFooStringPiece()); + assertEquals(message.getFooStringPiece(), "foo"); + assertEquals(message.getFooStringPieceBytes(), TestUtil.toBytes("foo")); + + assertFalse(builder.clearFooStringPiece().hasFooStringPiece()); + TestOneof2 message2 = builder.build(); + assertFalse(message2.hasFooStringPiece()); + assertEquals(message2.getFooStringPiece(), ""); + assertEquals(message2.getFooStringPieceBytes(), TestUtil.toBytes("")); + } + + // Message + { + // set + TestOneof2.Builder builder = TestOneof2.newBuilder(); + assertEquals(builder.getFooMessage().getQuxInt(), 0); + builder.setFooMessage( + TestOneof2.NestedMessage.newBuilder().setQuxInt(234).build()); + assertTrue(builder.hasFooMessage()); + assertEquals(builder.getFooMessage().getQuxInt(), 234); + TestOneof2 message = builder.buildPartial(); + assertTrue(message.hasFooMessage()); + assertEquals(message.getFooMessage().getQuxInt(), 234); + + // clear + assertFalse(builder.clearFooMessage().hasFooString()); + message = builder.build(); + assertFalse(message.hasFooMessage()); + assertEquals(message.getFooMessage().getQuxInt(), 0); + + // nested builder + builder = TestOneof2.newBuilder(); + assertSame(builder.getFooMessageOrBuilder(), + TestOneof2.NestedMessage.getDefaultInstance()); + assertFalse(builder.hasFooMessage()); + builder.getFooMessageBuilder().setQuxInt(123); + assertTrue(builder.hasFooMessage()); + assertEquals(builder.getFooMessage().getQuxInt(), 123); + message = builder.build(); + assertTrue(message.hasFooMessage()); + assertEquals(message.getFooMessage().getQuxInt(), 123); + } + + // LazyMessage is tested in LazyMessageLiteTest.java + } + + public void testOneofMerge() throws Exception { + // Primitive Type + { + TestOneof2.Builder builder = TestOneof2.newBuilder(); + TestOneof2 message = builder.setFooInt(123).build(); + TestOneof2 message2 = TestOneof2.newBuilder().mergeFrom(message).build(); + assertTrue(message2.hasFooInt()); + assertEquals(message2.getFooInt(), 123); + } + + // String + { + TestOneof2.Builder builder = TestOneof2.newBuilder(); + TestOneof2 message = builder.setFooString("foo").build(); + TestOneof2 message2 = TestOneof2.newBuilder().mergeFrom(message).build(); + assertTrue(message2.hasFooString()); + assertEquals(message2.getFooString(), "foo"); + } + + // Enum + { + TestOneof2.Builder builder = TestOneof2.newBuilder(); + TestOneof2 message = builder.setFooEnum(TestOneof2.NestedEnum.BAR).build(); + TestOneof2 message2 = TestOneof2.newBuilder().mergeFrom(message).build(); + assertTrue(message2.hasFooEnum()); + assertEquals(message2.getFooEnum(), TestOneof2.NestedEnum.BAR); + } + + // Message + { + TestOneof2.Builder builder = TestOneof2.newBuilder(); + TestOneof2 message = builder.setFooMessage( + TestOneof2.NestedMessage.newBuilder().setQuxInt(234).build()).build(); + TestOneof2 message2 = TestOneof2.newBuilder().mergeFrom(message).build(); + assertTrue(message2.hasFooMessage()); + assertEquals(message2.getFooMessage().getQuxInt(), 234); + } + } + + public void testOneofSerialization() throws Exception { + // Primitive Type + { + TestOneof2.Builder builder = TestOneof2.newBuilder(); + TestOneof2 message = builder.setFooInt(123).build(); + ByteString serialized = message.toByteString(); + TestOneof2 message2 = TestOneof2.parseFrom(serialized); + assertTrue(message2.hasFooInt()); + assertEquals(message2.getFooInt(), 123); + } + + // String + { + TestOneof2.Builder builder = TestOneof2.newBuilder(); + TestOneof2 message = builder.setFooString("foo").build(); + ByteString serialized = message.toByteString(); + TestOneof2 message2 = TestOneof2.parseFrom(serialized); + assertTrue(message2.hasFooString()); + assertEquals(message2.getFooString(), "foo"); + } + + // Enum + { + TestOneof2.Builder builder = TestOneof2.newBuilder(); + TestOneof2 message = builder.setFooEnum(TestOneof2.NestedEnum.BAR).build(); + ByteString serialized = message.toByteString(); + TestOneof2 message2 = TestOneof2.parseFrom(serialized); + assertTrue(message2.hasFooEnum()); + assertEquals(message2.getFooEnum(), TestOneof2.NestedEnum.BAR); + } + + // Message + { + TestOneof2.Builder builder = TestOneof2.newBuilder(); + TestOneof2 message = builder.setFooMessage( + TestOneof2.NestedMessage.newBuilder().setQuxInt(234).build()).build(); + ByteString serialized = message.toByteString(); + TestOneof2 message2 = TestOneof2.parseFrom(serialized); + assertTrue(message2.hasFooMessage()); + assertEquals(message2.getFooMessage().getQuxInt(), 234); + } + } } diff --git a/java/src/test/java/com/google/protobuf/LazyFieldLiteTest.java b/java/src/test/java/com/google/protobuf/LazyFieldLiteTest.java new file mode 100644 index 00000000..33a7297b --- /dev/null +++ b/java/src/test/java/com/google/protobuf/LazyFieldLiteTest.java @@ -0,0 +1,134 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// http://code.google.com/p/protobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package com.google.protobuf; + +import protobuf_unittest.UnittestProto.TestAllExtensions; +import protobuf_unittest.UnittestProto.TestAllTypes; + +import java.io.IOException; +import junit.framework.TestCase; + +/** + * Unit test for {@link LazyFieldLite}. + * + * @author xiangl@google.com (Xiang Li) + */ +public class LazyFieldLiteTest extends TestCase { + + public void testGetValue() { + MessageLite message = TestUtil.getAllSet(); + LazyFieldLite lazyField = createLazyFieldLiteFromMessage(message); + assertEquals(message, lazyField.getValue(TestAllTypes.getDefaultInstance())); + changeValue(lazyField); + assertNotEqual(message, lazyField.getValue(TestAllTypes.getDefaultInstance())); + } + + public void testGetValueEx() throws Exception { + TestAllExtensions message = TestUtil.getAllExtensionsSet(); + LazyFieldLite lazyField = createLazyFieldLiteFromMessage(message); + assertEquals(message, lazyField.getValue(TestAllExtensions.getDefaultInstance())); + changeValue(lazyField); + assertNotEqual(message, lazyField.getValue(TestAllExtensions.getDefaultInstance())); + } + + public void testSetValue() { + MessageLite message = TestUtil.getAllSet(); + LazyFieldLite lazyField = createLazyFieldLiteFromMessage(message); + changeValue(lazyField); + assertNotEqual(message, lazyField.getValue(TestAllTypes.getDefaultInstance())); + message = lazyField.getValue(TestAllTypes.getDefaultInstance()); + changeValue(lazyField); + assertEquals(message, lazyField.getValue(TestAllTypes.getDefaultInstance())); + } + + public void testSetValueEx() throws Exception { + TestAllExtensions message = TestUtil.getAllExtensionsSet(); + LazyFieldLite lazyField = createLazyFieldLiteFromMessage(message); + changeValue(lazyField); + assertNotEqual(message, lazyField.getValue(TestAllExtensions.getDefaultInstance())); + MessageLite value = lazyField.getValue(TestAllExtensions.getDefaultInstance()); + changeValue(lazyField); + assertEquals(value, lazyField.getValue(TestAllExtensions.getDefaultInstance())); + } + + public void testGetSerializedSize() { + MessageLite message = TestUtil.getAllSet(); + LazyFieldLite lazyField = createLazyFieldLiteFromMessage(message); + assertEquals(message.getSerializedSize(), lazyField.getSerializedSize()); + changeValue(lazyField); + assertNotEqual(message.getSerializedSize(), lazyField.getSerializedSize()); + } + + public void testGetSerializedSizeEx() throws Exception { + TestAllExtensions message = TestUtil.getAllExtensionsSet(); + LazyFieldLite lazyField = createLazyFieldLiteFromMessage(message); + assertEquals(message.getSerializedSize(), lazyField.getSerializedSize()); + changeValue(lazyField); + assertNotEqual(message.getSerializedSize(), lazyField.getSerializedSize()); + } + + public void testGetByteString() { + MessageLite message = TestUtil.getAllSet(); + LazyFieldLite lazyField = createLazyFieldLiteFromMessage(message); + assertEquals(message.toByteString(), lazyField.toByteString()); + changeValue(lazyField); + assertNotEqual(message.toByteString(), lazyField.toByteString()); + } + + public void testGetByteStringEx() throws Exception { + TestAllExtensions message = TestUtil.getAllExtensionsSet(); + LazyFieldLite lazyField = createLazyFieldLiteFromMessage(message); + assertEquals(message.toByteString(), lazyField.toByteString()); + changeValue(lazyField); + assertNotEqual(message.toByteString(), lazyField.toByteString()); + } + + + // Help methods. + + private LazyFieldLite createLazyFieldLiteFromMessage(MessageLite message) { + ByteString bytes = message.toByteString(); + return new LazyFieldLite(TestUtil.getExtensionRegistry(), bytes); + } + + private void changeValue(LazyFieldLite lazyField) { + TestAllTypes.Builder builder = TestUtil.getAllSet().toBuilder(); + builder.addRepeatedBool(true); + MessageLite newMessage = builder.build(); + lazyField.setValue(newMessage); + } + + private void assertNotEqual(Object unexpected, Object actual) { + assertFalse(unexpected == actual + || (unexpected != null && unexpected.equals(actual))); + } + +} diff --git a/java/src/test/java/com/google/protobuf/LazyFieldTest.java b/java/src/test/java/com/google/protobuf/LazyFieldTest.java new file mode 100644 index 00000000..5aedc32a --- /dev/null +++ b/java/src/test/java/com/google/protobuf/LazyFieldTest.java @@ -0,0 +1,121 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// http://code.google.com/p/protobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package com.google.protobuf; + +import protobuf_unittest.UnittestProto.TestAllExtensions; +import protobuf_unittest.UnittestProto.TestAllTypes; + +import java.io.IOException; +import junit.framework.TestCase; + +/** + * Unit test for {@link LazyField}. + * + * @author xiangl@google.com (Xiang Li) + */ +public class LazyFieldTest extends TestCase { + public void testHashCode() { + MessageLite message = TestUtil.getAllSet(); + LazyField lazyField = + createLazyFieldFromMessage(message); + assertEquals(message.hashCode(), lazyField.hashCode()); + lazyField.getValue(); + assertEquals(message.hashCode(), lazyField.hashCode()); + changeValue(lazyField); + // make sure two messages have different hash code + assertNotEqual(message.hashCode(), lazyField.hashCode()); + } + + public void testHashCodeEx() throws Exception { + TestAllExtensions message = TestUtil.getAllExtensionsSet(); + LazyField lazyField = createLazyFieldFromMessage(message); + assertEquals(message.hashCode(), lazyField.hashCode()); + lazyField.getValue(); + assertEquals(message.hashCode(), lazyField.hashCode()); + changeValue(lazyField); + // make sure two messages have different hash code + assertNotEqual(message.hashCode(), lazyField.hashCode()); + } + + public void testGetValue() { + MessageLite message = TestUtil.getAllSet(); + LazyField lazyField = createLazyFieldFromMessage(message); + assertEquals(message, lazyField.getValue()); + changeValue(lazyField); + assertNotEqual(message, lazyField.getValue()); + } + + public void testGetValueEx() throws Exception { + TestAllExtensions message = TestUtil.getAllExtensionsSet(); + LazyField lazyField = createLazyFieldFromMessage(message); + assertEquals(message, lazyField.getValue()); + changeValue(lazyField); + assertNotEqual(message, lazyField.getValue()); + } + + public void testEqualsObject() { + MessageLite message = TestUtil.getAllSet(); + LazyField lazyField = createLazyFieldFromMessage(message); + assertTrue(lazyField.equals(message)); + changeValue(lazyField); + assertFalse(lazyField.equals(message)); + assertFalse(message.equals(lazyField.getValue())); + } + + public void testEqualsObjectEx() throws Exception { + TestAllExtensions message = TestUtil.getAllExtensionsSet(); + LazyField lazyField = createLazyFieldFromMessage(message); + assertTrue(lazyField.equals(message)); + changeValue(lazyField); + assertFalse(lazyField.equals(message)); + assertFalse(message.equals(lazyField.getValue())); + } + + // Help methods. + + private LazyField createLazyFieldFromMessage(MessageLite message) { + ByteString bytes = message.toByteString(); + return new LazyField(message.getDefaultInstanceForType(), + TestUtil.getExtensionRegistry(), bytes); + } + + private void changeValue(LazyField lazyField) { + TestAllTypes.Builder builder = TestUtil.getAllSet().toBuilder(); + builder.addRepeatedBool(true); + MessageLite newMessage = builder.build(); + lazyField.setValue(newMessage); + } + + private void assertNotEqual(Object unexpected, Object actual) { + assertFalse(unexpected == actual + || (unexpected != null && unexpected.equals(actual))); + } +} diff --git a/java/src/test/java/com/google/protobuf/LazyMessageLiteTest.java b/java/src/test/java/com/google/protobuf/LazyMessageLiteTest.java new file mode 100644 index 00000000..63028db9 --- /dev/null +++ b/java/src/test/java/com/google/protobuf/LazyMessageLiteTest.java @@ -0,0 +1,319 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// http://code.google.com/p/protobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package com.google.protobuf; + +import protobuf_unittest.LazyFieldsLite.LazyInnerMessageLite; +import protobuf_unittest.LazyFieldsLite.LazyMessageLite; +import protobuf_unittest.LazyFieldsLite.LazyNestedInnerMessageLite; + +import junit.framework.TestCase; + +import org.easymock.classextension.EasyMock; + +import java.util.ArrayList; + +/** + * Unit test for messages with lazy fields. + * + * @author niwasaki@google.com (Naoki Iwasaki) + */ +public class LazyMessageLiteTest extends TestCase { + + private Parser originalLazyInnerMessageLiteParser; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + originalLazyInnerMessageLiteParser = LazyInnerMessageLite.PARSER; + } + + @Override + protected void tearDown() throws Exception { + LazyInnerMessageLite.PARSER = originalLazyInnerMessageLiteParser; + + super.tearDown(); + } + + public void testSetValues() { + LazyNestedInnerMessageLite nested = LazyNestedInnerMessageLite.newBuilder() + .setNum(3) + .build(); + LazyInnerMessageLite inner = LazyInnerMessageLite.newBuilder() + .setNum(2) + .setNested(nested) + .build(); + LazyMessageLite outer = LazyMessageLite.newBuilder() + .setNum(1) + .setInner(inner) + .setOneofNum(123) + .setOneofInner(inner) + .build(); + + assertEquals(1, outer.getNum()); + assertEquals(421, outer.getNumWithDefault()); + + assertEquals(2, outer.getInner().getNum()); + assertEquals(42, outer.getInner().getNumWithDefault()); + + assertEquals(3, outer.getInner().getNested().getNum()); + assertEquals(4, outer.getInner().getNested().getNumWithDefault()); + + assertFalse(outer.hasOneofNum()); + assertTrue(outer.hasOneofInner()); + + assertEquals(2, outer.getOneofInner().getNum()); + assertEquals(42, outer.getOneofInner().getNumWithDefault()); + assertEquals(3, outer.getOneofInner().getNested().getNum()); + assertEquals(4, outer.getOneofInner().getNested().getNumWithDefault()); + } + + public void testSetRepeatedValues() { + LazyMessageLite outer = LazyMessageLite.newBuilder() + .setNum(1) + .addRepeatedInner(LazyInnerMessageLite.newBuilder().setNum(119)) + .addRepeatedInner(LazyInnerMessageLite.newBuilder().setNum(122)) + .build(); + + assertEquals(1, outer.getNum()); + assertEquals(2, outer.getRepeatedInnerCount()); + assertEquals(119, outer.getRepeatedInner(0).getNum()); + assertEquals(122, outer.getRepeatedInner(1).getNum()); + } + + public void testAddAll() { + ArrayList inners = new ArrayList(); + int count = 4; + for (int i = 0; i < count; i++) { + LazyInnerMessageLite inner = LazyInnerMessageLite.newBuilder() + .setNum(i) + .build(); + inners.add(inner); + } + + LazyMessageLite outer = LazyMessageLite.newBuilder() + .addAllRepeatedInner(inners) + .build(); + assertEquals(count, outer.getRepeatedInnerCount()); + for (int i = 0; i < count; i++) { + assertEquals(i, outer.getRepeatedInner(i).getNum()); + } + } + + public void testGetDefaultValues() { + LazyMessageLite outer = LazyMessageLite.newBuilder() + .build(); + + assertEquals(0, outer.getNum()); + assertEquals(421, outer.getNumWithDefault()); + + assertEquals(0, outer.getInner().getNum()); + assertEquals(42, outer.getInner().getNumWithDefault()); + + assertEquals(0, outer.getInner().getNested().getNum()); + assertEquals(4, outer.getInner().getNested().getNumWithDefault()); + + assertEquals(0, outer.getOneofNum()); + + assertEquals(0, outer.getOneofInner().getNum()); + assertEquals(42, outer.getOneofInner().getNumWithDefault()); + assertEquals(0, outer.getOneofInner().getNested().getNum()); + assertEquals(4, outer.getOneofInner().getNested().getNumWithDefault()); + } + + public void testClearValues() { + LazyInnerMessageLite inner = LazyInnerMessageLite.newBuilder() + .setNum(115) + .build(); + + LazyMessageLite.Builder outerBuilder = LazyMessageLite.newBuilder(); + + assertEquals(0, outerBuilder.build().getNum()); + + + // Set/Clear num + outerBuilder.setNum(100); + + assertEquals(100, outerBuilder.build().getNum()); + assertEquals(421, outerBuilder.build().getNumWithDefault()); + assertFalse(outerBuilder.build().hasInner()); + + outerBuilder.clearNum(); + + assertEquals(0, outerBuilder.build().getNum()); + assertEquals(421, outerBuilder.build().getNumWithDefault()); + assertFalse(outerBuilder.build().hasInner()); + + + // Set/Clear all + outerBuilder.setNum(100) + .setInner(inner) + .addRepeatedInner(LazyInnerMessageLite.newBuilder().setNum(119)) + .addRepeatedInner(LazyInnerMessageLite.newBuilder().setNum(122)) + .setOneofInner(LazyInnerMessageLite.newBuilder().setNum(123)); + + LazyMessageLite outer = outerBuilder.build(); + assertEquals(100, outer.getNum()); + assertEquals(421, outer.getNumWithDefault()); + assertTrue(outer.hasInner()); + assertEquals(115, outer.getInner().getNum()); + assertEquals(2, outer.getRepeatedInnerCount()); + assertEquals(119, outer.getRepeatedInner(0).getNum()); + assertEquals(122, outer.getRepeatedInner(1).getNum()); + assertTrue(outer.hasOneofInner()); + assertEquals(123, outer.getOneofInner().getNum()); + + outerBuilder.clear(); + + outer = outerBuilder.build(); + + assertEquals(0, outer.getNum()); + assertEquals(421, outer.getNumWithDefault()); + assertFalse(outer.hasInner()); + assertEquals(0, outer.getRepeatedInnerCount()); + assertFalse(outer.hasOneofInner()); + assertEquals(0, outer.getOneofInner().getNum()); + } + + public void testMergeValues() { + LazyMessageLite outerBase = LazyMessageLite.newBuilder() + .setNumWithDefault(122) + .build(); + + LazyInnerMessageLite innerMerging = LazyInnerMessageLite.newBuilder() + .setNum(115) + .build(); + LazyMessageLite outerMerging = LazyMessageLite.newBuilder() + .setNum(119) + .setInner(innerMerging) + .setOneofInner(innerMerging) + .build(); + + LazyMessageLite merged = LazyMessageLite + .newBuilder(outerBase) + .mergeFrom(outerMerging) + .build(); + assertEquals(119, merged.getNum()); + assertEquals(122, merged.getNumWithDefault()); + assertEquals(115, merged.getInner().getNum()); + assertEquals(42, merged.getInner().getNumWithDefault()); + assertEquals(115, merged.getOneofInner().getNum()); + assertEquals(42, merged.getOneofInner().getNumWithDefault()); + } + + public void testMergeDefaultValues() { + LazyInnerMessageLite innerBase = LazyInnerMessageLite.newBuilder() + .setNum(115) + .build(); + LazyMessageLite outerBase = LazyMessageLite.newBuilder() + .setNum(119) + .setNumWithDefault(122) + .setInner(innerBase) + .setOneofInner(innerBase) + .build(); + + LazyMessageLite outerMerging = LazyMessageLite.newBuilder() + .build(); + + LazyMessageLite merged = LazyMessageLite + .newBuilder(outerBase) + .mergeFrom(outerMerging) + .build(); + // Merging default-instance shouldn't overwrite values in the base message. + assertEquals(119, merged.getNum()); + assertEquals(122, merged.getNumWithDefault()); + assertEquals(115, merged.getInner().getNum()); + assertEquals(42, merged.getInner().getNumWithDefault()); + assertEquals(115, merged.getOneofInner().getNum()); + assertEquals(42, merged.getOneofInner().getNumWithDefault()); + } + + public void testSerialize() throws InvalidProtocolBufferException { + LazyNestedInnerMessageLite nested = LazyNestedInnerMessageLite.newBuilder() + .setNum(3) + .build(); + LazyInnerMessageLite inner = LazyInnerMessageLite.newBuilder() + .setNum(2) + .setNested(nested) + .build(); + LazyMessageLite outer = LazyMessageLite.newBuilder() + .setNum(1) + .setInner(inner) + .setOneofInner(inner) + .build(); + + ByteString bytes = outer.toByteString(); + assertEquals(bytes.size(), outer.getSerializedSize()); + + LazyMessageLite deserialized = LazyMessageLite.parseFrom(bytes); + + assertEquals(1, deserialized.getNum()); + assertEquals(421, deserialized.getNumWithDefault()); + + assertEquals(2, deserialized.getInner().getNum()); + assertEquals(42, deserialized.getInner().getNumWithDefault()); + + assertEquals(3, deserialized.getInner().getNested().getNum()); + assertEquals(4, deserialized.getInner().getNested().getNumWithDefault()); + + assertEquals(2, deserialized.getOneofInner().getNum()); + assertEquals(42, deserialized.getOneofInner().getNumWithDefault()); + assertEquals(3, deserialized.getOneofInner().getNested().getNum()); + assertEquals(4, deserialized.getOneofInner().getNested().getNumWithDefault()); + + assertEquals(bytes, deserialized.toByteString()); + } + + public void testLaziness() throws InvalidProtocolBufferException { + LazyInnerMessageLite inner = LazyInnerMessageLite.newBuilder() + .setNum(2) + .build(); + LazyMessageLite outer = LazyMessageLite.newBuilder() + .setNum(1) + .setInner(inner) + .setOneofInner(inner) + .build(); + ByteString bytes = outer.toByteString(); + + + // The parser for inner / oneofInner message shouldn't be used if + // getInner / getOneofInner is not called. + LazyInnerMessageLite.PARSER = EasyMock.createStrictMock(Parser.class); + + EasyMock.replay(LazyInnerMessageLite.PARSER); + + LazyMessageLite deserialized = LazyMessageLite.parseFrom(bytes); + assertEquals(1, deserialized.getNum()); + assertEquals(421, deserialized.getNumWithDefault()); + + EasyMock.verify(LazyInnerMessageLite.PARSER); + } +} diff --git a/java/src/test/java/com/google/protobuf/LazyStringArrayListTest.java b/java/src/test/java/com/google/protobuf/LazyStringArrayListTest.java index d500595f..850658ca 100644 --- a/java/src/test/java/com/google/protobuf/LazyStringArrayListTest.java +++ b/java/src/test/java/com/google/protobuf/LazyStringArrayListTest.java @@ -67,6 +67,14 @@ public class LazyStringArrayListTest extends TestCase { list.remove(1); assertSame(STRING_A, list.get(0)); assertSame(STRING_C, list.get(1)); + + List byteStringList = list.asByteStringList(); + assertEquals(BYTE_STRING_A, byteStringList.get(0)); + assertEquals(BYTE_STRING_C, byteStringList.get(1)); + + // Underlying list should be transformed. + assertSame(byteStringList.get(0), list.getByteString(0)); + assertSame(byteStringList.get(1), list.getByteString(1)); } public void testJustByteString() { @@ -83,6 +91,10 @@ public class LazyStringArrayListTest extends TestCase { list.remove(1); assertSame(BYTE_STRING_A, list.getByteString(0)); assertSame(BYTE_STRING_C, list.getByteString(1)); + + List byteStringList = list.asByteStringList(); + assertSame(BYTE_STRING_A, byteStringList.get(0)); + assertSame(BYTE_STRING_C, byteStringList.get(1)); } public void testConversionBackAndForth() { diff --git a/java/src/test/java/com/google/protobuf/LiteEqualsAndHashTest.java b/java/src/test/java/com/google/protobuf/LiteEqualsAndHashTest.java new file mode 100644 index 00000000..471acbb6 --- /dev/null +++ b/java/src/test/java/com/google/protobuf/LiteEqualsAndHashTest.java @@ -0,0 +1,85 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// http://code.google.com/p/protobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package com.google.protobuf; + +import protobuf_unittest.lite_equals_and_hash.LiteEqualsAndHash.Bar; +import protobuf_unittest.lite_equals_and_hash.LiteEqualsAndHash.BarPrime; +import protobuf_unittest.lite_equals_and_hash.LiteEqualsAndHash.Foo; + +import junit.framework.TestCase; + +/** + * Test generate equal and hash methods for the lite runtime. + * + * @author pbogle@google.com Phil Bogle + */ +public class LiteEqualsAndHashTest extends TestCase { + + public void testEquals() throws Exception { + // Since the generated equals and hashCode methods for lite messages are a + // mostly complete subset of those for regular messages, we can mostly assume + // that the generated methods are already thoroughly tested by the regular tests. + + // This test mostly just verifies is that a proto with + // optimize_for = LITE_RUNTIME and java_generates_equals_and_hash_compiles + // correctly when linked only against the lite library. + + // We do however do some basic testing to make sure that equals is actually + // overriden to test for value equality rather than simple object equality. + + // Check that two identical objs are equal. + Foo foo1a = Foo.newBuilder() + .setValue(1) + .addBar(Bar.newBuilder().setName("foo1")) + .build(); + Foo foo1b = Foo.newBuilder() + .setValue(1) + .addBar(Bar.newBuilder().setName("foo1")) + .build(); + Foo foo2 = Foo.newBuilder() + .setValue(1) + .addBar(Bar.newBuilder().setName("foo2")) + .build(); + + // Check that equals is doing value rather than object equality. + assertEquals(foo1a, foo1b); + assertEquals(foo1a.hashCode(), foo1b.hashCode()); + + // Check that a diffeent object is not equal. + assertFalse(foo1a.equals(foo2)); + + // Check that two objects which have different types but the same field values are not + // considered to be equal. + Bar bar = Bar.newBuilder().setName("bar").build(); + BarPrime barPrime = BarPrime.newBuilder().setName("bar").build(); + assertFalse(bar.equals(barPrime)); + } +} diff --git a/java/src/test/java/com/google/protobuf/ParserTest.java b/java/src/test/java/com/google/protobuf/ParserTest.java index 396902cf..8cc4693e 100644 --- a/java/src/test/java/com/google/protobuf/ParserTest.java +++ b/java/src/test/java/com/google/protobuf/ParserTest.java @@ -33,15 +33,14 @@ package com.google.protobuf; import com.google.protobuf.UnittestLite.TestAllTypesLite; import com.google.protobuf.UnittestLite.TestPackedExtensionsLite; import com.google.protobuf.UnittestLite.TestParsingMergeLite; -import com.google.protobuf.UnittestLite; import protobuf_unittest.UnittestOptimizeFor.TestOptimizedForSize; import protobuf_unittest.UnittestOptimizeFor.TestRequiredOptimizedForSize; import protobuf_unittest.UnittestOptimizeFor; import protobuf_unittest.UnittestProto.ForeignMessage; import protobuf_unittest.UnittestProto.TestAllTypes; import protobuf_unittest.UnittestProto.TestEmptyMessage; -import protobuf_unittest.UnittestProto.TestRequired; import protobuf_unittest.UnittestProto.TestParsingMerge; +import protobuf_unittest.UnittestProto.TestRequired; import protobuf_unittest.UnittestProto; import junit.framework.TestCase; @@ -84,12 +83,15 @@ public class ParserTest extends TestCase { CodedInputStream.newInstance(data), registry)); } + @SuppressWarnings("unchecked") private void assertRoundTripEquals(MessageLite message) throws Exception { final byte[] data = message.toByteArray(); final int offset = 20; final int length = data.length; final int padding = 30; - Parser parser = message.getParserForType(); + + Parser parser = + (Parser) message.getParserForType(); assertMessageEquals(message, parser.parseFrom(data)); assertMessageEquals(message, parser.parseFrom( generatePaddingArray(data, offset, padding), @@ -101,7 +103,8 @@ public class ParserTest extends TestCase { CodedInputStream.newInstance(data))); } - private void assertMessageEquals(MessageLite expected, MessageLite actual) + private void assertMessageEquals( + MessageLite expected, MessageLite actual) throws Exception { if (expected instanceof Message) { assertEquals(expected, actual); @@ -120,15 +123,17 @@ public class ParserTest extends TestCase { assertRoundTripEquals(TestUtil.getAllSet()); } + public void testParsePartial() throws Exception { - Parser parser = TestRequired.PARSER; + assertParsePartial(TestRequired.PARSER, + TestRequired.newBuilder().setA(1).buildPartial()); + } + + private void assertParsePartial( + Parser parser, T partialMessage) throws Exception { final String errorString = "Should throw exceptions when the parsed message isn't initialized."; - // TestRequired.b and TestRequired.c are not set. - TestRequired partialMessage = TestRequired.newBuilder() - .setA(1).buildPartial(); - // parsePartialFrom should pass. byte[] data = partialMessage.toByteArray(); assertEquals(partialMessage, parser.parsePartialFrom(data)); @@ -218,6 +223,7 @@ public class ParserTest extends TestCase { emptyMessage.toByteString()); } + public void testOptimizeForSize() throws Exception { TestOptimizedForSize.Builder builder = TestOptimizedForSize.newBuilder(); builder.setI(12).setMsg(ForeignMessage.newBuilder().setC(34).build()); diff --git a/java/src/test/java/com/google/protobuf/TestBadIdentifiers.java b/java/src/test/java/com/google/protobuf/TestBadIdentifiers.java index 382acf0f..84f16947 100644 --- a/java/src/test/java/com/google/protobuf/TestBadIdentifiers.java +++ b/java/src/test/java/com/google/protobuf/TestBadIdentifiers.java @@ -60,4 +60,37 @@ public class TestBadIdentifiers extends TestCase { .Descriptor.NestedDescriptor.getDefaultInstance() .getDescriptorForType(); } + + public void testConflictingFieldNames() throws Exception { + TestBadIdentifiersProto.TestConflictingFieldNames message = + TestBadIdentifiersProto.TestConflictingFieldNames.getDefaultInstance(); + // Make sure generated accessors are properly named. + assertEquals(0, message.getInt32Field1Count()); + assertEquals(0, message.getEnumField2Count()); + assertEquals(0, message.getStringField3Count()); + assertEquals(0, message.getBytesField4Count()); + assertEquals(0, message.getMessageField5Count()); + + assertEquals(0, message.getInt32FieldCount11()); + assertEquals(1, message.getEnumFieldCount12().getNumber()); + assertEquals("", message.getStringFieldCount13()); + assertEquals(ByteString.EMPTY, message.getBytesFieldCount14()); + assertEquals(0, message.getMessageFieldCount15().getSerializedSize()); + + assertEquals(0, message.getInt32Field21Count()); + assertEquals(0, message.getEnumField22Count()); + assertEquals(0, message.getStringField23Count()); + assertEquals(0, message.getBytesField24Count()); + assertEquals(0, message.getMessageField25Count()); + + assertEquals(0, message.getInt32Field1List().size()); + assertEquals(0, message.getInt32FieldList31()); + + assertEquals(0, message.getInt64FieldCount()); + assertEquals(0L, message.getExtension( + TestBadIdentifiersProto.TestConflictingFieldNames.int64FieldCount).longValue()); + assertEquals(0L, message.getExtension( + TestBadIdentifiersProto.TestConflictingFieldNames.int64FieldList).longValue()); + + } } diff --git a/java/src/test/java/com/google/protobuf/TestUtil.java b/java/src/test/java/com/google/protobuf/TestUtil.java index 76f5c602..e3cf8a63 100644 --- a/java/src/test/java/com/google/protobuf/TestUtil.java +++ b/java/src/test/java/com/google/protobuf/TestUtil.java @@ -56,6 +56,11 @@ import static protobuf_unittest.UnittestProto.defaultImportEnumExtension; import static protobuf_unittest.UnittestProto.defaultStringPieceExtension; import static protobuf_unittest.UnittestProto.defaultCordExtension; +import static protobuf_unittest.UnittestProto.oneofUint32Extension; +import static protobuf_unittest.UnittestProto.oneofNestedMessageExtension; +import static protobuf_unittest.UnittestProto.oneofStringExtension; +import static protobuf_unittest.UnittestProto.oneofBytesExtension; + import static protobuf_unittest.UnittestProto.optionalInt32Extension; import static protobuf_unittest.UnittestProto.optionalInt64Extension; import static protobuf_unittest.UnittestProto.optionalUint32Extension; @@ -148,6 +153,11 @@ import static com.google.protobuf.UnittestLite.defaultImportEnumExtensionLite; import static com.google.protobuf.UnittestLite.defaultStringPieceExtensionLite; import static com.google.protobuf.UnittestLite.defaultCordExtensionLite; +import static com.google.protobuf.UnittestLite.oneofUint32ExtensionLite; +import static com.google.protobuf.UnittestLite.oneofNestedMessageExtensionLite; +import static com.google.protobuf.UnittestLite.oneofStringExtensionLite; +import static com.google.protobuf.UnittestLite.oneofBytesExtensionLite; + import static com.google.protobuf.UnittestLite.optionalInt32ExtensionLite; import static com.google.protobuf.UnittestLite.optionalInt64ExtensionLite; import static com.google.protobuf.UnittestLite.optionalUint32ExtensionLite; @@ -223,6 +233,7 @@ import protobuf_unittest.UnittestProto.TestAllExtensions; import protobuf_unittest.UnittestProto.TestAllExtensionsOrBuilder; import protobuf_unittest.UnittestProto.TestAllTypes; import protobuf_unittest.UnittestProto.TestAllTypesOrBuilder; +import protobuf_unittest.UnittestProto.TestOneof2; import protobuf_unittest.UnittestProto.TestPackedExtensions; import protobuf_unittest.UnittestProto.TestPackedTypes; import protobuf_unittest.UnittestProto.TestUnpackedTypes; @@ -469,6 +480,12 @@ public final class TestUtil { message.setDefaultStringPiece("424"); message.setDefaultCord("425"); + + message.setOneofUint32(601); + message.setOneofNestedMessage( + TestAllTypes.NestedMessage.newBuilder().setBb(602).build()); + message.setOneofString("603"); + message.setOneofBytes(toBytes("604")); } // ------------------------------------------------------------------- @@ -718,6 +735,13 @@ public final class TestUtil { Assert.assertEquals("424", message.getDefaultStringPiece()); Assert.assertEquals("425", message.getDefaultCord()); + + Assert.assertFalse(message.hasOneofUint32()); + Assert.assertFalse(message.hasOneofNestedMessage()); + Assert.assertFalse(message.hasOneofString()); + Assert.assertTrue(message.hasOneofBytes()); + + Assert.assertEquals(toBytes("604"), message.getOneofBytes()); } // ------------------------------------------------------------------- @@ -872,6 +896,11 @@ public final class TestUtil { Assert.assertEquals("abc", message.getDefaultStringPiece()); Assert.assertEquals("123", message.getDefaultCord()); + + Assert.assertFalse(message.hasOneofUint32()); + Assert.assertFalse(message.hasOneofNestedMessage()); + Assert.assertFalse(message.hasOneofString()); + Assert.assertFalse(message.hasOneofBytes()); } // ------------------------------------------------------------------- @@ -1358,6 +1387,12 @@ public final class TestUtil { message.setExtension(defaultStringPieceExtension, "424"); message.setExtension(defaultCordExtension, "425"); + + message.setExtension(oneofUint32Extension, 601); + message.setExtension(oneofNestedMessageExtension, + TestAllTypes.NestedMessage.newBuilder().setBb(602).build()); + message.setExtension(oneofStringExtension, "603"); + message.setExtension(oneofBytesExtension, toBytes("604")); } // ------------------------------------------------------------------- @@ -1621,6 +1656,10 @@ public final class TestUtil { assertEqualsExactType("424", message.getExtension(defaultStringPieceExtension)); assertEqualsExactType("425", message.getExtension(defaultCordExtension)); + + Assert.assertTrue(message.hasExtension(oneofBytesExtension)); + + assertEqualsExactType(toBytes("604"), message.getExtension(oneofBytesExtension)); } // ------------------------------------------------------------------- @@ -1807,6 +1846,11 @@ public final class TestUtil { assertEqualsExactType("abc", message.getExtension(defaultStringPieceExtension)); assertEqualsExactType("123", message.getExtension(defaultCordExtension)); + + Assert.assertFalse(message.hasExtension(oneofUint32Extension)); + Assert.assertFalse(message.hasExtension(oneofNestedMessageExtension)); + Assert.assertFalse(message.hasExtension(oneofStringExtension)); + Assert.assertFalse(message.hasExtension(oneofBytesExtension)); } // ------------------------------------------------------------------- @@ -2133,6 +2177,12 @@ public final class TestUtil { message.setExtension(defaultStringPieceExtensionLite, "424"); message.setExtension(defaultCordExtensionLite, "425"); + + message.setExtension(oneofUint32ExtensionLite, 601); + message.setExtension(oneofNestedMessageExtensionLite, + TestAllTypesLite.NestedMessage.newBuilder().setBb(602).build()); + message.setExtension(oneofStringExtensionLite, "603"); + message.setExtension(oneofBytesExtensionLite, toBytes("604")); } // ------------------------------------------------------------------- @@ -2397,6 +2447,10 @@ public final class TestUtil { assertEqualsExactType("424", message.getExtension(defaultStringPieceExtensionLite)); assertEqualsExactType("425", message.getExtension(defaultCordExtensionLite)); + + Assert.assertTrue(message.hasExtension(oneofBytesExtensionLite)); + + assertEqualsExactType(toBytes("604"), message.getExtension(oneofBytesExtensionLite)); } // ------------------------------------------------------------------- @@ -2562,6 +2616,11 @@ public final class TestUtil { assertEqualsExactType("abc", message.getExtension(defaultStringPieceExtensionLite)); assertEqualsExactType("123", message.getExtension(defaultCordExtensionLite)); + + Assert.assertFalse(message.hasExtension(oneofUint32ExtensionLite)); + Assert.assertFalse(message.hasExtension(oneofNestedMessageExtensionLite)); + Assert.assertFalse(message.hasExtension(oneofStringExtensionLite)); + Assert.assertFalse(message.hasExtension(oneofBytesExtensionLite)); } // ------------------------------------------------------------------- @@ -2749,6 +2808,82 @@ public final class TestUtil { message.getExtension(packedEnumExtensionLite, 1)); } + // =================================================================== + // oneof + public static void setOneof(TestOneof2.Builder message) { + message.setFooLazyMessage( + TestOneof2.NestedMessage.newBuilder().setQuxInt(100).build()); + message.setBarString("101"); + message.setBazInt(102); + message.setBazString("103"); + } + + public static void assertOneofSet(TestOneof2 message) { + Assert.assertTrue(message.hasFooLazyMessage ()); + Assert.assertTrue(message.getFooLazyMessage().hasQuxInt()); + + Assert.assertTrue(message.hasBarString()); + Assert.assertTrue(message.hasBazInt ()); + Assert.assertTrue(message.hasBazString()); + + Assert.assertEquals(100 , message.getFooLazyMessage().getQuxInt()); + Assert.assertEquals("101", message.getBarString ()); + Assert.assertEquals(102 , message.getBazInt ()); + Assert.assertEquals("103", message.getBazString ()); + } + + public static void assertAtMostOneFieldSetOneof(TestOneof2 message) { + int count = 0; + if (message.hasFooInt()) { ++count; } + if (message.hasFooString()) { ++count; } + if (message.hasFooCord()) { ++count; } + if (message.hasFooStringPiece()) { ++count; } + if (message.hasFooBytes()) { ++count; } + if (message.hasFooEnum()) { ++count; } + if (message.hasFooMessage()) { ++count; } + if (message.hasFooGroup()) { ++count; } + if (message.hasFooLazyMessage()) { ++count; } + Assert.assertTrue(count <= 1); + + count = 0; + if (message.hasBarInt()) { ++count; } + if (message.hasBarString()) { ++count; } + if (message.hasBarCord()) { ++count; } + if (message.hasBarStringPiece()) { ++count; } + if (message.hasBarBytes()) { ++count; } + if (message.hasBarEnum()) { ++count; } + Assert.assertTrue(count <= 1); + + switch (message.getFooCase()) { + case FOO_INT: + Assert.assertTrue(message.hasFooInt()); + break; + case FOO_STRING: + Assert.assertTrue(message.hasFooString()); + break; + case FOO_CORD: + Assert.assertTrue(message.hasFooCord()); + break; + case FOO_BYTES: + Assert.assertTrue(message.hasFooBytes()); + break; + case FOO_ENUM: + Assert.assertTrue(message.hasFooEnum()); + break; + case FOO_MESSAGE: + Assert.assertTrue(message.hasFooMessage()); + break; + case FOOGROUP: + Assert.assertTrue(message.hasFooGroup()); + break; + case FOO_LAZY_MESSAGE: + Assert.assertTrue(message.hasFooLazyMessage()); + break; + case FOO_NOT_SET: + break; + } + } + // ================================================================= /** @@ -2915,8 +3050,8 @@ public final class TestUtil { return parent.newBuilderForField(field); } else { ExtensionRegistry.ExtensionInfo extension = - extensionRegistry.findExtensionByNumber(field.getContainingType(), - field.getNumber()); + extensionRegistry.findImmutableExtensionByNumber( + field.getContainingType(), field.getNumber()); Assert.assertNotNull(extension); Assert.assertNotNull(extension.defaultInstance); return extension.defaultInstance.newBuilderForType(); @@ -3078,6 +3213,13 @@ public final class TestUtil { message.setField(f("default_string_piece" ), "424"); message.setField(f("default_cord" ), "425"); + + message.setField(f("oneof_uint32" ), 601); + message.setField(f("oneof_nested_message"), + newBuilderForField(message, f("oneof_nested_message")) + .setField(nestedB, 602).build()); + message.setField(f("oneof_string" ), "603"); + message.setField(f("oneof_bytes" ), toBytes("604")); } // ------------------------------------------------------------------- @@ -3372,6 +3514,24 @@ public final class TestUtil { Assert.assertEquals("424", message.getField(f("default_string_piece"))); Assert.assertEquals("425", message.getField(f("default_cord"))); + + Assert.assertTrue(message.hasField(f("oneof_bytes"))); + Assert.assertEquals(toBytes("604"), message.getField(f("oneof_bytes"))); + + if (extensionRegistry == null) { + Assert.assertFalse(message.hasField(f("oneof_uint32"))); + Assert.assertFalse(message.hasField(f("oneof_nested_message"))); + Assert.assertFalse(message.hasField(f("oneof_string"))); + } else { + Assert.assertTrue(message.hasField(f("oneof_uint32"))); + Assert.assertTrue(message.hasField(f("oneof_nested_message"))); + Assert.assertTrue(message.hasField(f("oneof_string"))); + Assert.assertEquals(601, message.getField(f("oneof_uint32"))); + Assert.assertEquals(602, + ((MessageOrBuilder) message.getField(f("oneof_nested_message"))) + .getField(nestedB)); + Assert.assertEquals("603", message.getField(f("oneof_string"))); + } } // ------------------------------------------------------------------- @@ -3549,6 +3709,15 @@ public final class TestUtil { Assert.assertEquals("abc", message.getField(f("default_string_piece"))); Assert.assertEquals("123", message.getField(f("default_cord"))); + + Assert.assertFalse(message.hasField(f("oneof_uint32"))); + Assert.assertFalse(message.hasField(f("oneof_nested_message"))); + Assert.assertFalse(message.hasField(f("oneof_string"))); + Assert.assertFalse(message.hasField(f("oneof_bytes"))); + + Assert.assertEquals(0, message.getField(f("oneof_uint32"))); + Assert.assertEquals("", message.getField(f("oneof_string"))); + Assert.assertEquals(toBytes(""), message.getField(f("oneof_bytes"))); } @@ -3910,7 +4079,7 @@ public final class TestUtil { */ public static ByteString getGoldenMessage() { if (goldenMessage == null) { - goldenMessage = readBytesFromFile("golden_message"); + goldenMessage = readBytesFromFile("golden_message_oneof_implemented"); } return goldenMessage; } diff --git a/java/src/test/java/com/google/protobuf/TextFormatTest.java b/java/src/test/java/com/google/protobuf/TextFormatTest.java index 5323d70e..7cb70021 100644 --- a/java/src/test/java/com/google/protobuf/TextFormatTest.java +++ b/java/src/test/java/com/google/protobuf/TextFormatTest.java @@ -31,6 +31,7 @@ package com.google.protobuf; import com.google.protobuf.Descriptors.FieldDescriptor; +import com.google.protobuf.TextFormat.Parser.SingularOverwritePolicy; import protobuf_unittest.UnittestMset.TestMessageSet; import protobuf_unittest.UnittestMset.TestMessageSetExtension1; import protobuf_unittest.UnittestMset.TestMessageSetExtension2; @@ -39,6 +40,7 @@ import protobuf_unittest.UnittestProto.TestAllExtensions; import protobuf_unittest.UnittestProto.TestAllTypes; import protobuf_unittest.UnittestProto.TestAllTypes.NestedMessage; import protobuf_unittest.UnittestProto.TestEmptyMessage; +import protobuf_unittest.UnittestProto.TestOneof2; import junit.framework.TestCase; @@ -64,7 +66,7 @@ public class TextFormatTest extends TestCase { + "and \\t tabs and \\001 slashes \\\\"; private static String allFieldsSetText = TestUtil.readTextFromFile( - "text_format_unittest_data.txt"); + "text_format_unittest_data_oneof_implemented.txt"); private static String allExtensionsSetText = TestUtil.readTextFromFile( "text_format_unittest_extensions_data.txt"); @@ -109,6 +111,26 @@ public class TextFormatTest extends TestCase { " str: \"foo\"\n" + "}\n"; + private String messageSetTextWithRepeatedExtension = + "[protobuf_unittest.TestMessageSetExtension1] {\n" + + " i: 123\n" + + "}\n" + + "[protobuf_unittest.TestMessageSetExtension1] {\n" + + " i: 456\n" + + "}\n"; + + private final TextFormat.Parser parserAllowingUnknownFields = + TextFormat.Parser.newBuilder().setAllowUnknownFields(true).build(); + + private final TextFormat.Parser parserWithOverwriteForbidden = + TextFormat.Parser.newBuilder() + .setSingularOverwritePolicy( + SingularOverwritePolicy.FORBID_SINGULAR_OVERWRITES) + .build(); + + private final TextFormat.Parser defaultParser = + TextFormat.Parser.newBuilder().build(); + /** Print TestAllTypes and compare with golden file. */ public void testPrintMessage() throws Exception { String javaText = TextFormat.printToString(TestUtil.getAllSet()); @@ -250,8 +272,8 @@ public class TextFormatTest extends TestCase { .addRepeatedInt32 (1 << 31) .addRepeatedUint32(1 << 31) - .addRepeatedInt64 (1l << 63) - .addRepeatedUint64(1l << 63) + .addRepeatedInt64 (1L << 63) + .addRepeatedUint64(1L << 63) // Floats of various precisions and exponents. .addRepeatedDouble(123) @@ -371,6 +393,40 @@ public class TextFormatTest extends TestCase { TestMessageSetExtension2.messageSetExtension)); assertEquals("foo", messageSet.getExtension( TestMessageSetExtension2.messageSetExtension).getStr()); + + builder = TestMessageSet.newBuilder(); + TextFormat.merge(messageSetTextWithRepeatedExtension, extensionRegistry, + builder); + messageSet = builder.build(); + assertEquals(456, messageSet.getExtension( + TestMessageSetExtension1.messageSetExtension).getI()); + } + + public void testParseMessageSetWithOverwriteForbidden() throws Exception { + ExtensionRegistry extensionRegistry = ExtensionRegistry.newInstance(); + extensionRegistry.add(TestMessageSetExtension1.messageSetExtension); + extensionRegistry.add(TestMessageSetExtension2.messageSetExtension); + + TestMessageSet.Builder builder = TestMessageSet.newBuilder(); + parserWithOverwriteForbidden.merge( + messageSetText, extensionRegistry, builder); + TestMessageSet messageSet = builder.build(); + assertEquals(123, messageSet.getExtension( + TestMessageSetExtension1.messageSetExtension).getI()); + assertEquals("foo", messageSet.getExtension( + TestMessageSetExtension2.messageSetExtension).getStr()); + + builder = TestMessageSet.newBuilder(); + try { + parserWithOverwriteForbidden.merge( + messageSetTextWithRepeatedExtension, extensionRegistry, builder); + fail("expected parse exception"); + } catch (TextFormat.ParseException e) { + assertEquals("6:1: Non-repeated field " + + "\"protobuf_unittest.TestMessageSetExtension1.message_set_extension\"" + + " cannot be overwritten.", + e.getMessage()); + } } public void testParseNumericEnum() throws Exception { @@ -407,12 +463,51 @@ public class TextFormatTest extends TestCase { } } + private void assertParseErrorWithUnknownFields(String error, String text) { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + try { + parserAllowingUnknownFields.merge( + text, TestUtil.getExtensionRegistry(), builder); + fail("Expected parse exception."); + } catch (TextFormat.ParseException e) { + assertEquals(error, e.getMessage()); + } + } + + private TestAllTypes assertParseSuccessWithUnknownFields(String text) + throws TextFormat.ParseException { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + parserAllowingUnknownFields.merge( + text, TestUtil.getExtensionRegistry(), builder); + return builder.build(); + } + + private void assertParseErrorWithOverwriteForbidden(String error, + String text) { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + try { + parserWithOverwriteForbidden.merge( + text, TestUtil.getExtensionRegistry(), builder); + fail("Expected parse exception."); + } catch (TextFormat.ParseException e) { + assertEquals(error, e.getMessage()); + } + } + + private TestAllTypes assertParseSuccessWithOverwriteForbidden( + String text) throws TextFormat.ParseException { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + parserWithOverwriteForbidden.merge( + text, TestUtil.getExtensionRegistry(), builder); + return builder.build(); + } + public void testParseErrors() throws Exception { assertParseError( "1:16: Expected \":\".", "optional_int32 123"); assertParseError( - "1:23: Expected identifier.", + "1:23: Expected identifier. Found '?'", "optional_nested_enum: ?"); assertParseError( "1:18: Couldn't parse integer: Number must be positive: -1", @@ -469,10 +564,10 @@ public class TextFormatTest extends TestCase { // Delimiters must match. assertParseError( - "1:22: Expected identifier.", + "1:22: Expected identifier. Found '}'", "OptionalGroup < a: 1 }"); assertParseError( - "1:22: Expected identifier.", + "1:22: Expected identifier. Found '>'", "OptionalGroup { a: 1 >"); } @@ -762,7 +857,7 @@ public class TextFormatTest extends TestCase { TextFormat.shortDebugString(makeUnknownFieldSet())); } - public void testPrintToUnicodeString() { + public void testPrintToUnicodeString() throws Exception { assertEquals( "optional_string: \"abc\u3042efg\"\n" + "optional_bytes: \"\\343\\201\\202\"\n" + @@ -772,6 +867,49 @@ public class TextFormatTest extends TestCase { .setOptionalBytes(bytes(0xe3, 0x81, 0x82)) .addRepeatedString("\u3093XYZ") .build())); + + // Double quotes and backslashes should be escaped + assertEquals( + "optional_string: \"a\\\\bc\\\"ef\\\"g\"\n", + TextFormat.printToUnicodeString(TestAllTypes.newBuilder() + .setOptionalString("a\\bc\"ef\"g") + .build())); + + // Test escaping roundtrip + TestAllTypes message = TestAllTypes.newBuilder() + .setOptionalString("a\\bc\\\"ef\"g") + .build(); + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TextFormat.merge(TextFormat.printToUnicodeString(message), builder); + assertEquals(message.getOptionalString(), builder.getOptionalString()); + } + + public void testPrintToUnicodeStringWithNewlines() { + // No newlines at start and end + assertEquals("optional_string: \"test newlines\n\nin\nstring\"\n", + TextFormat.printToUnicodeString(TestAllTypes.newBuilder() + .setOptionalString("test newlines\n\nin\nstring") + .build())); + + // Newlines at start and end + assertEquals("optional_string: \"\ntest\nnewlines\n\nin\nstring\n\"\n", + TextFormat.printToUnicodeString(TestAllTypes.newBuilder() + .setOptionalString("\ntest\nnewlines\n\nin\nstring\n") + .build())); + + // Strings with 0, 1 and 2 newlines. + assertEquals("optional_string: \"\"\n", + TextFormat.printToUnicodeString(TestAllTypes.newBuilder() + .setOptionalString("") + .build())); + assertEquals("optional_string: \"\n\"\n", + TextFormat.printToUnicodeString(TestAllTypes.newBuilder() + .setOptionalString("\n") + .build())); + assertEquals("optional_string: \"\n\n\"\n", + TextFormat.printToUnicodeString(TestAllTypes.newBuilder() + .setOptionalString("\n\n") + .build())); } public void testPrintToUnicodeString_unknown() { @@ -783,4 +921,177 @@ public class TextFormatTest extends TestCase { .addLengthDelimited(bytes(0xe3, 0x81, 0x82)).build()) .build())); } + + public void testParseUnknownFields() throws Exception { + assertParseSuccessWithUnknownFields("unknown_field: 12345"); + assertParseSuccessWithUnknownFields("unknown_field: -12345"); + assertParseSuccessWithUnknownFields("unknown_field: 1.2345"); + assertParseSuccessWithUnknownFields("unknown_field: -1.2345"); + assertParseSuccessWithUnknownFields("unknown_field: 1.2345f"); + assertParseSuccessWithUnknownFields("unknown_field: -1.2345f"); + assertParseSuccessWithUnknownFields("unknown_field: inf"); + assertParseSuccessWithUnknownFields("unknown_field: -inf"); + assertParseSuccessWithUnknownFields("unknown_field: TYPE_STRING"); + assertParseSuccessWithUnknownFields("unknown_field: \"string value\""); + // Invalid field value + assertParseErrorWithUnknownFields( + "1:16: Invalid field value: -TYPE_STRING", + "unknown_field: -TYPE_STRING"); + // Two or more unknown fields + assertParseSuccessWithUnknownFields("unknown_field1: TYPE_STRING\n" + + "unknown_field2: 12345"); + // Unknown nested message + assertParseSuccessWithUnknownFields("unknown_message1: {}\n" + + "unknown_message2 {\n" + + " unknown_field: 12345\n" + + "}\n" + + "unknown_message3 <\n" + + " unknown_nested_message {\n" + + " unknown_field: 12345\n" + + " }\n" + + ">"); + // Unmatched delimeters for message body + assertParseErrorWithUnknownFields( + "1:19: Expected \"}\".", "unknown_message: {>"); + // Unknown extension + assertParseSuccessWithUnknownFields( + "[somewhere.unknown_extension1]: 12345\n" + + "[somewhere.unknown_extension2] {\n" + + " unknown_field: 12345\n" + + "}"); + // Unknown fields between known fields. + TestAllTypes expected = TestAllTypes.newBuilder() + .setOptionalInt32(1) + .setOptionalString("string") + .setOptionalNestedMessage(NestedMessage.newBuilder() + .setBb(2)) + .build(); + assertEquals(expected, assertParseSuccessWithUnknownFields( + "optional_int32: 1\n" + + "unknown_field: 12345\n" + + "optional_string: \"string\"\n" + + "unknown_message { unknown : 0 }\n" + + "optional_nested_message { bb: 2 }")); + // Nested unknown extensions. + assertParseSuccessWithUnknownFields( + "[test.extension1] <\n" + + " unknown_nested_message <\n" + + " [test.extension2] <\n" + + " unknown_field: 12345\n" + + " >\n" + + " >\n" + + ">"); + assertParseSuccessWithUnknownFields( + "[test.extension1] {\n" + + " unknown_nested_message {\n" + + " [test.extension2] {\n" + + " unknown_field: 12345\n" + + " }\n" + + " }\n" + + "}"); + assertParseSuccessWithUnknownFields( + "[test.extension1] <\n" + + " some_unknown_fields: <\n" + + " unknown_field: 12345\n" + + " >\n" + + ">"); + assertParseSuccessWithUnknownFields( + "[test.extension1] {\n" + + " some_unknown_fields: {\n" + + " unknown_field: 12345\n" + + " }\n" + + "}"); + } + + public void testParseNonRepeatedFields() throws Exception { + assertParseSuccessWithOverwriteForbidden( + "repeated_int32: 1\n" + + "repeated_int32: 2\n"); + assertParseSuccessWithOverwriteForbidden( + "RepeatedGroup { a: 1 }\n" + + "RepeatedGroup { a: 2 }\n"); + assertParseSuccessWithOverwriteForbidden( + "repeated_nested_message { bb: 1 }\n" + + "repeated_nested_message { bb: 2 }\n"); + assertParseErrorWithOverwriteForbidden( + "3:17: Non-repeated field " + + "\"protobuf_unittest.TestAllTypes.optional_int32\" " + + "cannot be overwritten.", + "optional_int32: 1\n" + + "optional_bool: true\n" + + "optional_int32: 1\n"); + assertParseErrorWithOverwriteForbidden( + "2:17: Non-repeated field " + + "\"protobuf_unittest.TestAllTypes.optionalgroup\" " + + "cannot be overwritten.", + "OptionalGroup { a: 1 }\n" + + "OptionalGroup { }\n"); + assertParseErrorWithOverwriteForbidden( + "2:33: Non-repeated field " + + "\"protobuf_unittest.TestAllTypes.optional_nested_message\" " + + "cannot be overwritten.", + "optional_nested_message { }\n" + + "optional_nested_message { bb: 3 }\n"); + assertParseErrorWithOverwriteForbidden( + "2:16: Non-repeated field " + + "\"protobuf_unittest.TestAllTypes.default_int32\" " + + "cannot be overwritten.", + "default_int32: 41\n" + // the default value + "default_int32: 41\n"); + assertParseErrorWithOverwriteForbidden( + "2:17: Non-repeated field " + + "\"protobuf_unittest.TestAllTypes.default_string\" " + + "cannot be overwritten.", + "default_string: \"zxcv\"\n" + + "default_string: \"asdf\"\n"); + } + + public void testParseShortRepeatedFormOfRepeatedFields() throws Exception { + assertParseSuccessWithOverwriteForbidden("repeated_foreign_enum: [FOREIGN_FOO, FOREIGN_BAR]"); + assertParseSuccessWithOverwriteForbidden("repeated_int32: [ 1, 2 ]\n"); + assertParseSuccessWithOverwriteForbidden("RepeatedGroup [{ a: 1 },{ a: 2 }]\n"); + assertParseSuccessWithOverwriteForbidden("repeated_nested_message [{ bb: 1 }, { bb: 2 }]\n"); + } + + public void testParseShortRepeatedFormOfNonRepeatedFields() throws Exception { + assertParseErrorWithOverwriteForbidden( + "1:17: Couldn't parse integer: For input string: \"[\"", + "optional_int32: [1]\n"); + } + + // ======================================================================= + // test oneof + + public void testOneofTextFormat() throws Exception { + TestOneof2.Builder builder = TestOneof2.newBuilder(); + TestUtil.setOneof(builder); + TestOneof2 message = builder.build(); + TestOneof2.Builder dest = TestOneof2.newBuilder(); + TextFormat.merge(TextFormat.printToUnicodeString(message), dest); + TestUtil.assertOneofSet(dest.build()); + } + + public void testOneofOverwriteForbidden() throws Exception { + String input = "foo_string: \"stringvalue\" foo_int: 123"; + TestOneof2.Builder builder = TestOneof2.newBuilder(); + try { + parserWithOverwriteForbidden.merge( + input, TestUtil.getExtensionRegistry(), builder); + fail("Expected parse exception."); + } catch (TextFormat.ParseException e) { + assertEquals("1:36: Field \"protobuf_unittest.TestOneof2.foo_int\"" + + " is specified along with field \"protobuf_unittest.TestOneof2.foo_string\"," + + " another member of oneof \"foo\".", e.getMessage()); + } + } + + public void testOneofOverwriteAllowed() throws Exception { + String input = "foo_string: \"stringvalue\" foo_int: 123"; + TestOneof2.Builder builder = TestOneof2.newBuilder(); + defaultParser.merge(input, TestUtil.getExtensionRegistry(), builder); + // Only the last value sticks. + TestOneof2 oneof = builder.build(); + assertFalse(oneof.hasFooString()); + assertTrue(oneof.hasFooInt()); + } } diff --git a/java/src/test/java/com/google/protobuf/UnknownFieldSetTest.java b/java/src/test/java/com/google/protobuf/UnknownFieldSetTest.java index ea088b32..d4139d20 100644 --- a/java/src/test/java/com/google/protobuf/UnknownFieldSetTest.java +++ b/java/src/test/java/com/google/protobuf/UnknownFieldSetTest.java @@ -31,10 +31,13 @@ package com.google.protobuf; import protobuf_unittest.UnittestProto; +import protobuf_unittest.UnittestProto.ForeignEnum; import protobuf_unittest.UnittestProto.TestAllExtensions; import protobuf_unittest.UnittestProto.TestAllTypes; import protobuf_unittest.UnittestProto.TestEmptyMessage; import protobuf_unittest.UnittestProto.TestEmptyMessageWithExtensions; +import protobuf_unittest.UnittestProto.TestPackedExtensions; +import protobuf_unittest.UnittestProto.TestPackedTypes; import junit.framework.TestCase; @@ -204,6 +207,13 @@ public class UnknownFieldSetTest extends TestCase { TestEmptyMessage.newBuilder().mergeFrom(emptyMessage).clear().build(); assertEquals(0, message.getSerializedSize()); } + + public void testClearField() throws Exception { + int fieldNumber = unknownFields.asMap().keySet().iterator().next(); + UnknownFieldSet fields = + UnknownFieldSet.newBuilder().mergeFrom(unknownFields).clearField(fieldNumber).build(); + assertFalse(fields.hasField(fieldNumber)); + } public void testParseKnownAndUnknown() throws Exception { // Test mixing known and unknown fields when parsing. @@ -434,4 +444,210 @@ public class UnknownFieldSetTest extends TestCase { assertEquals(copy, set); assertEquals(set.hashCode(), copy.hashCode()); } + + // ================================================================= + + public void testSerializeLite() throws Exception { + UnittestLite.TestEmptyMessageLite emptyMessageLite = + UnittestLite.TestEmptyMessageLite.parseFrom(allFieldsData); + assertEquals(allFieldsData.size(), emptyMessageLite.getSerializedSize()); + ByteString data = emptyMessageLite.toByteString(); + TestAllTypes message = TestAllTypes.parseFrom(data); + TestUtil.assertAllFieldsSet(message); + assertEquals(allFieldsData, data); + } + + public void testAllExtensionsLite() throws Exception { + TestAllExtensions allExtensions = TestUtil.getAllExtensionsSet(); + ByteString allExtensionsData = allExtensions.toByteString(); + UnittestLite.TestEmptyMessageLite emptyMessageLite = + UnittestLite.TestEmptyMessageLite.PARSER.parseFrom(allExtensionsData); + ByteString data = emptyMessageLite.toByteString(); + TestAllExtensions message = + TestAllExtensions.parseFrom(data, TestUtil.getExtensionRegistry()); + TestUtil.assertAllExtensionsSet(message); + assertEquals(allExtensionsData, data); + } + + public void testAllPackedFieldsLite() throws Exception { + TestPackedTypes allPackedFields = TestUtil.getPackedSet(); + ByteString allPackedData = allPackedFields.toByteString(); + UnittestLite.TestEmptyMessageLite emptyMessageLite = + UnittestLite.TestEmptyMessageLite.parseFrom(allPackedData); + ByteString data = emptyMessageLite.toByteString(); + TestPackedTypes message = + TestPackedTypes.parseFrom(data, TestUtil.getExtensionRegistry()); + TestUtil.assertPackedFieldsSet(message); + assertEquals(allPackedData, data); + } + + public void testAllPackedExtensionsLite() throws Exception { + TestPackedExtensions allPackedExtensions = TestUtil.getPackedExtensionsSet(); + ByteString allPackedExtensionsData = allPackedExtensions.toByteString(); + UnittestLite.TestEmptyMessageLite emptyMessageLite = + UnittestLite.TestEmptyMessageLite.parseFrom(allPackedExtensionsData); + ByteString data = emptyMessageLite.toByteString(); + TestPackedExtensions message = + TestPackedExtensions.parseFrom(data, TestUtil.getExtensionRegistry()); + TestUtil.assertPackedExtensionsSet(message); + assertEquals(allPackedExtensionsData, data); + } + + public void testCopyFromLite() throws Exception { + UnittestLite.TestEmptyMessageLite emptyMessageLite = + UnittestLite.TestEmptyMessageLite.parseFrom(allFieldsData); + UnittestLite.TestEmptyMessageLite emptyMessageLite2 = + UnittestLite.TestEmptyMessageLite.newBuilder() + .mergeFrom(emptyMessageLite).build(); + assertEquals(emptyMessageLite.toByteString(), emptyMessageLite2.toByteString()); + } + + public void testMergeFromLite() throws Exception { + TestAllTypes message1 = TestAllTypes.newBuilder() + .setOptionalInt32(1) + .setOptionalString("foo") + .addRepeatedString("bar") + .setOptionalNestedEnum(TestAllTypes.NestedEnum.BAZ) + .build(); + + TestAllTypes message2 = TestAllTypes.newBuilder() + .setOptionalInt64(2) + .setOptionalString("baz") + .addRepeatedString("qux") + .setOptionalForeignEnum(ForeignEnum.FOREIGN_BAZ) + .build(); + + ByteString data1 = message1.toByteString(); + UnittestLite.TestEmptyMessageLite emptyMessageLite1 = + UnittestLite.TestEmptyMessageLite.parseFrom(data1); + ByteString data2 = message2.toByteString(); + UnittestLite.TestEmptyMessageLite emptyMessageLite2 = + UnittestLite.TestEmptyMessageLite.parseFrom(data2); + + message1 = TestAllTypes.newBuilder(message1).mergeFrom(message2).build(); + emptyMessageLite1 = UnittestLite.TestEmptyMessageLite.newBuilder(emptyMessageLite1) + .mergeFrom(emptyMessageLite2).build(); + + data1 = emptyMessageLite1.toByteString(); + message2 = TestAllTypes.parseFrom(data1); + + assertEquals(message1, message2); + } + + public void testWrongTypeTreatedAsUnknownLite() throws Exception { + // Test that fields of the wrong wire type are treated like unknown fields + // when parsing. + + ByteString bizarroData = getBizarroData(); + TestAllTypes allTypesMessage = TestAllTypes.parseFrom(bizarroData); + UnittestLite.TestEmptyMessageLite emptyMessageLite = + UnittestLite.TestEmptyMessageLite.parseFrom(bizarroData); + ByteString data = emptyMessageLite.toByteString(); + TestAllTypes allTypesMessage2 = TestAllTypes.parseFrom(data); + + assertEquals(allTypesMessage.toString(), allTypesMessage2.toString()); + } + + public void testUnknownExtensionsLite() throws Exception { + // Make sure fields are properly parsed to the UnknownFieldSet even when + // they are declared as extension numbers. + + UnittestLite.TestEmptyMessageWithExtensionsLite message = + UnittestLite.TestEmptyMessageWithExtensionsLite.parseFrom(allFieldsData); + + assertEquals(allFieldsData, message.toByteString()); + } + + public void testWrongExtensionTypeTreatedAsUnknownLite() throws Exception { + // Test that fields of the wrong wire type are treated like unknown fields + // when parsing extensions. + + ByteString bizarroData = getBizarroData(); + TestAllExtensions allExtensionsMessage = + TestAllExtensions.parseFrom(bizarroData); + UnittestLite.TestEmptyMessageLite emptyMessageLite = + UnittestLite.TestEmptyMessageLite.parseFrom(bizarroData); + + // All fields should have been interpreted as unknown, so the byte strings + // should be the same. + assertEquals(emptyMessageLite.toByteString(), + allExtensionsMessage.toByteString()); + } + + public void testParseUnknownEnumValueLite() throws Exception { + Descriptors.FieldDescriptor singularField = + TestAllTypes.getDescriptor().findFieldByName("optional_nested_enum"); + Descriptors.FieldDescriptor repeatedField = + TestAllTypes.getDescriptor().findFieldByName("repeated_nested_enum"); + assertNotNull(singularField); + assertNotNull(repeatedField); + + ByteString data = + UnknownFieldSet.newBuilder() + .addField(singularField.getNumber(), + UnknownFieldSet.Field.newBuilder() + .addVarint(TestAllTypes.NestedEnum.BAR.getNumber()) + .addVarint(5) // not valid + .build()) + .addField(repeatedField.getNumber(), + UnknownFieldSet.Field.newBuilder() + .addVarint(TestAllTypes.NestedEnum.FOO.getNumber()) + .addVarint(4) // not valid + .addVarint(TestAllTypes.NestedEnum.BAZ.getNumber()) + .addVarint(6) // not valid + .build()) + .build() + .toByteString(); + + UnittestLite.TestEmptyMessageLite emptyMessageLite = + UnittestLite.TestEmptyMessageLite.parseFrom(data); + data = emptyMessageLite.toByteString(); + + { + TestAllTypes message = TestAllTypes.parseFrom(data); + assertEquals(TestAllTypes.NestedEnum.BAR, + message.getOptionalNestedEnum()); + assertEquals( + Arrays.asList(TestAllTypes.NestedEnum.FOO, TestAllTypes.NestedEnum.BAZ), + message.getRepeatedNestedEnumList()); + assertEquals(Arrays.asList(5L), + message.getUnknownFields() + .getField(singularField.getNumber()) + .getVarintList()); + assertEquals(Arrays.asList(4L, 6L), + message.getUnknownFields() + .getField(repeatedField.getNumber()) + .getVarintList()); + } + + { + TestAllExtensions message = + TestAllExtensions.parseFrom(data, TestUtil.getExtensionRegistry()); + assertEquals(TestAllTypes.NestedEnum.BAR, + message.getExtension(UnittestProto.optionalNestedEnumExtension)); + assertEquals( + Arrays.asList(TestAllTypes.NestedEnum.FOO, TestAllTypes.NestedEnum.BAZ), + message.getExtension(UnittestProto.repeatedNestedEnumExtension)); + assertEquals(Arrays.asList(5L), + message.getUnknownFields() + .getField(singularField.getNumber()) + .getVarintList()); + assertEquals(Arrays.asList(4L, 6L), + message.getUnknownFields() + .getField(repeatedField.getNumber()) + .getVarintList()); + } + } + + public void testClearLite() throws Exception { + UnittestLite.TestEmptyMessageLite emptyMessageLite1 = + UnittestLite.TestEmptyMessageLite.parseFrom(allFieldsData); + UnittestLite.TestEmptyMessageLite emptyMessageLite2 = + UnittestLite.TestEmptyMessageLite.newBuilder() + .mergeFrom(emptyMessageLite1).clear().build(); + assertEquals(0, emptyMessageLite2.getSerializedSize()); + ByteString data = emptyMessageLite2.toByteString(); + assertEquals(0, data.size()); + } + } diff --git a/java/src/test/java/com/google/protobuf/UnmodifiableLazyStringListTest.java b/java/src/test/java/com/google/protobuf/UnmodifiableLazyStringListTest.java index ed5d069e..ad4bbf8d 100644 --- a/java/src/test/java/com/google/protobuf/UnmodifiableLazyStringListTest.java +++ b/java/src/test/java/com/google/protobuf/UnmodifiableLazyStringListTest.java @@ -33,6 +33,7 @@ package com.google.protobuf; import junit.framework.TestCase; import java.util.Iterator; +import java.util.List; import java.util.ListIterator; /** @@ -60,6 +61,11 @@ public class UnmodifiableLazyStringListTest extends TestCase { assertEquals(BYTE_STRING_A, list.getByteString(0)); assertEquals(BYTE_STRING_B, list.getByteString(1)); assertEquals(BYTE_STRING_C, list.getByteString(2)); + + List byteStringList = list.asByteStringList(); + assertSame(list.getByteString(0), byteStringList.get(0)); + assertSame(list.getByteString(1), byteStringList.get(1)); + assertSame(list.getByteString(2), byteStringList.get(2)); } public void testModifyMethods() { @@ -88,6 +94,35 @@ public class UnmodifiableLazyStringListTest extends TestCase { } catch (UnsupportedOperationException e) { // expected } + assertEquals(3, list.size()); + + List byteStringList = list.asByteStringList(); + try { + byteStringList.remove(0); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + assertEquals(3, list.size()); + assertEquals(3, byteStringList.size()); + + try { + byteStringList.add(BYTE_STRING_B); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + assertEquals(3, list.size()); + assertEquals(3, byteStringList.size()); + + try { + byteStringList.set(1, BYTE_STRING_B); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + assertEquals(3, list.size()); + assertEquals(3, byteStringList.size()); } public void testIterator() { @@ -108,6 +143,20 @@ public class UnmodifiableLazyStringListTest extends TestCase { } assertEquals(3, count); + List byteStringList = list.asByteStringList(); + Iterator byteIter = byteStringList.iterator(); + count = 0; + while (byteIter.hasNext()) { + byteIter.next(); + count++; + try { + byteIter.remove(); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + } + assertEquals(3, count); } public void testListIterator() { @@ -140,6 +189,32 @@ public class UnmodifiableLazyStringListTest extends TestCase { } assertEquals(3, count); + List byteStringList = list.asByteStringList(); + ListIterator byteIter = byteStringList.listIterator(); + count = 0; + while (byteIter.hasNext()) { + byteIter.next(); + count++; + try { + byteIter.remove(); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + try { + byteIter.set(BYTE_STRING_A); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + try { + byteIter.add(BYTE_STRING_A); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + } + assertEquals(3, count); } private LazyStringArrayList createSampleList() { diff --git a/java/src/test/java/com/google/protobuf/WireFormatTest.java b/java/src/test/java/com/google/protobuf/WireFormatTest.java index 94f62134..146029df 100644 --- a/java/src/test/java/com/google/protobuf/WireFormatTest.java +++ b/java/src/test/java/com/google/protobuf/WireFormatTest.java @@ -40,6 +40,8 @@ import protobuf_unittest.UnittestProto; import protobuf_unittest.UnittestProto.TestAllExtensions; import protobuf_unittest.UnittestProto.TestAllTypes; import protobuf_unittest.UnittestProto.TestFieldOrderings; +import protobuf_unittest.UnittestProto.TestOneof2; +import protobuf_unittest.UnittestProto.TestOneofBackwardsCompatible; import protobuf_unittest.UnittestProto.TestPackedExtensions; import protobuf_unittest.UnittestProto.TestPackedTypes; import protobuf_unittest.UnittestMset.TestMessageSet; @@ -218,8 +220,8 @@ public class WireFormatTest extends TestCase { } public void testExtensionsSerializedSize() throws Exception { - assertEquals(TestUtil.getAllSet().getSerializedSize(), - TestUtil.getAllExtensionsSet().getSerializedSize()); + assertNotSame(TestUtil.getAllSet().getSerializedSize(), + TestUtil.getAllExtensionsSet().getSerializedSize()); } public void testSerializeDelimited() throws Exception { @@ -577,4 +579,28 @@ public class WireFormatTest extends TestCase { assertEquals(123, messageSet.getExtension( TestMessageSetExtension1.messageSetExtension).getI()); } + + // ================================================================ + // oneof + public void testOneofWireFormat() throws Exception { + TestOneof2.Builder builder = TestOneof2.newBuilder(); + TestUtil.setOneof(builder); + TestOneof2 message = builder.build(); + ByteString rawBytes = message.toByteString(); + + assertEquals(rawBytes.size(), message.getSerializedSize()); + + TestOneof2 message2 = TestOneof2.parseFrom(rawBytes); + TestUtil.assertOneofSet(message2); + } + + public void testOneofOnlyLastSet() throws Exception { + TestOneofBackwardsCompatible source = TestOneofBackwardsCompatible + .newBuilder().setFooInt(100).setFooString("101").build(); + + ByteString rawBytes = source.toByteString(); + TestOneof2 message = TestOneof2.parseFrom(rawBytes); + assertFalse(message.hasFooInt()); + assertTrue(message.hasFooString()); + } } 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 new file mode 100644 index 00000000..8801db90 --- /dev/null +++ b/java/src/test/java/com/google/protobuf/lazy_fields_lite.proto @@ -0,0 +1,61 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// http://code.google.com/p/protobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Author: Naoki Iwasaki (niwasaki@google.com) +// +// A proto file with lazy fields + + +package protobuf_unittest; + +option optimize_for = LITE_RUNTIME; + +message LazyMessageLite { + optional int32 num = 1; + optional int32 num_with_default = 2 [default = 421]; + optional LazyInnerMessageLite inner = 3 [lazy = true]; + repeated LazyInnerMessageLite repeated_inner = 4 [lazy = true]; + + oneof oneof_field { + int32 oneof_num = 5; + LazyInnerMessageLite oneof_inner = 6 [lazy = true]; + } +} + +message LazyInnerMessageLite { + optional int32 num = 1; + optional int32 num_with_default = 2 [default = 42]; + optional LazyNestedInnerMessageLite nested = 3 [lazy = true]; +} + +message LazyNestedInnerMessageLite { + optional int32 num = 1; + optional int32 num_with_default = 2 [default = 4]; +} diff --git a/java/src/test/java/com/google/protobuf/lite_equals_and_hash.proto b/java/src/test/java/com/google/protobuf/lite_equals_and_hash.proto new file mode 100644 index 00000000..8b18f078 --- /dev/null +++ b/java/src/test/java/com/google/protobuf/lite_equals_and_hash.proto @@ -0,0 +1,55 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// http://code.google.com/p/protobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Author: pbogle@google.com (Phil Bogle) + + +package protobuf_unittest.lite_equals_and_hash; + +// This proto definition is used to test that java_generate_equals_and_hash +// works correctly with the LITE_RUNTIME. +option java_generate_equals_and_hash = true; +option optimize_for = LITE_RUNTIME; + +message Foo { + optional int32 value = 1; + repeated Bar bar = 2; +} + +message Bar { + optional string name = 1; +} + +message BarPrime { + optional string name = 1; +} + +message Empty { +} diff --git a/java/src/test/java/com/google/protobuf/multiple_files_test.proto b/java/src/test/java/com/google/protobuf/multiple_files_test.proto index 9a040145..401e1681 100644 --- a/java/src/test/java/com/google/protobuf/multiple_files_test.proto +++ b/java/src/test/java/com/google/protobuf/multiple_files_test.proto @@ -38,12 +38,14 @@ option java_generic_services = true; // auto-added import "google/protobuf/unittest.proto"; +import "google/protobuf/descriptor.proto"; package protobuf_unittest; option java_multiple_files = true; option java_outer_classname = "MultipleFilesTestProto"; + message MessageWithNoOuter { message NestedMessage { optional int32 i = 1; @@ -57,8 +59,12 @@ message MessageWithNoOuter { optional EnumWithNoOuter foreign_enum = 4; } +extend google.protobuf.EnumValueOptions { + optional int32 enum_value_option = 7654321; +} + enum EnumWithNoOuter { - FOO = 1; + FOO = 1 [(enum_value_option) = 12345]; BAR = 2; } diff --git a/java/src/test/java/com/google/protobuf/nested_extension.proto b/java/src/test/java/com/google/protobuf/nested_extension.proto index 9fe5d560..777db032 100644 --- a/java/src/test/java/com/google/protobuf/nested_extension.proto +++ b/java/src/test/java/com/google/protobuf/nested_extension.proto @@ -38,6 +38,7 @@ import "com/google/protobuf/non_nested_extension.proto"; package protobuf_unittest; + message MyNestedExtension { extend MessageToBeExtended { optional MessageToBeExtended recursiveExtension = 2; diff --git a/java/src/test/java/com/google/protobuf/non_nested_extension.proto b/java/src/test/java/com/google/protobuf/non_nested_extension.proto index f61b419b..17c0b623 100644 --- a/java/src/test/java/com/google/protobuf/non_nested_extension.proto +++ b/java/src/test/java/com/google/protobuf/non_nested_extension.proto @@ -35,6 +35,7 @@ package protobuf_unittest; + message MessageToBeExtended { extensions 1 to max; } diff --git a/java/src/test/java/com/google/protobuf/outer_class_name_test.proto b/java/src/test/java/com/google/protobuf/outer_class_name_test.proto new file mode 100644 index 00000000..2c251e5d --- /dev/null +++ b/java/src/test/java/com/google/protobuf/outer_class_name_test.proto @@ -0,0 +1,38 @@ +// 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 protobuf_unittest; + + +// This message's name is the same with the default outer class name of this +// proto file. It's used to test if the compiler can avoid this conflict +// correctly. +message OuterClassNameTest { +} diff --git a/java/src/test/java/com/google/protobuf/outer_class_name_test2.proto b/java/src/test/java/com/google/protobuf/outer_class_name_test2.proto new file mode 100644 index 00000000..98b5b7a9 --- /dev/null +++ b/java/src/test/java/com/google/protobuf/outer_class_name_test2.proto @@ -0,0 +1,42 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// http://code.google.com/p/protobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package protobuf_unittest; + + +message TestMessage2 { + message NestedMessage { + // This message's name is the same with the default outer class name of this + // proto file. It's used to test if the compiler can avoid this conflict + // correctly. + message OuterClassNameTest2 { + } + } +} diff --git a/java/src/test/java/com/google/protobuf/outer_class_name_test3.proto b/java/src/test/java/com/google/protobuf/outer_class_name_test3.proto new file mode 100644 index 00000000..22fb1e65 --- /dev/null +++ b/java/src/test/java/com/google/protobuf/outer_class_name_test3.proto @@ -0,0 +1,43 @@ +// 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 protobuf_unittest; + + +message TestMessage3 { + message NestedMessage { + // This enum's name is the same with the default outer class name of this + // proto file. It's used to test if the compiler can avoid this conflict + // correctly. + enum OuterClassNameTest3 { + DUMMY_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 6e67d97a..97f27129 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 @@ -42,8 +42,12 @@ package io_protocol_tests; 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; + optional string class = 3; } message Descriptor { @@ -54,6 +58,9 @@ message Descriptor { optional string descriptor = 1; } optional NestedDescriptor nested_descriptor = 2; + enum NestedEnum { + FOO = 1; + } } message Parser { @@ -66,6 +73,9 @@ message Parser { message Deprecated { enum TestEnum { FOO = 1; + + // Test if @Deprecated annotation conflicts with Deprecated message name. + BAR = 2 [ deprecated = true ]; } optional int32 field1 = 1 [deprecated=true]; @@ -106,3 +116,42 @@ service TestConflictingMethodNames { rpc Override(TestMessage) returns (TestMessage); } +message TestConflictingFieldNames { + enum TestEnum { + FOO = 1; + } + message TestMessage { + } + repeated int32 int32_field = 1; + repeated TestEnum enum_field = 2; + repeated string string_field = 3; + repeated bytes bytes_field = 4; + repeated TestMessage message_field = 5; + + optional int32 int32_field_count = 11; + optional TestEnum enum_field_count = 12; + optional string string_field_count = 13; + optional bytes bytes_field_count = 14; + optional TestMessage message_field_count = 15; + + repeated int32 Int32Field = 21; + repeated TestEnum EnumField = 22; + repeated string StringField = 23; + repeated bytes BytesField = 24; + repeated TestMessage MessageField = 25; + + // This field conflicts with "int32_field" as they both generate + // the method getInt32FieldList(). + required int32 int32_field_list = 31; + + extensions 1000 to max; + + repeated int64 int64_field = 41; + extend TestConflictingFieldNames { + // We don't generate accessors for extensions so the following extension + // fields don't conflict with the repeated field "int64_field". + optional int64 int64_field_count = 1001; + optional int64 int64_field_list = 1002; + } +} + diff --git a/java/src/test/java/com/google/protobuf/test_check_utf8.proto b/java/src/test/java/com/google/protobuf/test_check_utf8.proto new file mode 100644 index 00000000..c327ed67 --- /dev/null +++ b/java/src/test/java/com/google/protobuf/test_check_utf8.proto @@ -0,0 +1,50 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// http://code.google.com/p/protobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Author: Jacob Butcher (jbaum@google.com) +// +// Test file option java_string_check_utf8. + +package proto2_test_check_utf8; + +option java_outer_classname = "TestCheckUtf8"; +option java_string_check_utf8 = true; + +message StringWrapper { + required string req = 1; + optional string opt = 2; + repeated string rep = 3; +} + +message BytesWrapper { + required bytes req = 1; + optional bytes opt = 2; + repeated bytes rep = 3; +} diff --git a/java/src/test/java/com/google/protobuf/test_check_utf8_size.proto b/java/src/test/java/com/google/protobuf/test_check_utf8_size.proto new file mode 100644 index 00000000..591bc563 --- /dev/null +++ b/java/src/test/java/com/google/protobuf/test_check_utf8_size.proto @@ -0,0 +1,51 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// http://code.google.com/p/protobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Author: Jacob Butcher (jbaum@google.com) +// +// Test file option java_string_check_utf8. + +package proto2_test_check_utf8_size; + +option java_outer_classname = "TestCheckUtf8Size"; +option java_string_check_utf8 = true; +option optimize_for = CODE_SIZE; + +message StringWrapperSize { + required string req = 1; + optional string opt = 2; + repeated string rep = 3; +} + +message BytesWrapperSize { + required bytes req = 1; + optional bytes opt = 2; + repeated bytes rep = 3; +} diff --git a/java/src/test/java/com/google/protobuf/test_custom_options.proto b/java/src/test/java/com/google/protobuf/test_custom_options.proto new file mode 100644 index 00000000..f509d29e --- /dev/null +++ b/java/src/test/java/com/google/protobuf/test_custom_options.proto @@ -0,0 +1,43 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// http://code.google.com/p/protobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Author: Feng Xiao (xiaofeng@google.com) +// +// Test that custom options defined in a proto file's dependencies are properly +// initialized. + +package protobuf_unittest; + + +import "google/protobuf/unittest_custom_options.proto"; + +message TestMessageWithCustomOptionsContainer { + optional TestMessageWithCustomOptions field = 1; +} diff --git a/java/src/test/java/com/google/protobuf/test_extra_interfaces.proto b/java/src/test/java/com/google/protobuf/test_extra_interfaces.proto new file mode 100644 index 00000000..3d6ce0c7 --- /dev/null +++ b/java/src/test/java/com/google/protobuf/test_extra_interfaces.proto @@ -0,0 +1,60 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// http://code.google.com/p/protobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Author: Darick Tong (darick@google.com) + +package protobuf_unittest; + +message Proto1 { + option experimental_java_message_interface = + "com.google.protobuf.ExtraInterfaces.HasBoolValue"; + + option experimental_java_interface_extends = + "com.google.protobuf.ExtraInterfaces.HasByteValue"; + + option experimental_java_message_interface = + "com.google.protobuf.ExtraInterfaces.HasStringValue"; + + option experimental_java_builder_interface = + "com.google.protobuf.ExtraInterfaces.HasStringValueBuilder" + ""; + + optional string string_value = 1; + optional bool bool_value = 2; + optional bytes byte_value = 3; + optional int32 int_value = 4; +} + +message Proto2 { + option experimental_java_message_interface = + "com.google.protobuf.ExtraInterfaces.HasBoolValue"; + + optional bool bool_value = 1; +} -- cgit v1.2.3