diff options
Diffstat (limited to 'java')
-rw-r--r-- | java/core/generate-sources-build.xml | 20 | ||||
-rw-r--r-- | java/core/generate-test-sources-build.xml | 43 | ||||
-rw-r--r-- | java/core/pom.xml | 118 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/AbstractMessage.java (renamed from java/src/main/java/com/google/protobuf/AbstractMessage.java) | 61 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/AbstractMessageLite.java (renamed from java/src/main/java/com/google/protobuf/AbstractMessageLite.java) | 79 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/AbstractParser.java (renamed from java/src/main/java/com/google/protobuf/AbstractParser.java) | 85 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/AbstractProtobufList.java (renamed from java/src/main/java/com/google/protobuf/AbstractProtobufList.java) | 48 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/BlockingRpcChannel.java (renamed from java/src/main/java/com/google/protobuf/BlockingRpcChannel.java) | 0 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/BlockingService.java (renamed from java/src/main/java/com/google/protobuf/BlockingService.java) | 0 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/BooleanArrayList.java (renamed from java/src/main/java/com/google/protobuf/BooleanArrayList.java) | 66 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/ByteBufferWriter.java | 145 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/ByteOutput.java | 116 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/ByteString.java (renamed from java/src/main/java/com/google/protobuf/ByteString.java) | 647 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/CodedInputStream.java (renamed from java/src/main/java/com/google/protobuf/CodedInputStream.java) | 237 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/CodedOutputStream.java | 2664 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/Descriptors.java (renamed from java/src/main/java/com/google/protobuf/Descriptors.java) | 228 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/DoubleArrayList.java (renamed from java/src/main/java/com/google/protobuf/DoubleArrayList.java) | 60 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/DynamicMessage.java (renamed from java/src/main/java/com/google/protobuf/DynamicMessage.java) | 40 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/ExperimentalApi.java | 66 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/Extension.java (renamed from java/src/main/java/com/google/protobuf/Extension.java) | 1 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/ExtensionLite.java (renamed from java/src/main/java/com/google/protobuf/ExtensionLite.java) | 0 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/ExtensionRegistry.java (renamed from java/src/main/java/com/google/protobuf/ExtensionRegistry.java) | 0 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/ExtensionRegistryLite.java (renamed from java/src/main/java/com/google/protobuf/ExtensionRegistryLite.java) | 0 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/FieldSet.java (renamed from java/src/main/java/com/google/protobuf/FieldSet.java) | 19 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/FloatArrayList.java (renamed from java/src/main/java/com/google/protobuf/FloatArrayList.java) | 59 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/GeneratedMessage.java (renamed from java/src/main/java/com/google/protobuf/GeneratedMessage.java) | 321 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/GeneratedMessageLite.java (renamed from java/src/main/java/com/google/protobuf/GeneratedMessageLite.java) | 1295 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/IntArrayList.java (renamed from java/src/main/java/com/google/protobuf/IntArrayList.java) | 59 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/Internal.java (renamed from java/src/main/java/com/google/protobuf/Internal.java) | 100 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/InvalidProtocolBufferException.java (renamed from java/src/main/java/com/google/protobuf/InvalidProtocolBufferException.java) | 12 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/LazyField.java (renamed from java/src/main/java/com/google/protobuf/LazyField.java) | 12 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/LazyFieldLite.java (renamed from java/src/main/java/com/google/protobuf/LazyFieldLite.java) | 153 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/LazyStringArrayList.java (renamed from java/src/main/java/com/google/protobuf/LazyStringArrayList.java) | 52 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/LazyStringList.java (renamed from java/src/main/java/com/google/protobuf/LazyStringList.java) | 0 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/LongArrayList.java (renamed from java/src/main/java/com/google/protobuf/LongArrayList.java) | 59 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/MapEntry.java (renamed from java/src/main/java/com/google/protobuf/MapEntry.java) | 0 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/MapEntryLite.java (renamed from java/src/main/java/com/google/protobuf/MapEntryLite.java) | 10 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/MapField.java (renamed from java/src/main/java/com/google/protobuf/MapField.java) | 3 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/MapFieldLite.java (renamed from java/src/main/java/com/google/protobuf/MapFieldLite.java) | 11 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/Message.java (renamed from java/src/main/java/com/google/protobuf/Message.java) | 70 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/MessageLite.java (renamed from java/src/main/java/com/google/protobuf/MessageLite.java) | 21 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/MessageLiteOrBuilder.java (renamed from java/src/main/java/com/google/protobuf/MessageLiteOrBuilder.java) | 0 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/MessageLiteToString.java | 239 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/MessageOrBuilder.java (renamed from java/src/main/java/com/google/protobuf/MessageOrBuilder.java) | 2 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/MessageReflection.java (renamed from java/src/main/java/com/google/protobuf/MessageReflection.java) | 107 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/MutabilityOracle.java (renamed from java/src/main/java/com/google/protobuf/MutabilityOracle.java) | 0 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/NioByteString.java | 290 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/Parser.java (renamed from java/src/main/java/com/google/protobuf/Parser.java) | 11 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/ProtobufArrayList.java (renamed from java/src/main/java/com/google/protobuf/ProtobufArrayList.java) | 20 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/ProtocolMessageEnum.java (renamed from java/src/main/java/com/google/protobuf/ProtocolMessageEnum.java) | 1 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/ProtocolStringList.java (renamed from java/src/main/java/com/google/protobuf/ProtocolStringList.java) | 0 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/RepeatedFieldBuilder.java (renamed from java/src/main/java/com/google/protobuf/RepeatedFieldBuilder.java) | 8 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/RopeByteString.java (renamed from java/src/main/java/com/google/protobuf/RopeByteString.java) | 286 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/RpcCallback.java (renamed from java/src/main/java/com/google/protobuf/RpcCallback.java) | 0 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/RpcChannel.java (renamed from java/src/main/java/com/google/protobuf/RpcChannel.java) | 0 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/RpcController.java (renamed from java/src/main/java/com/google/protobuf/RpcController.java) | 0 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/RpcUtil.java (renamed from java/src/main/java/com/google/protobuf/RpcUtil.java) | 4 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/Service.java (renamed from java/src/main/java/com/google/protobuf/Service.java) | 0 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/ServiceException.java (renamed from java/src/main/java/com/google/protobuf/ServiceException.java) | 0 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/SingleFieldBuilder.java (renamed from java/src/main/java/com/google/protobuf/SingleFieldBuilder.java) | 2 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/SmallSortedMap.java (renamed from java/src/main/java/com/google/protobuf/SmallSortedMap.java) | 108 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/TextFormat.java (renamed from java/src/main/java/com/google/protobuf/TextFormat.java) | 228 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/TextFormatEscaper.java | 137 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/TextFormatParseInfoTree.java | 225 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/TextFormatParseLocation.java | 104 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/UninitializedMessageException.java (renamed from java/src/main/java/com/google/protobuf/UninitializedMessageException.java) | 0 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/UnknownFieldSet.java (renamed from java/src/main/java/com/google/protobuf/UnknownFieldSet.java) | 77 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/UnknownFieldSetLite.java (renamed from java/src/main/java/com/google/protobuf/UnknownFieldSetLite.java) | 110 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/UnmodifiableLazyStringList.java (renamed from java/src/main/java/com/google/protobuf/UnmodifiableLazyStringList.java) | 50 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/UnsafeByteOperations.java | 84 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/Utf8.java | 1683 | ||||
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/WireFormat.java (renamed from java/src/main/java/com/google/protobuf/WireFormat.java) | 19 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/AbstractMessageTest.java (renamed from java/src/test/java/com/google/protobuf/AbstractMessageTest.java) | 42 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/AnyTest.java (renamed from java/src/test/java/com/google/protobuf/AnyTest.java) | 45 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/BooleanArrayListTest.java (renamed from java/src/test/java/com/google/protobuf/BooleanArrayListTest.java) | 22 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/BoundedByteStringTest.java (renamed from java/src/test/java/com/google/protobuf/BoundedByteStringTest.java) | 7 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/ByteBufferWriterTest.java | 81 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/ByteStringTest.java (renamed from java/src/test/java/com/google/protobuf/ByteStringTest.java) | 38 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/CheckUtf8Test.java (renamed from java/src/test/java/com/google/protobuf/CheckUtf8Test.java) | 1 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/CodedInputStreamTest.java (renamed from java/src/test/java/com/google/protobuf/CodedInputStreamTest.java) | 52 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/CodedOutputStreamTest.java | 776 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/DeprecatedFieldTest.java (renamed from java/src/test/java/com/google/protobuf/DeprecatedFieldTest.java) | 1 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/DescriptorsTest.java (renamed from java/src/test/java/com/google/protobuf/DescriptorsTest.java) | 46 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/DoubleArrayListTest.java (renamed from java/src/test/java/com/google/protobuf/DoubleArrayListTest.java) | 20 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/DynamicMessageTest.java (renamed from java/src/test/java/com/google/protobuf/DynamicMessageTest.java) | 2 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/EnumTest.java | 76 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/FieldPresenceTest.java (renamed from java/src/test/java/com/google/protobuf/FieldPresenceTest.java) | 98 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/FloatArrayListTest.java (renamed from java/src/test/java/com/google/protobuf/FloatArrayListTest.java) | 20 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/ForceFieldBuildersPreRun.java (renamed from java/src/test/java/com/google/protobuf/ForceFieldBuildersPreRun.java) | 2 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/GeneratedMessageTest.java (renamed from java/src/test/java/com/google/protobuf/GeneratedMessageTest.java) | 23 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/IntArrayListTest.java (renamed from java/src/test/java/com/google/protobuf/IntArrayListTest.java) | 14 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/IsValidUtf8Test.java (renamed from java/src/test/java/com/google/protobuf/IsValidUtf8Test.java) | 73 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/IsValidUtf8TestUtil.java (renamed from java/src/test/java/com/google/protobuf/IsValidUtf8TestUtil.java) | 202 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/LazyFieldLiteTest.java (renamed from java/src/test/java/com/google/protobuf/LazyFieldLiteTest.java) | 3 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/LazyFieldTest.java (renamed from java/src/test/java/com/google/protobuf/LazyFieldTest.java) | 3 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/LazyMessageLiteTest.java (renamed from java/src/test/java/com/google/protobuf/LazyMessageLiteTest.java) | 0 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/LazyStringArrayListTest.java (renamed from java/src/test/java/com/google/protobuf/LazyStringArrayListTest.java) | 0 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/LazyStringEndToEndTest.java (renamed from java/src/test/java/com/google/protobuf/LazyStringEndToEndTest.java) | 0 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/LiteEqualsAndHashTest.java (renamed from java/src/test/java/com/google/protobuf/LiteEqualsAndHashTest.java) | 17 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/LiteTest.java (renamed from java/src/test/java/com/google/protobuf/LiteTest.java) | 881 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/LiteralByteStringTest.java (renamed from java/src/test/java/com/google/protobuf/LiteralByteStringTest.java) | 90 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/LongArrayListTest.java (renamed from java/src/test/java/com/google/protobuf/LongArrayListTest.java) | 20 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/MapForProto2LiteTest.java (renamed from java/src/test/java/com/google/protobuf/MapForProto2LiteTest.java) | 2 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/MapForProto2Test.java (renamed from java/src/test/java/com/google/protobuf/MapForProto2Test.java) | 1 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/MapTest.java (renamed from java/src/test/java/com/google/protobuf/MapTest.java) | 1 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/MessageTest.java (renamed from java/src/test/java/com/google/protobuf/MessageTest.java) | 4 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/NestedBuildersTest.java (renamed from java/src/test/java/com/google/protobuf/NestedBuildersTest.java) | 2 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/NioByteStringTest.java | 620 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/ParseExceptionsTest.java | 273 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/ParserTest.java (renamed from java/src/test/java/com/google/protobuf/ParserTest.java) | 20 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/ProtobufArrayListTest.java (renamed from java/src/test/java/com/google/protobuf/ProtobufArrayListTest.java) | 18 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/RepeatedFieldBuilderTest.java (renamed from java/src/test/java/com/google/protobuf/RepeatedFieldBuilderTest.java) | 0 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/RopeByteStringSubstringTest.java (renamed from java/src/test/java/com/google/protobuf/RopeByteStringSubstringTest.java) | 2 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/RopeByteStringTest.java (renamed from java/src/test/java/com/google/protobuf/RopeByteStringTest.java) | 0 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/ServiceTest.java (renamed from java/src/test/java/com/google/protobuf/ServiceTest.java) | 34 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/SingleFieldBuilderTest.java (renamed from java/src/test/java/com/google/protobuf/SingleFieldBuilderTest.java) | 0 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/SmallSortedMapTest.java (renamed from java/src/test/java/com/google/protobuf/SmallSortedMapTest.java) | 3 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/TestBadIdentifiers.java (renamed from java/src/test/java/com/google/protobuf/TestBadIdentifiers.java) | 0 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/TestUtil.java (renamed from java/src/test/java/com/google/protobuf/TestUtil.java) | 596 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/TestUtilLite.java | 559 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/TextFormatParseInfoTreeTest.java | 182 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/TextFormatParseLocationTest.java | 86 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/TextFormatTest.java (renamed from java/src/test/java/com/google/protobuf/TextFormatTest.java) | 112 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/UnknownEnumValueTest.java (renamed from java/src/test/java/com/google/protobuf/UnknownEnumValueTest.java) | 0 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/UnknownFieldSetLiteTest.java (renamed from java/src/test/java/com/google/protobuf/UnknownFieldSetLiteTest.java) | 40 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/UnknownFieldSetTest.java (renamed from java/src/test/java/com/google/protobuf/UnknownFieldSetTest.java) | 1 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/UnmodifiableLazyStringListTest.java (renamed from java/src/test/java/com/google/protobuf/UnmodifiableLazyStringListTest.java) | 0 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/WellKnownTypesTest.java (renamed from java/src/test/java/com/google/protobuf/WellKnownTypesTest.java) | 0 | ||||
-rw-r--r-- | java/core/src/test/java/com/google/protobuf/WireFormatTest.java (renamed from java/src/test/java/com/google/protobuf/WireFormatTest.java) | 32 | ||||
-rw-r--r-- | java/core/src/test/proto/com/google/protobuf/any_test.proto (renamed from java/src/test/java/com/google/protobuf/any_test.proto) | 0 | ||||
-rw-r--r-- | java/core/src/test/proto/com/google/protobuf/field_presence_test.proto (renamed from java/src/test/java/com/google/protobuf/field_presence_test.proto) | 0 | ||||
-rw-r--r-- | java/core/src/test/proto/com/google/protobuf/lazy_fields_lite.proto (renamed from java/src/test/java/com/google/protobuf/lazy_fields_lite.proto) | 0 | ||||
-rw-r--r-- | java/core/src/test/proto/com/google/protobuf/lite_equals_and_hash.proto (renamed from java/src/test/java/com/google/protobuf/lite_equals_and_hash.proto) | 15 | ||||
-rw-r--r-- | java/core/src/test/proto/com/google/protobuf/map_for_proto2_lite_test.proto (renamed from java/src/test/java/com/google/protobuf/map_for_proto2_lite_test.proto) | 0 | ||||
-rw-r--r-- | java/core/src/test/proto/com/google/protobuf/map_for_proto2_test.proto (renamed from java/src/test/java/com/google/protobuf/map_for_proto2_test.proto) | 0 | ||||
-rw-r--r-- | java/core/src/test/proto/com/google/protobuf/map_initialization_order_test.proto (renamed from java/src/test/java/com/google/protobuf/map_initialization_order_test.proto) | 0 | ||||
-rw-r--r-- | java/core/src/test/proto/com/google/protobuf/map_test.proto (renamed from java/src/test/java/com/google/protobuf/map_test.proto) | 1 | ||||
-rw-r--r-- | java/core/src/test/proto/com/google/protobuf/multiple_files_test.proto (renamed from java/src/test/java/com/google/protobuf/multiple_files_test.proto) | 0 | ||||
-rw-r--r-- | java/core/src/test/proto/com/google/protobuf/nested_builders_test.proto (renamed from java/src/test/java/com/google/protobuf/nested_builders_test.proto) | 0 | ||||
-rw-r--r-- | java/core/src/test/proto/com/google/protobuf/nested_extension.proto (renamed from java/src/test/java/com/google/protobuf/nested_extension.proto) | 0 | ||||
-rw-r--r-- | java/core/src/test/proto/com/google/protobuf/nested_extension_lite.proto (renamed from java/src/test/java/com/google/protobuf/nested_extension_lite.proto) | 0 | ||||
-rw-r--r-- | java/core/src/test/proto/com/google/protobuf/non_nested_extension.proto (renamed from java/src/test/java/com/google/protobuf/non_nested_extension.proto) | 0 | ||||
-rw-r--r-- | java/core/src/test/proto/com/google/protobuf/non_nested_extension_lite.proto (renamed from java/src/test/java/com/google/protobuf/non_nested_extension_lite.proto) | 0 | ||||
-rw-r--r-- | java/core/src/test/proto/com/google/protobuf/outer_class_name_test.proto (renamed from java/src/test/java/com/google/protobuf/outer_class_name_test.proto) | 0 | ||||
-rw-r--r-- | java/core/src/test/proto/com/google/protobuf/outer_class_name_test2.proto (renamed from java/src/test/java/com/google/protobuf/outer_class_name_test2.proto) | 0 | ||||
-rw-r--r-- | java/core/src/test/proto/com/google/protobuf/outer_class_name_test3.proto (renamed from java/src/test/java/com/google/protobuf/outer_class_name_test3.proto) | 0 | ||||
-rw-r--r-- | java/core/src/test/proto/com/google/protobuf/test_bad_identifiers.proto (renamed from java/src/test/java/com/google/protobuf/test_bad_identifiers.proto) | 1 | ||||
-rw-r--r-- | java/core/src/test/proto/com/google/protobuf/test_check_utf8.proto (renamed from java/src/test/java/com/google/protobuf/test_check_utf8.proto) | 0 | ||||
-rw-r--r-- | java/core/src/test/proto/com/google/protobuf/test_check_utf8_size.proto (renamed from java/src/test/java/com/google/protobuf/test_check_utf8_size.proto) | 0 | ||||
-rw-r--r-- | java/core/src/test/proto/com/google/protobuf/test_custom_options.proto (renamed from java/src/test/java/com/google/protobuf/test_custom_options.proto) | 0 | ||||
-rw-r--r-- | java/core/src/test/proto/com/google/protobuf/test_extra_interfaces.proto (renamed from java/src/test/java/com/google/protobuf/test_extra_interfaces.proto) | 0 | ||||
-rw-r--r-- | java/lite/generate-sources-build.xml | 20 | ||||
-rw-r--r-- | java/lite/generate-test-sources-build.xml | 43 | ||||
-rw-r--r-- | java/lite/pom.xml | 156 | ||||
-rw-r--r-- | java/pom.xml | 417 | ||||
-rw-r--r-- | java/src/main/java/com/google/protobuf/BoundedByteString.java | 180 | ||||
-rw-r--r-- | java/src/main/java/com/google/protobuf/CodedOutputStream.java | 1385 | ||||
-rw-r--r-- | java/src/main/java/com/google/protobuf/LiteralByteString.java | 372 | ||||
-rw-r--r-- | java/src/main/java/com/google/protobuf/Utf8.java | 481 | ||||
-rw-r--r-- | java/src/test/java/com/google/protobuf/CodedOutputStreamTest.java | 509 | ||||
-rw-r--r-- | java/util/pom.xml | 185 | ||||
-rw-r--r-- | java/util/src/main/java/com/google/protobuf/util/FieldMaskTree.java | 81 | ||||
-rw-r--r-- | java/util/src/main/java/com/google/protobuf/util/FieldMaskUtil.java | 124 | ||||
-rw-r--r-- | java/util/src/main/java/com/google/protobuf/util/JsonFormat.java | 259 | ||||
-rw-r--r-- | java/util/src/main/java/com/google/protobuf/util/TimeUtil.java | 12 | ||||
-rw-r--r-- | java/util/src/test/java/com/google/protobuf/util/FieldMaskTreeTest.java | 144 | ||||
-rw-r--r-- | java/util/src/test/java/com/google/protobuf/util/FieldMaskUtilTest.java | 57 | ||||
-rw-r--r-- | java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java | 610 | ||||
-rw-r--r-- | java/util/src/test/java/com/google/protobuf/util/TimeUtilTest.java | 67 | ||||
-rw-r--r-- | java/util/src/test/proto/com/google/protobuf/util/json_test.proto (renamed from java/util/src/test/java/com/google/protobuf/util/json_test.proto) | 43 |
170 files changed, 15284 insertions, 5896 deletions
diff --git a/java/core/generate-sources-build.xml b/java/core/generate-sources-build.xml new file mode 100644 index 00000000..0996e5ff --- /dev/null +++ b/java/core/generate-sources-build.xml @@ -0,0 +1,20 @@ +<project name="generate-sources"> + <echo message="Running protoc ..."/> + <mkdir dir="${generated.sources.dir}"/> + <exec executable="${protoc}"> + <arg value="--java_out=${generated.sources.dir}"/> + <arg value="--proto_path=${protobuf.source.dir}"/> + <arg value="${protobuf.source.dir}/google/protobuf/any.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/api.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/descriptor.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/duration.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/empty.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/field_mask.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/source_context.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/struct.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/timestamp.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/type.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/wrappers.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/compiler/plugin.proto"/> + </exec> +</project>
\ No newline at end of file diff --git a/java/core/generate-test-sources-build.xml b/java/core/generate-test-sources-build.xml new file mode 100644 index 00000000..ab415db6 --- /dev/null +++ b/java/core/generate-test-sources-build.xml @@ -0,0 +1,43 @@ +<project name="generate-test-sources"> + <mkdir dir="${generated.testsources.dir}"/> + <exec executable="${protoc}"> + <arg value="--java_out=${generated.testsources.dir}"/> + <arg value="--proto_path=${protobuf.source.dir}"/> + <arg value="--proto_path=${test.proto.dir}"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_import.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_import_public.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_mset.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_mset_wire_format.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_optimize_for.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_custom_options.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_lite.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_import_lite.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_import_public_lite.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_lite_imports_nonlite.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_enormous_descriptor.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_no_generic_services.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_well_known_types.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/lazy_fields_lite.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/lite_equals_and_hash.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/multiple_files_test.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/nested_builders_test.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/nested_extension.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/nested_extension_lite.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/non_nested_extension.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/non_nested_extension_lite.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/outer_class_name_test.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/outer_class_name_test2.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/outer_class_name_test3.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/test_bad_identifiers.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/test_check_utf8.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/test_check_utf8_size.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/test_custom_options.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/any_test.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/field_presence_test.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/map_for_proto2_lite_test.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/map_for_proto2_test.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/map_test.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/map_initialization_order_test.proto"/> + </exec> +</project>
\ No newline at end of file diff --git a/java/core/pom.xml b/java/core/pom.xml new file mode 100644 index 00000000..0d4c5c75 --- /dev/null +++ b/java/core/pom.xml @@ -0,0 +1,118 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>com.google.protobuf</groupId> + <artifactId>protobuf-parent</artifactId> + <version>3.0.0-beta-3</version> + </parent> + + <artifactId>protobuf-java</artifactId> + <packaging>bundle</packaging> + + <name>Protocol Buffers [Core]</name> + <description> + Core Protocol Buffers library. Protocol Buffers are a way of encoding structured data in an + efficient yet extensible format. + </description> + + <dependencies> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + </dependency> + <dependency> + <groupId>org.easymock</groupId> + <artifactId>easymock</artifactId> + </dependency> + <dependency> + <groupId>org.easymock</groupId> + <artifactId>easymockclassextension</artifactId> + </dependency> + </dependencies> + + <build> + <!-- Include core protos in the bundle as resources --> + <resources> + <resource> + <directory>${protobuf.source.dir}</directory> + <includes> + <include>google/protobuf/any.proto</include> + <include>google/protobuf/api.proto</include> + <include>google/protobuf/descriptor.proto</include> + <include>google/protobuf/duration.proto</include> + <include>google/protobuf/empty.proto</include> + <include>google/protobuf/field_mask.proto</include> + <include>google/protobuf/source_context.proto</include> + <include>google/protobuf/struct.proto</include> + <include>google/protobuf/timestamp.proto</include> + <include>google/protobuf/type.proto</include> + <include>google/protobuf/wrappers.proto</include> + <include>google/protobuf/compiler/plugin.proto</include> + </includes> + </resource> + </resources> + + <plugins> + <!-- Use Antrun plugin to generate sources with protoc --> + <plugin> + <artifactId>maven-antrun-plugin</artifactId> + <executions> + <!-- Generate core protos --> + <execution> + <id>generate-sources</id> + <phase>generate-sources</phase> + <configuration> + <target> + <ant antfile="generate-sources-build.xml"/> + </target> + </configuration> + <goals> + <goal>run</goal> + </goals> + </execution> + + <!-- Generate the test protos --> + <execution> + <id>generate-test-sources</id> + <phase>generate-test-sources</phase> + <configuration> + <target> + <ant antfile="generate-test-sources-build.xml"/> + </target> + </configuration> + <goals> + <goal>run</goal> + </goals> + </execution> + </executions> + </plugin> + + <!-- Add the generated sources to the build --> + <plugin> + <artifactId>maven-compiler-plugin</artifactId> + <configuration> + <generatedSourcesDirectory>${generated.sources.dir}</generatedSourcesDirectory> + <generatedTestSourcesDirectory>${generated.testsources.dir}</generatedTestSourcesDirectory> + </configuration> + </plugin> + + <!-- OSGI bundle configuration --> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <extensions>true</extensions> + <configuration> + <instructions> + <Bundle-DocURL>https://developers.google.com/protocol-buffers/</Bundle-DocURL> + <Bundle-SymbolicName>com.google.protobuf</Bundle-SymbolicName> + <Export-Package>com.google.protobuf;version=${project.version}</Export-Package> + </instructions> + </configuration> + </plugin> + </plugins> + </build> + +</project> diff --git a/java/src/main/java/com/google/protobuf/AbstractMessage.java b/java/core/src/main/java/com/google/protobuf/AbstractMessage.java index cc89173a..03c0d579 100644 --- a/java/src/main/java/com/google/protobuf/AbstractMessage.java +++ b/java/core/src/main/java/com/google/protobuf/AbstractMessage.java @@ -30,6 +30,7 @@ package com.google.protobuf; +import com.google.protobuf.Descriptors.EnumValueDescriptor; import com.google.protobuf.Descriptors.FieldDescriptor; import com.google.protobuf.Descriptors.OneofDescriptor; import com.google.protobuf.Internal.EnumLite; @@ -49,17 +50,23 @@ import java.util.Map; * * @author kenton@google.com Kenton Varda */ -public abstract class AbstractMessage extends AbstractMessageLite - implements Message { +public abstract class AbstractMessage + // TODO(dweis): Update GeneratedMessage to parameterize with MessageType and BuilderType. + extends AbstractMessageLite + implements Message { + + @Override public boolean isInitialized() { return MessageReflection.isInitialized(this); } + @Override public List<String> findInitializationErrors() { return MessageReflection.findMissingFields(this); } + @Override public String getInitializationErrorString() { return MessageReflection.delimitWithCommas(findInitializationErrors()); } @@ -82,12 +89,14 @@ public abstract class AbstractMessage extends AbstractMessageLite return TextFormat.printToString(this); } + @Override public void writeTo(final CodedOutputStream output) throws IOException { MessageReflection.writeMessageTo(this, getAllFields(), output, false); } protected int memoizedSize = -1; + @Override public int getSerializedSize() { int size = memoizedSize; if (size != -1) { @@ -161,10 +170,18 @@ public abstract class AbstractMessage extends AbstractMessageLite Descriptors.Descriptor descriptor = entry.getDescriptorForType(); Descriptors.FieldDescriptor key = descriptor.findFieldByName("key"); Descriptors.FieldDescriptor value = descriptor.findFieldByName("value"); - result.put(entry.getField(key), entry.getField(value)); + Object fieldValue = entry.getField(value); + if (fieldValue instanceof EnumValueDescriptor) { + fieldValue = ((EnumValueDescriptor) fieldValue).getNumber(); + } + result.put(entry.getField(key), fieldValue); while (iterator.hasNext()) { entry = (Message) iterator.next(); - result.put(entry.getField(key), entry.getField(value)); + fieldValue = entry.getField(value); + if (fieldValue instanceof EnumValueDescriptor) { + fieldValue = ((EnumValueDescriptor) fieldValue).getNumber(); + } + result.put(entry.getField(key), fieldValue); } return result; } @@ -279,8 +296,8 @@ public abstract class AbstractMessage extends AbstractMessageLite * other methods. */ @SuppressWarnings("unchecked") - public static abstract class Builder<BuilderType extends Builder> - extends AbstractMessageLite.Builder<BuilderType> + public static abstract class Builder<BuilderType extends Builder<BuilderType>> + extends AbstractMessageLite.Builder implements Message.Builder { // The compiler produces an error if this is not declared explicitly. @Override @@ -305,6 +322,7 @@ public abstract class AbstractMessage extends AbstractMessageLite throw new UnsupportedOperationException("clearOneof() is not implemented."); } + @Override public BuilderType clear() { for (final Map.Entry<FieldDescriptor, Object> entry : getAllFields().entrySet()) { @@ -313,14 +331,22 @@ public abstract class AbstractMessage extends AbstractMessageLite return (BuilderType) this; } + @Override public List<String> findInitializationErrors() { return MessageReflection.findMissingFields(this); } + @Override public String getInitializationErrorString() { return MessageReflection.delimitWithCommas(findInitializationErrors()); } + + @Override + protected BuilderType internalMergeFrom(AbstractMessageLite other) { + return mergeFrom((Message) other); + } + @Override public BuilderType mergeFrom(final Message other) { if (other.getDescriptorForType() != getDescriptorForType()) { throw new IllegalArgumentException( @@ -398,6 +424,7 @@ public abstract class AbstractMessage extends AbstractMessageLite return (BuilderType) this; } + @Override public BuilderType mergeUnknownFields(final UnknownFieldSet unknownFields) { setUnknownFields( UnknownFieldSet.newBuilder(getUnknownFields()) @@ -406,17 +433,19 @@ public abstract class AbstractMessage extends AbstractMessageLite return (BuilderType) this; } + @Override public Message.Builder getFieldBuilder(final FieldDescriptor field) { throw new UnsupportedOperationException( "getFieldBuilder() called on an unsupported message type."); } - public Message.Builder getRepeatedFieldBuilder(final FieldDescriptor field, - int index) { + @Override + public Message.Builder getRepeatedFieldBuilder(final FieldDescriptor field, int index) { throw new UnsupportedOperationException( "getRepeatedFieldBuilder() called on an unsupported message type."); } + @Override public String toString() { return TextFormat.printToString(this); } @@ -453,7 +482,7 @@ public abstract class AbstractMessage extends AbstractMessageLite @Override public BuilderType mergeFrom(final ByteString data) throws InvalidProtocolBufferException { - return super.mergeFrom(data); + return (BuilderType) super.mergeFrom(data); } @Override @@ -461,20 +490,20 @@ public abstract class AbstractMessage extends AbstractMessageLite final ByteString data, final ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { - return super.mergeFrom(data, extensionRegistry); + return (BuilderType) super.mergeFrom(data, extensionRegistry); } @Override public BuilderType mergeFrom(final byte[] data) throws InvalidProtocolBufferException { - return super.mergeFrom(data); + return (BuilderType) super.mergeFrom(data); } @Override public BuilderType mergeFrom( final byte[] data, final int off, final int len) throws InvalidProtocolBufferException { - return super.mergeFrom(data, off, len); + return (BuilderType) super.mergeFrom(data, off, len); } @Override @@ -482,7 +511,7 @@ public abstract class AbstractMessage extends AbstractMessageLite final byte[] data, final ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { - return super.mergeFrom(data, extensionRegistry); + return (BuilderType) super.mergeFrom(data, extensionRegistry); } @Override @@ -490,13 +519,13 @@ public abstract class AbstractMessage extends AbstractMessageLite final byte[] data, final int off, final int len, final ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { - return super.mergeFrom(data, off, len, extensionRegistry); + return (BuilderType) super.mergeFrom(data, off, len, extensionRegistry); } @Override public BuilderType mergeFrom(final InputStream input) throws IOException { - return super.mergeFrom(input); + return (BuilderType) super.mergeFrom(input); } @Override @@ -504,7 +533,7 @@ public abstract class AbstractMessage extends AbstractMessageLite final InputStream input, final ExtensionRegistryLite extensionRegistry) throws IOException { - return super.mergeFrom(input, extensionRegistry); + return (BuilderType) super.mergeFrom(input, extensionRegistry); } @Override diff --git a/java/src/main/java/com/google/protobuf/AbstractMessageLite.java b/java/core/src/main/java/com/google/protobuf/AbstractMessageLite.java index 12384983..43736dd1 100644 --- a/java/src/main/java/com/google/protobuf/AbstractMessageLite.java +++ b/java/core/src/main/java/com/google/protobuf/AbstractMessageLite.java @@ -43,9 +43,13 @@ import java.util.Collection; * * @author kenton@google.com Kenton Varda */ -public abstract class AbstractMessageLite implements MessageLite { +public abstract class AbstractMessageLite< + MessageType extends AbstractMessageLite<MessageType, BuilderType>, + BuilderType extends AbstractMessageLite.Builder<MessageType, BuilderType>> + implements MessageLite { protected int memoizedHashCode = 0; - + + @Override public ByteString toByteString() { try { final ByteString.CodedBuilder out = @@ -59,6 +63,7 @@ public abstract class AbstractMessageLite implements MessageLite { } } + @Override public byte[] toByteArray() { try { final byte[] result = new byte[getSerializedSize()]; @@ -73,6 +78,7 @@ public abstract class AbstractMessageLite implements MessageLite { } } + @Override public void writeTo(final OutputStream output) throws IOException { final int bufferSize = CodedOutputStream.computePreferredBufferSize(getSerializedSize()); @@ -82,6 +88,7 @@ public abstract class AbstractMessageLite implements MessageLite { codedOutput.flush(); } + @Override public void writeDelimitedTo(final OutputStream output) throws IOException { final int serialized = getSerializedSize(); final int bufferSize = CodedOutputStream.computePreferredBufferSize( @@ -120,25 +127,27 @@ public abstract class AbstractMessageLite implements MessageLite { * other methods. */ @SuppressWarnings("unchecked") - public static abstract class Builder<BuilderType extends Builder> + public abstract static class Builder< + MessageType extends AbstractMessageLite<MessageType, BuilderType>, + BuilderType extends Builder<MessageType, BuilderType>> implements MessageLite.Builder { // The compiler produces an error if this is not declared explicitly. @Override public abstract BuilderType clone(); - public BuilderType mergeFrom(final CodedInputStream input) - throws IOException { + @Override + public BuilderType mergeFrom(final CodedInputStream input) throws IOException { return mergeFrom(input, ExtensionRegistryLite.getEmptyRegistry()); } // Re-defined here for return type covariance. + @Override public abstract BuilderType mergeFrom( - final CodedInputStream input, - final ExtensionRegistryLite extensionRegistry) + final CodedInputStream input, final ExtensionRegistryLite extensionRegistry) throws IOException; - public BuilderType mergeFrom(final ByteString data) - throws InvalidProtocolBufferException { + @Override + public BuilderType mergeFrom(final ByteString data) throws InvalidProtocolBufferException { try { final CodedInputStream input = data.newCodedInput(); mergeFrom(input); @@ -153,9 +162,9 @@ public abstract class AbstractMessageLite implements MessageLite { } } + @Override public BuilderType mergeFrom( - final ByteString data, - final ExtensionRegistryLite extensionRegistry) + final ByteString data, final ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { try { final CodedInputStream input = data.newCodedInput(); @@ -171,14 +180,14 @@ public abstract class AbstractMessageLite implements MessageLite { } } - public BuilderType mergeFrom(final byte[] data) - throws InvalidProtocolBufferException { + @Override + public BuilderType mergeFrom(final byte[] data) throws InvalidProtocolBufferException { return mergeFrom(data, 0, data.length); } - public BuilderType mergeFrom(final byte[] data, final int off, - final int len) - throws InvalidProtocolBufferException { + @Override + public BuilderType mergeFrom(final byte[] data, final int off, final int len) + throws InvalidProtocolBufferException { try { final CodedInputStream input = CodedInputStream.newInstance(data, off, len); @@ -194,15 +203,17 @@ public abstract class AbstractMessageLite implements MessageLite { } } - public BuilderType mergeFrom( - final byte[] data, - final ExtensionRegistryLite extensionRegistry) + @Override + public BuilderType mergeFrom(final byte[] data, final ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { return mergeFrom(data, 0, data.length, extensionRegistry); } + @Override public BuilderType mergeFrom( - final byte[] data, final int off, final int len, + final byte[] data, + final int off, + final int len, final ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { try { @@ -220,6 +231,7 @@ public abstract class AbstractMessageLite implements MessageLite { } } + @Override public BuilderType mergeFrom(final InputStream input) throws IOException { final CodedInputStream codedInput = CodedInputStream.newInstance(input); mergeFrom(codedInput); @@ -227,10 +239,9 @@ public abstract class AbstractMessageLite implements MessageLite { return (BuilderType) this; } + @Override public BuilderType mergeFrom( - final InputStream input, - final ExtensionRegistryLite extensionRegistry) - throws IOException { + final InputStream input, final ExtensionRegistryLite extensionRegistry) throws IOException { final CodedInputStream codedInput = CodedInputStream.newInstance(input); mergeFrom(codedInput, extensionRegistry); codedInput.checkLastTagWas(0); @@ -292,10 +303,9 @@ public abstract class AbstractMessageLite implements MessageLite { } } + @Override public boolean mergeDelimitedFrom( - final InputStream input, - final ExtensionRegistryLite extensionRegistry) - throws IOException { + final InputStream input, final ExtensionRegistryLite extensionRegistry) throws IOException { final int firstByte = input.read(); if (firstByte == -1) { return false; @@ -306,11 +316,24 @@ public abstract class AbstractMessageLite implements MessageLite { return true; } - public boolean mergeDelimitedFrom(final InputStream input) - throws IOException { + @Override + public boolean mergeDelimitedFrom(final InputStream input) throws IOException { return mergeDelimitedFrom(input, ExtensionRegistryLite.getEmptyRegistry()); } + + @Override + @SuppressWarnings("unchecked") // isInstance takes care of this + public BuilderType mergeFrom(final MessageLite other) { + if (!getDefaultInstanceForType().getClass().isInstance(other)) { + throw new IllegalArgumentException( + "mergeFrom(MessageLite) can only merge messages of the same type."); + } + + return internalMergeFrom((MessageType) other); + } + + protected abstract BuilderType internalMergeFrom(MessageType message); /** * Construct an UninitializedMessageException reporting missing fields in diff --git a/java/src/main/java/com/google/protobuf/AbstractParser.java b/java/core/src/main/java/com/google/protobuf/AbstractParser.java index 1a4c6311..66b0ee3b 100644 --- a/java/src/main/java/com/google/protobuf/AbstractParser.java +++ b/java/core/src/main/java/com/google/protobuf/AbstractParser.java @@ -78,26 +78,27 @@ public abstract class AbstractParser<MessageType extends MessageLite> private static final ExtensionRegistryLite EMPTY_REGISTRY = ExtensionRegistryLite.getEmptyRegistry(); + @Override public MessageType parsePartialFrom(CodedInputStream input) throws InvalidProtocolBufferException { return parsePartialFrom(input, EMPTY_REGISTRY); } - public MessageType parseFrom(CodedInputStream input, - ExtensionRegistryLite extensionRegistry) + @Override + public MessageType parseFrom(CodedInputStream input, ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { return checkMessageInitialized( parsePartialFrom(input, extensionRegistry)); } - public MessageType parseFrom(CodedInputStream input) - throws InvalidProtocolBufferException { + @Override + public MessageType parseFrom(CodedInputStream input) throws InvalidProtocolBufferException { return parseFrom(input, EMPTY_REGISTRY); } - public MessageType parsePartialFrom(ByteString data, - ExtensionRegistryLite extensionRegistry) - throws InvalidProtocolBufferException { + @Override + public MessageType parsePartialFrom(ByteString data, ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { MessageType message; try { CodedInputStream input = data.newCodedInput(); @@ -113,24 +114,25 @@ public abstract class AbstractParser<MessageType extends MessageLite> } } - public MessageType parsePartialFrom(ByteString data) - throws InvalidProtocolBufferException { + @Override + public MessageType parsePartialFrom(ByteString data) throws InvalidProtocolBufferException { return parsePartialFrom(data, EMPTY_REGISTRY); } - public MessageType parseFrom(ByteString data, - ExtensionRegistryLite extensionRegistry) + @Override + public MessageType parseFrom(ByteString data, ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { return checkMessageInitialized(parsePartialFrom(data, extensionRegistry)); } - public MessageType parseFrom(ByteString data) - throws InvalidProtocolBufferException { + @Override + public MessageType parseFrom(ByteString data) throws InvalidProtocolBufferException { return parseFrom(data, EMPTY_REGISTRY); } - public MessageType parsePartialFrom(byte[] data, int off, int len, - ExtensionRegistryLite extensionRegistry) + @Override + public MessageType parsePartialFrom( + byte[] data, int off, int len, ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { try { CodedInputStream input = CodedInputStream.newInstance(data, off, len); @@ -146,47 +148,50 @@ public abstract class AbstractParser<MessageType extends MessageLite> } } + @Override public MessageType parsePartialFrom(byte[] data, int off, int len) throws InvalidProtocolBufferException { return parsePartialFrom(data, off, len, EMPTY_REGISTRY); } - public MessageType parsePartialFrom(byte[] data, - ExtensionRegistryLite extensionRegistry) + @Override + public MessageType parsePartialFrom(byte[] data, ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { return parsePartialFrom(data, 0, data.length, extensionRegistry); } - public MessageType parsePartialFrom(byte[] data) - throws InvalidProtocolBufferException { + @Override + public MessageType parsePartialFrom(byte[] data) throws InvalidProtocolBufferException { return parsePartialFrom(data, 0, data.length, EMPTY_REGISTRY); } - public MessageType parseFrom(byte[] data, int off, int len, - ExtensionRegistryLite extensionRegistry) + @Override + public MessageType parseFrom( + byte[] data, int off, int len, ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { return checkMessageInitialized( parsePartialFrom(data, off, len, extensionRegistry)); } + @Override public MessageType parseFrom(byte[] data, int off, int len) throws InvalidProtocolBufferException { return parseFrom(data, off, len, EMPTY_REGISTRY); } - public MessageType parseFrom(byte[] data, - ExtensionRegistryLite extensionRegistry) + @Override + public MessageType parseFrom(byte[] data, ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { return parseFrom(data, 0, data.length, extensionRegistry); } - public MessageType parseFrom(byte[] data) - throws InvalidProtocolBufferException { + @Override + public MessageType parseFrom(byte[] data) throws InvalidProtocolBufferException { return parseFrom(data, EMPTY_REGISTRY); } - public MessageType parsePartialFrom(InputStream input, - ExtensionRegistryLite extensionRegistry) + @Override + public MessageType parsePartialFrom(InputStream input, ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { CodedInputStream codedInput = CodedInputStream.newInstance(input); MessageType message = parsePartialFrom(codedInput, extensionRegistry); @@ -198,26 +203,26 @@ public abstract class AbstractParser<MessageType extends MessageLite> return message; } - public MessageType parsePartialFrom(InputStream input) - throws InvalidProtocolBufferException { + @Override + public MessageType parsePartialFrom(InputStream input) throws InvalidProtocolBufferException { return parsePartialFrom(input, EMPTY_REGISTRY); } - public MessageType parseFrom(InputStream input, - ExtensionRegistryLite extensionRegistry) + @Override + public MessageType parseFrom(InputStream input, ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { return checkMessageInitialized( parsePartialFrom(input, extensionRegistry)); } - public MessageType parseFrom(InputStream input) - throws InvalidProtocolBufferException { + @Override + public MessageType parseFrom(InputStream input) throws InvalidProtocolBufferException { return parseFrom(input, EMPTY_REGISTRY); } + @Override public MessageType parsePartialDelimitedFrom( - InputStream input, - ExtensionRegistryLite extensionRegistry) + InputStream input, ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { int size; try { @@ -233,21 +238,21 @@ public abstract class AbstractParser<MessageType extends MessageLite> return parsePartialFrom(limitedInput, extensionRegistry); } + @Override public MessageType parsePartialDelimitedFrom(InputStream input) throws InvalidProtocolBufferException { return parsePartialDelimitedFrom(input, EMPTY_REGISTRY); } - public MessageType parseDelimitedFrom( - InputStream input, - ExtensionRegistryLite extensionRegistry) + @Override + public MessageType parseDelimitedFrom(InputStream input, ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { return checkMessageInitialized( parsePartialDelimitedFrom(input, extensionRegistry)); } - public MessageType parseDelimitedFrom(InputStream input) - throws InvalidProtocolBufferException { + @Override + public MessageType parseDelimitedFrom(InputStream input) throws InvalidProtocolBufferException { return parseDelimitedFrom(input, EMPTY_REGISTRY); } } diff --git a/java/src/main/java/com/google/protobuf/AbstractProtobufList.java b/java/core/src/main/java/com/google/protobuf/AbstractProtobufList.java index bb6446b2..b17db6e0 100644 --- a/java/src/main/java/com/google/protobuf/AbstractProtobufList.java +++ b/java/core/src/main/java/com/google/protobuf/AbstractProtobufList.java @@ -34,19 +34,25 @@ import com.google.protobuf.Internal.ProtobufList; import java.util.AbstractList; import java.util.Collection; +import java.util.List; +import java.util.RandomAccess; /** * An abstract implementation of {@link ProtobufList} which manages mutability semantics. All mutate - * methods are check if the list is mutable before proceeding. Subclasses must invoke + * methods must check if the list is mutable before proceeding. Subclasses must invoke * {@link #ensureIsMutable()} manually when overriding those methods. + * <p> + * This implementation assumes all subclasses are array based, supporting random access. */ abstract class AbstractProtobufList<E> extends AbstractList<E> implements ProtobufList<E> { + protected static final int DEFAULT_CAPACITY = 10; + /** * Whether or not this list is modifiable. */ private boolean isMutable; - + /** * Constructs a mutable list by default. */ @@ -55,6 +61,44 @@ abstract class AbstractProtobufList<E> extends AbstractList<E> implements Protob } @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof List)) { + return false; + } + // Handle lists that do not support RandomAccess as efficiently as possible by using an iterator + // based approach in our super class. Otherwise our index based approach will avoid those + // allocations. + if (!(o instanceof RandomAccess)) { + return super.equals(o); + } + + List<?> other = (List<?>) o; + final int size = size(); + if (size != other.size()) { + return false; + } + for (int i = 0; i < size; i++) { + if (!get(i).equals(other.get(i))) { + return false; + } + } + return true; + } + + @Override + public int hashCode() { + final int size = size(); + int hashCode = 1; + for (int i = 0; i < size; i++) { + hashCode = (31 * hashCode) + get(i).hashCode(); + } + return hashCode; + } + + @Override public boolean add(E e) { ensureIsMutable(); return super.add(e); diff --git a/java/src/main/java/com/google/protobuf/BlockingRpcChannel.java b/java/core/src/main/java/com/google/protobuf/BlockingRpcChannel.java index d535efb9..d535efb9 100644 --- a/java/src/main/java/com/google/protobuf/BlockingRpcChannel.java +++ b/java/core/src/main/java/com/google/protobuf/BlockingRpcChannel.java diff --git a/java/src/main/java/com/google/protobuf/BlockingService.java b/java/core/src/main/java/com/google/protobuf/BlockingService.java index d01f0b8f..d01f0b8f 100644 --- a/java/src/main/java/com/google/protobuf/BlockingService.java +++ b/java/core/src/main/java/com/google/protobuf/BlockingService.java diff --git a/java/src/main/java/com/google/protobuf/BooleanArrayList.java b/java/core/src/main/java/com/google/protobuf/BooleanArrayList.java index 45492d2f..8b2820b6 100644 --- a/java/src/main/java/com/google/protobuf/BooleanArrayList.java +++ b/java/core/src/main/java/com/google/protobuf/BooleanArrayList.java @@ -34,7 +34,6 @@ import com.google.protobuf.Internal.BooleanList; import java.util.Arrays; import java.util.Collection; -import java.util.List; import java.util.RandomAccess; /** @@ -45,8 +44,6 @@ import java.util.RandomAccess; final class BooleanArrayList extends AbstractProtobufList<Boolean> implements BooleanList, RandomAccess { - private static final int DEFAULT_CAPACITY = 10; - private static final BooleanArrayList EMPTY_LIST = new BooleanArrayList(); static { EMPTY_LIST.makeImmutable(); @@ -60,7 +57,7 @@ final class BooleanArrayList * The backing store for the list. */ private boolean[] array; - + /** * The size of the list distinct from the length of the array. That is, it is the number of * elements set in the list. @@ -68,31 +65,60 @@ final class BooleanArrayList private int size; /** - * Constructs a new mutable {@code BooleanArrayList}. + * Constructs a new mutable {@code BooleanArrayList} with default capacity. */ BooleanArrayList() { - array = new boolean[DEFAULT_CAPACITY]; - size = 0; + this(new boolean[DEFAULT_CAPACITY], 0); } /** - * Constructs a new mutable {@code BooleanArrayList} containing the same elements as - * {@code other}. + * Constructs a new mutable {@code BooleanArrayList}. */ - BooleanArrayList(List<Boolean> other) { - if (other instanceof BooleanArrayList) { - BooleanArrayList list = (BooleanArrayList) other; - array = list.array.clone(); - size = list.size; - } else { - size = other.size(); - array = new boolean[size]; - for (int i = 0; i < size; i++) { - array[i] = other.get(i); + private BooleanArrayList(boolean[] array, int size) { + this.array = array; + this.size = size; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof BooleanArrayList)) { + return super.equals(o); + } + BooleanArrayList other = (BooleanArrayList) o; + if (size != other.size) { + return false; + } + + final boolean[] arr = other.array; + for (int i = 0; i < size; i++) { + if (array[i] != arr[i]) { + return false; } } + + return true; } - + + @Override + public int hashCode() { + int result = 1; + for (int i = 0; i < size; i++) { + result = (31 * result) + Internal.hashBoolean(array[i]); + } + return result; + } + + @Override + public BooleanList mutableCopyWithCapacity(int capacity) { + if (capacity < size) { + throw new IllegalArgumentException(); + } + return new BooleanArrayList(Arrays.copyOf(array, capacity), size); + } + @Override public Boolean get(int index) { return getBoolean(index); diff --git a/java/core/src/main/java/com/google/protobuf/ByteBufferWriter.java b/java/core/src/main/java/com/google/protobuf/ByteBufferWriter.java new file mode 100644 index 00000000..0cc38175 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/ByteBufferWriter.java @@ -0,0 +1,145 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// 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 static java.lang.Math.max; +import static java.lang.Math.min; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.ref.SoftReference; +import java.nio.ByteBuffer; + +/** + * Utility class to provide efficient writing of {@link ByteBuffer}s to {@link OutputStream}s. + */ +final class ByteBufferWriter { + private ByteBufferWriter() {} + + /** + * Minimum size for a cached buffer. This prevents us from allocating buffers that are too + * small to be easily reused. + */ + // TODO(nathanmittler): tune this property or allow configuration? + private static final int MIN_CACHED_BUFFER_SIZE = 1024; + + /** + * Maximum size for a cached buffer. If a larger buffer is required, it will be allocated + * but not cached. + */ + // TODO(nathanmittler): tune this property or allow configuration? + private static final int MAX_CACHED_BUFFER_SIZE = 16 * 1024; + + /** + * The fraction of the requested buffer size under which the buffer will be reallocated. + */ + // TODO(nathanmittler): tune this property or allow configuration? + private static final float BUFFER_REALLOCATION_THRESHOLD = 0.5f; + + /** + * Keeping a soft reference to a thread-local buffer. This buffer is used for writing a + * {@link ByteBuffer} to an {@link OutputStream} when no zero-copy alternative was available. + * Using a "soft" reference since VMs may keep this reference around longer than "weak" + * (e.g. HotSpot will maintain soft references until memory pressure warrants collection). + */ + private static final ThreadLocal<SoftReference<byte[]>> BUFFER = + new ThreadLocal<SoftReference<byte[]>>(); + + /** + * For testing purposes only. Clears the cached buffer to force a new allocation on the next + * invocation. + */ + static void clearCachedBuffer() { + BUFFER.set(null); + } + + /** + * Writes the remaining content of the buffer to the given stream. The buffer {@code position} + * will remain unchanged by this method. + */ + static void write(ByteBuffer buffer, OutputStream output) throws IOException { + final int initialPos = buffer.position(); + try { + if (buffer.hasArray()) { + // Optimized write for array-backed buffers. + // Note that we're taking the risk that a malicious OutputStream could modify the array. + output.write(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining()); + } else if (output instanceof FileOutputStream) { + // Use a channel to write out the ByteBuffer. This will automatically empty the buffer. + ((FileOutputStream) output).getChannel().write(buffer); + } else { + // Read all of the data from the buffer to an array. + // TODO(nathanmittler): Consider performance improvements for other "known" stream types. + final byte[] array = getOrCreateBuffer(buffer.remaining()); + while (buffer.hasRemaining()) { + int length = min(buffer.remaining(), array.length); + buffer.get(array, 0, length); + output.write(array, 0, length); + } + } + } finally { + // Restore the initial position. + buffer.position(initialPos); + } + } + + private static byte[] getOrCreateBuffer(int requestedSize) { + requestedSize = max(requestedSize, MIN_CACHED_BUFFER_SIZE); + + byte[] buffer = getBuffer(); + // Only allocate if we need to. + if (buffer == null || needToReallocate(requestedSize, buffer.length)) { + buffer = new byte[requestedSize]; + + // Only cache the buffer if it's not too big. + if (requestedSize <= MAX_CACHED_BUFFER_SIZE) { + setBuffer(buffer); + } + } + return buffer; + } + + private static boolean needToReallocate(int requestedSize, int bufferLength) { + // First check against just the requested length to avoid the multiply. + return bufferLength < requestedSize + && bufferLength < requestedSize * BUFFER_REALLOCATION_THRESHOLD; + } + + private static byte[] getBuffer() { + SoftReference<byte[]> sr = BUFFER.get(); + return sr == null ? null : sr.get(); + } + + private static void setBuffer(byte[] value) { + BUFFER.set(new SoftReference<byte[]>(value)); + } +} diff --git a/java/core/src/main/java/com/google/protobuf/ByteOutput.java b/java/core/src/main/java/com/google/protobuf/ByteOutput.java new file mode 100644 index 00000000..8b7b04c8 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/ByteOutput.java @@ -0,0 +1,116 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// 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.io.IOException; +import java.nio.ByteBuffer; + +/** + * An output target for raw bytes. This interface provides semantics that support two types of + * writing: + * + * <p/><b>Traditional write operations:</b> + * (as defined by {@link java.io.OutputStream}) where the target method is responsible for either + * copying the data or completing the write before returning from the method call. + * + * <p/><b>Lazy write operations:</b> where the caller guarantees that it will never modify the + * provided buffer and it can therefore be considered immutable. The target method is free to + * maintain a reference to the buffer beyond the scope of the method call (e.g. until the write + * operation completes). + */ +@ExperimentalApi +public abstract class ByteOutput { + /** + * Writes a single byte. + * + * @param value the byte to be written + * @throws IOException thrown if an error occurred while writing + */ + public abstract void write(byte value) throws IOException; + + /** + * Writes a sequence of bytes. The {@link ByteOutput} must copy {@code value} if it will + * not be processed prior to the return of this method call, since {@code value} may be + * reused/altered by the caller. + * + * <p>NOTE: This method <strong>MUST NOT</strong> modify the {@code value}. Doing so is a + * programming error and will lead to data corruption which will be difficult to debug. + * + * @param value the bytes to be written + * @param offset the offset of the start of the writable range + * @param length the number of bytes to write starting from {@code offset} + * @throws IOException thrown if an error occurred while writing + */ + public abstract void write(byte[] value, int offset, int length) throws IOException; + + /** + * Writes a sequence of bytes. The {@link ByteOutput} is free to retain a reference to the value + * beyond the scope of this method call (e.g. write later) since it is considered immutable and is + * guaranteed not to change by the caller. + * + * <p>NOTE: This method <strong>MUST NOT</strong> modify the {@code value}. Doing so is a + * programming error and will lead to data corruption which will be difficult to debug. + * + * @param value the bytes to be written + * @param offset the offset of the start of the writable range + * @param length the number of bytes to write starting from {@code offset} + * @throws IOException thrown if an error occurred while writing + */ + public abstract void writeLazy(byte[] value, int offset, int length) throws IOException; + + /** + * Writes a sequence of bytes. The {@link ByteOutput} must copy {@code value} if it will + * not be processed prior to the return of this method call, since {@code value} may be + * reused/altered by the caller. + * + * <p>NOTE: This method <strong>MUST NOT</strong> modify the {@code value}. Doing so is a + * programming error and will lead to data corruption which will be difficult to debug. + * + * @param value the bytes to be written. Upon returning from this call, the {@code position} of + * this buffer will be set to the {@code limit} + * @throws IOException thrown if an error occurred while writing + */ + public abstract void write(ByteBuffer value) throws IOException; + + /** + * Writes a sequence of bytes. The {@link ByteOutput} is free to retain a reference to the value + * beyond the scope of this method call (e.g. write later) since it is considered immutable and is + * guaranteed not to change by the caller. + * + * <p>NOTE: This method <strong>MUST NOT</strong> modify the {@code value}. Doing so is a + * programming error and will lead to data corruption which will be difficult to debug. + * + * @param value the bytes to be written. Upon returning from this call, the {@code position} of + * this buffer will be set to the {@code limit} + * @throws IOException thrown if an error occurred while writing + */ + public abstract void writeLazy(ByteBuffer value) throws IOException; +} diff --git a/java/src/main/java/com/google/protobuf/ByteString.java b/java/core/src/main/java/com/google/protobuf/ByteString.java index b092bc36..62c94508 100644 --- a/java/src/main/java/com/google/protobuf/ByteString.java +++ b/java/core/src/main/java/com/google/protobuf/ByteString.java @@ -30,9 +30,12 @@ package com.google.protobuf; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; import java.io.OutputStream; import java.io.Serializable; import java.io.UnsupportedEncodingException; @@ -40,7 +43,9 @@ import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.UnsupportedCharsetException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; @@ -81,7 +86,62 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { /** * Empty {@code ByteString}. */ - public static final ByteString EMPTY = new LiteralByteString(new byte[0]); + public static final ByteString EMPTY = new LiteralByteString(Internal.EMPTY_BYTE_ARRAY); + + /** + * An interface to efficiently copy {@code byte[]}. + * + * <p>One of the noticable costs of copying a byte[] into a new array using + * {@code System.arraycopy} is nullification of a new buffer before the copy. It has been shown + * the Hotspot VM is capable to intrisicfy {@code Arrays.copyOfRange} operation to avoid this + * expensive nullification and provide substantial performance gain. Unfortunately this does not + * hold on Android runtimes and could make the copy slightly slower due to additional code in + * the {@code Arrays.copyOfRange}. Thus we provide two different implementation for array copier + * for Hotspot and Android runtimes. + */ + private interface ByteArrayCopier { + /** + * Copies the specified range of the specified array into a new array + */ + byte[] copyFrom(byte[] bytes, int offset, int size); + } + + /** Implementation of {@code ByteArrayCopier} which uses {@link System#arraycopy}. */ + private static final class SystemByteArrayCopier implements ByteArrayCopier { + @Override + public byte[] copyFrom(byte[] bytes, int offset, int size) { + byte[] copy = new byte[size]; + System.arraycopy(bytes, offset, copy, 0, size); + return copy; + } + } + + /** Implementation of {@code ByteArrayCopier} which uses {@link Arrays#copyOfRange}. */ + private static final class ArraysByteArrayCopier implements ByteArrayCopier { + @Override + public byte[] copyFrom(byte[] bytes, int offset, int size) { + return Arrays.copyOfRange(bytes, offset, offset + size); + } + } + + private static final ByteArrayCopier byteArrayCopier; + static { + boolean isAndroid = true; + try { + Class.forName("android.content.Context"); + } catch (ClassNotFoundException e) { + isAndroid = false; + } + + byteArrayCopier = isAndroid ? new SystemByteArrayCopier() : new ArraysByteArrayCopier(); + } + + /** + * Cached hash value. Intentionally accessed via a data race, which + * is safe because of the Java Memory Model's "no out-of-thin-air values" + * guarantees for ints. A value of 0 implies that the hash has not been set. + */ + private int hash = 0; // This constructor is here to prevent subclassing outside of this package, ByteString() {} @@ -94,7 +154,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { * * @param index index of byte * @return the value - * @throws ArrayIndexOutOfBoundsException {@code index < 0 or index >= size} + * @throws IndexOutOfBoundsException {@code index < 0 or index >= size} */ public abstract byte byteAt(int index); @@ -105,7 +165,38 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { * * @return the iterator */ - public abstract ByteIterator iterator(); + @Override + public final ByteIterator iterator() { + return new ByteIterator() { + private int position = 0; + private final int limit = size(); + + @Override + public boolean hasNext() { + return position < limit; + } + + @Override + public Byte next() { + // Boxing calls Byte.valueOf(byte), which does not instantiate. + return nextByte(); + } + + @Override + public byte nextByte() { + try { + return byteAt(position++); + } catch (IndexOutOfBoundsException e) { + throw new NoSuchElementException(e.getMessage()); + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } /** * This interface extends {@code Iterator<Byte>}, so that we can return an @@ -134,7 +225,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { * * @return true if this is zero bytes long */ - public boolean isEmpty() { + public final boolean isEmpty() { return size() == 0; } @@ -150,7 +241,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { * @throws IndexOutOfBoundsException if {@code beginIndex < 0} or * {@code beginIndex > size()}. */ - public ByteString substring(int beginIndex) { + public final ByteString substring(int beginIndex) { return substring(beginIndex, size()); } @@ -175,7 +266,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { * argument is a prefix of the byte sequence represented by * this string; <code>false</code> otherwise. */ - public boolean startsWith(ByteString prefix) { + public final boolean startsWith(ByteString prefix) { return size() >= prefix.size() && substring(0, prefix.size()).equals(prefix); } @@ -189,7 +280,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { * argument is a suffix of the byte sequence represented by * this string; <code>false</code> otherwise. */ - public boolean endsWith(ByteString suffix) { + public final boolean endsWith(ByteString suffix) { return size() >= suffix.size() && substring(size() - suffix.size()).equals(suffix); } @@ -206,9 +297,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { * @return new {@code ByteString} */ public static ByteString copyFrom(byte[] bytes, int offset, int size) { - byte[] copy = new byte[size]; - System.arraycopy(bytes, offset, copy, 0, size); - return new LiteralByteString(copy); + return new LiteralByteString(byteArrayCopier.copyFrom(bytes, offset, size)); } /** @@ -220,6 +309,24 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { public static ByteString copyFrom(byte[] bytes) { return copyFrom(bytes, 0, bytes.length); } + + /** + * Wraps the given bytes into a {@code ByteString}. Intended for internal only + * usage to force a classload of ByteString before LiteralByteString. + */ + static ByteString wrap(byte[] bytes) { + // TODO(dweis): Return EMPTY when bytes are empty to reduce allocations? + return new LiteralByteString(bytes); + } + + /** + * Wraps the given bytes into a {@code ByteString}. Intended for internal only + * usage to force a classload of ByteString before BoundedByteString and + * LiteralByteString. + */ + static ByteString wrap(byte[] bytes, int offset, int length) { + return new BoundedByteString(bytes, offset, length); + } /** * Copies the next {@code size} bytes from a {@code java.nio.ByteBuffer} into @@ -309,8 +416,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { */ public static ByteString readFrom(InputStream streamToDrain) throws IOException { - return readFrom( - streamToDrain, MIN_READ_FROM_CHUNK_SIZE, MAX_READ_FROM_CHUNK_SIZE); + return readFrom(streamToDrain, MIN_READ_FROM_CHUNK_SIZE, MAX_READ_FROM_CHUNK_SIZE); } /** @@ -383,10 +489,10 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { if (bytesRead == 0) { return null; - } else { - // Always make a copy since InputStream could steal a reference to buf. - return ByteString.copyFrom(buf, 0, bytesRead); } + + // Always make a copy since InputStream could steal a reference to buf. + return ByteString.copyFrom(buf, 0, bytesRead); } // ================================================================= @@ -402,12 +508,10 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { * @param other string to concatenate * @return a new {@code ByteString} instance */ - public ByteString concat(ByteString other) { - int thisSize = size(); - int otherSize = other.size(); - if ((long) thisSize + otherSize >= Integer.MAX_VALUE) { + public final ByteString concat(ByteString other) { + if (Integer.MAX_VALUE - size() < other.size()) { throw new IllegalArgumentException("ByteString would be too long: " + - thisSize + "+" + otherSize); + size() + "+" + other.size()); } return RopeByteString.concatenate(this, other); @@ -426,29 +530,29 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { * @return new {@code ByteString} */ public static ByteString copyFrom(Iterable<ByteString> byteStrings) { - Collection<ByteString> collection; + // Determine the size; + final int size; if (!(byteStrings instanceof Collection)) { - collection = new ArrayList<ByteString>(); - for (ByteString byteString : byteStrings) { - collection.add(byteString); + int tempSize = 0; + for (Iterator<ByteString> iter = byteStrings.iterator(); iter.hasNext(); + iter.next(), ++tempSize) { } + size = tempSize; } else { - collection = (Collection<ByteString>) byteStrings; + size = ((Collection<ByteString>) byteStrings).size(); } - ByteString result; - if (collection.isEmpty()) { - result = EMPTY; - } else { - result = balancedConcat(collection.iterator(), collection.size()); + + if (size == 0) { + return EMPTY; } - return result; + + return balancedConcat(byteStrings.iterator(), size); } // Internal function used by copyFrom(Iterable<ByteString>). // Create a balanced concatenation of the next "length" elements from the // iterable. - private static ByteString balancedConcat(Iterator<ByteString> iterator, - int length) { + private static ByteString balancedConcat(Iterator<ByteString> iterator, int length) { assert length >= 1; ByteString result; if (length == 1) { @@ -486,25 +590,10 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { * @throws IndexOutOfBoundsException if an offset or size is negative or too * large */ - public void copyTo(byte[] target, int sourceOffset, int targetOffset, + public final void copyTo(byte[] target, int sourceOffset, int targetOffset, int numberToCopy) { - if (sourceOffset < 0) { - throw new IndexOutOfBoundsException("Source offset < 0: " + sourceOffset); - } - if (targetOffset < 0) { - throw new IndexOutOfBoundsException("Target offset < 0: " + targetOffset); - } - if (numberToCopy < 0) { - throw new IndexOutOfBoundsException("Length < 0: " + numberToCopy); - } - if (sourceOffset + numberToCopy > size()) { - throw new IndexOutOfBoundsException( - "Source end offset < 0: " + (sourceOffset + numberToCopy)); - } - if (targetOffset + numberToCopy > target.length) { - throw new IndexOutOfBoundsException( - "Target end offset < 0: " + (targetOffset + numberToCopy)); - } + checkRange(sourceOffset, sourceOffset + numberToCopy, size()); + checkRange(targetOffset, targetOffset + numberToCopy, target.length); if (numberToCopy > 0) { copyToInternal(target, sourceOffset, targetOffset, numberToCopy); } @@ -534,8 +623,8 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { * * @return copied bytes */ - public byte[] toByteArray() { - int size = size(); + public final byte[] toByteArray() { + final int size = size(); if (size == 0) { return Internal.EMPTY_BYTE_ARRAY; } @@ -545,8 +634,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { } /** - * Writes the complete contents of this byte string to - * the specified output stream argument. + * Writes a copy of the contents of this byte string to the specified output stream argument. * * @param out the output stream to which to write the data. * @throws IOException if an I/O error occurs. @@ -560,33 +648,36 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { * @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 + * @throws IndexOutOfBoundsException if an offset or size is negative or too large */ - void writeTo(OutputStream out, int sourceOffset, int numberToWrite) + final 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)); - } + checkRange(sourceOffset, sourceOffset + numberToWrite, size()); 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; + abstract void writeToInternal(OutputStream out, int sourceOffset, int numberToWrite) + throws IOException; + + /** + * Writes this {@link ByteString} to the provided {@link ByteOutput}. Calling + * this method may result in multiple operations on the target {@link ByteOutput}. + * + * <p>This method may expose internal backing buffers of the {@link ByteString} to the {@link + * ByteOutput} in order to avoid additional copying overhead. It would be possible for a malicious + * {@link ByteOutput} to corrupt the {@link ByteString}. Use with caution! + * + * @param byteOutput the output target to receive the bytes + * @throws IOException if an I/O error occurs + * @see UnsafeByteOperations#unsafeWriteTo(ByteString, ByteOutput) + */ + abstract void writeTo(ByteOutput byteOutput) throws IOException; /** * Constructs a read-only {@code java.nio.ByteBuffer} whose content @@ -618,7 +709,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { * @return new string * @throws UnsupportedEncodingException if charset isn't recognized */ - public String toString(String charsetName) + public final String toString(String charsetName) throws UnsupportedEncodingException { try { return toString(Charset.forName(charsetName)); @@ -636,7 +727,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { * @param charset encode using this charset * @return new string */ - public String toString(Charset charset) { + public final String toString(Charset charset) { return size() == 0 ? "" : toStringInternal(charset); } @@ -657,7 +748,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { * * @return new string using UTF-8 encoding */ - public String toStringUtf8() { + public final String toStringUtf8() { return toString(Internal.UTF_8); } @@ -716,13 +807,51 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { public abstract boolean equals(Object o); /** - * Return a non-zero hashCode depending only on the sequence of bytes - * in this ByteString. + * Base class for leaf {@link ByteString}s (i.e. non-ropes). + */ + abstract static class LeafByteString extends ByteString { + @Override + protected final int getTreeDepth() { + return 0; + } + + @Override + protected final boolean isBalanced() { + return true; + } + + /** + * Check equality of the substring of given length of this object starting at + * zero with another {@code ByteString} substring starting at offset. + * + * @param other what to compare a substring in + * @param offset offset into other + * @param length number of bytes to compare + * @return true for equality of substrings, else false. + */ + abstract boolean equalsRange(ByteString other, int offset, int length); + } + + /** + * Compute the hashCode using the traditional algorithm from {@link + * ByteString}. * - * @return hashCode value for this object + * @return hashCode value */ @Override - public abstract int hashCode(); + public final int hashCode() { + int h = hash; + + if (h == 0) { + int size = size(); + h = partialHash(size, 0, size); + if (h == 0) { + h = 1; + } + hash = h; + } + return h; + } // ================================================================= // Input stream @@ -1034,7 +1163,9 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { * * @return value of cached hash code or 0 if not computed yet */ - protected abstract int peekCachedHashCode(); + protected final int peekCachedHashCode() { + return hash; + } /** * Compute the hash across the value bytes starting with the given hash, and @@ -1049,9 +1180,365 @@ public abstract class ByteString implements Iterable<Byte>, Serializable { */ protected abstract int partialHash(int h, int offset, int length); + /** + * Checks that the given index falls within the specified array size. + * + * @param index the index position to be tested + * @param size the length of the array + * @throws IndexOutOfBoundsException if the index does not fall within the array. + */ + static void checkIndex(int index, int size) { + if ((index | (size - (index + 1))) < 0) { + if (index < 0) { + throw new ArrayIndexOutOfBoundsException("Index < 0: " + index); + } + throw new ArrayIndexOutOfBoundsException("Index > length: " + index + ", " + size); + } + } + + /** + * Checks that the given range falls within the bounds of an array + * + * @param startIndex the start index of the range (inclusive) + * @param endIndex the end index of the range (exclusive) + * @param size the size of the array. + * @return the length of the range. + * @throws IndexOutOfBoundsException some or all of the range falls outside of the array. + */ + static int checkRange(int startIndex, int endIndex, int size) { + final int length = endIndex - startIndex; + if ((startIndex | endIndex | length | (size - endIndex)) < 0) { + if (startIndex < 0) { + throw new IndexOutOfBoundsException("Beginning index: " + startIndex + " < 0"); + } + if (endIndex < startIndex) { + throw new IndexOutOfBoundsException( + "Beginning index larger than ending index: " + startIndex + ", " + endIndex); + } + // endIndex >= size + throw new IndexOutOfBoundsException("End index: " + endIndex + " >= " + size); + } + return length; + } + @Override - public String toString() { + public final String toString() { return String.format("<ByteString@%s size=%d>", Integer.toHexString(System.identityHashCode(this)), size()); } + + /** + * This class implements a {@link com.google.protobuf.ByteString} backed by a + * single array of bytes, contiguous in memory. It supports substring by + * pointing to only a sub-range of the underlying byte array, meaning that a + * substring will reference the full byte-array of the string it's made from, + * exactly as with {@link String}. + * + * @author carlanton@google.com (Carl Haverl) + */ + // Keep this class private to avoid deadlocks in classloading across threads as ByteString's + // static initializer loads LiteralByteString and another thread loads LiteralByteString. + private static class LiteralByteString extends ByteString.LeafByteString { + private static final long serialVersionUID = 1L; + + protected final byte[] bytes; + + /** + * Creates a {@code LiteralByteString} backed by the given array, without + * copying. + * + * @param bytes array to wrap + */ + LiteralByteString(byte[] bytes) { + this.bytes = bytes; + } + + @Override + public byte byteAt(int index) { + // Unlike most methods in this class, this one is a direct implementation + // ignoring the potential offset because we need to do range-checking in the + // substring case anyway. + return bytes[index]; + } + + @Override + public int size() { + return bytes.length; + } + + // ================================================================= + // ByteString -> substring + + @Override + public final ByteString substring(int beginIndex, int endIndex) { + final int length = checkRange(beginIndex, endIndex, size()); + + if (length == 0) { + return ByteString.EMPTY; + } + + return new BoundedByteString(bytes, getOffsetIntoBytes() + beginIndex, length); + } + + // ================================================================= + // ByteString -> byte[] + + @Override + protected void copyToInternal( + byte[] target, int sourceOffset, int targetOffset, int numberToCopy) { + // Optimized form, not for subclasses, since we don't call + // getOffsetIntoBytes() or check the 'numberToCopy' parameter. + // TODO(nathanmittler): Is not calling getOffsetIntoBytes really saving that much? + System.arraycopy(bytes, sourceOffset, target, targetOffset, numberToCopy); + } + + @Override + public final void copyTo(ByteBuffer target) { + target.put(bytes, getOffsetIntoBytes(), size()); // Copies bytes + } + + @Override + public final ByteBuffer asReadOnlyByteBuffer() { + return ByteBuffer.wrap(bytes, getOffsetIntoBytes(), size()).asReadOnlyBuffer(); + } + + @Override + public final List<ByteBuffer> asReadOnlyByteBufferList() { + return Collections.singletonList(asReadOnlyByteBuffer()); + } + + @Override + public final void writeTo(OutputStream outputStream) throws IOException { + outputStream.write(toByteArray()); + } + + @Override + final void writeToInternal(OutputStream outputStream, int sourceOffset, int numberToWrite) + throws IOException { + outputStream.write(bytes, getOffsetIntoBytes() + sourceOffset, numberToWrite); + } + + @Override + final void writeTo(ByteOutput output) throws IOException { + output.writeLazy(bytes, getOffsetIntoBytes(), size()); + } + + @Override + protected final String toStringInternal(Charset charset) { + return new String(bytes, getOffsetIntoBytes(), size(), charset); + } + + // ================================================================= + // UTF-8 decoding + + @Override + public final boolean isValidUtf8() { + int offset = getOffsetIntoBytes(); + return Utf8.isValidUtf8(bytes, offset, offset + size()); + } + + @Override + protected final int partialIsValidUtf8(int state, int offset, int length) { + int index = getOffsetIntoBytes() + offset; + return Utf8.partialIsValidUtf8(state, bytes, index, index + length); + } + + // ================================================================= + // equals() and hashCode() + + @Override + public final boolean equals(Object other) { + if (other == this) { + return true; + } + if (!(other instanceof ByteString)) { + return false; + } + + if (size() != ((ByteString) other).size()) { + return false; + } + if (size() == 0) { + return true; + } + + if (other instanceof LiteralByteString) { + LiteralByteString otherAsLiteral = (LiteralByteString) other; + // If we know the hash codes and they are not equal, we know the byte + // strings are not equal. + int thisHash = peekCachedHashCode(); + int thatHash = otherAsLiteral.peekCachedHashCode(); + if (thisHash != 0 && thatHash != 0 && thisHash != thatHash) { + return false; + } + + return equalsRange((LiteralByteString) other, 0, size()); + } else { + // RopeByteString and NioByteString. + return other.equals(this); + } + } + + /** + * Check equality of the substring of given length of this object starting at + * zero with another {@code LiteralByteString} substring starting at offset. + * + * @param other what to compare a substring in + * @param offset offset into other + * @param length number of bytes to compare + * @return true for equality of substrings, else false. + */ + @Override + final boolean equalsRange(ByteString other, int offset, int length) { + if (length > other.size()) { + throw new IllegalArgumentException("Length too large: " + length + size()); + } + if (offset + length > other.size()) { + throw new IllegalArgumentException( + "Ran off end of other: " + offset + ", " + length + ", " + other.size()); + } + + if (other instanceof LiteralByteString) { + LiteralByteString lbsOther = (LiteralByteString) other; + byte[] thisBytes = bytes; + byte[] otherBytes = lbsOther.bytes; + int thisLimit = getOffsetIntoBytes() + length; + for ( + int thisIndex = getOffsetIntoBytes(), + otherIndex = lbsOther.getOffsetIntoBytes() + offset; + (thisIndex < thisLimit); ++thisIndex, ++otherIndex) { + if (thisBytes[thisIndex] != otherBytes[otherIndex]) { + return false; + } + } + return true; + } + + return other.substring(offset, offset + length).equals(substring(0, length)); + } + + @Override + protected final int partialHash(int h, int offset, int length) { + return Internal.partialHash(h, bytes, getOffsetIntoBytes() + offset, length); + } + + // ================================================================= + // Input stream + + @Override + public final InputStream newInput() { + return new ByteArrayInputStream(bytes, getOffsetIntoBytes(), size()); // No copy + } + + @Override + public final CodedInputStream newCodedInput() { + // We trust CodedInputStream not to modify the bytes, or to give anyone + // else access to them. + return CodedInputStream.newInstance( + bytes, getOffsetIntoBytes(), size(), true /* bufferIsImmutable */); + } + + // ================================================================= + // Internal methods + + /** + * Offset into {@code bytes[]} to use, non-zero for substrings. + * + * @return always 0 for this class + */ + protected int getOffsetIntoBytes() { + return 0; + } + } + + /** + * This class is used to represent the substring of a {@link ByteString} over a + * single byte array. In terms of the public API of {@link ByteString}, you end + * up here by calling {@link ByteString#copyFrom(byte[])} followed by {@link + * ByteString#substring(int, int)}. + * + * <p>This class contains most of the overhead involved in creating a substring + * from a {@link LiteralByteString}. The overhead involves some range-checking + * and two extra fields. + * + * @author carlanton@google.com (Carl Haverl) + */ + // Keep this class private to avoid deadlocks in classloading across threads as ByteString's + // static initializer loads LiteralByteString and another thread loads BoundedByteString. + private static final class BoundedByteString extends LiteralByteString { + + private final int bytesOffset; + private final int bytesLength; + + /** + * Creates a {@code BoundedByteString} backed by the sub-range of given array, + * without copying. + * + * @param bytes array to wrap + * @param offset index to first byte to use in bytes + * @param length number of bytes to use from bytes + * @throws IllegalArgumentException if {@code offset < 0}, {@code length < 0}, + * or if {@code offset + length > + * bytes.length}. + */ + BoundedByteString(byte[] bytes, int offset, int length) { + super(bytes); + checkRange(offset, offset + length, bytes.length); + + this.bytesOffset = offset; + this.bytesLength = length; + } + + /** + * Gets the byte at the given index. + * Throws {@link ArrayIndexOutOfBoundsException} + * for backwards-compatibility reasons although it would more properly be + * {@link IndexOutOfBoundsException}. + * + * @param index index of byte + * @return the value + * @throws ArrayIndexOutOfBoundsException {@code index} is < 0 or >= size + */ + @Override + public byte byteAt(int index) { + // We must check the index ourselves as we cannot rely on Java array index + // checking for substrings. + checkIndex(index, size()); + return bytes[bytesOffset + index]; + } + + @Override + public int size() { + return bytesLength; + } + + @Override + protected int getOffsetIntoBytes() { + return bytesOffset; + } + + // ================================================================= + // ByteString -> byte[] + + @Override + protected void copyToInternal(byte[] target, int sourceOffset, int targetOffset, + int numberToCopy) { + System.arraycopy(bytes, getOffsetIntoBytes() + sourceOffset, target, + targetOffset, numberToCopy); + } + + // ================================================================= + // Serializable + + private static final long serialVersionUID = 1L; + + Object writeReplace() { + return ByteString.wrap(toByteArray()); + } + + private void readObject(@SuppressWarnings("unused") ObjectInputStream in) throws IOException { + throw new InvalidObjectException( + "BoundedByteStream instances are not to be serialized directly"); + } + } } diff --git a/java/src/main/java/com/google/protobuf/CodedInputStream.java b/java/core/src/main/java/com/google/protobuf/CodedInputStream.java index d201f7c5..e8860651 100644 --- a/java/src/main/java/com/google/protobuf/CodedInputStream.java +++ b/java/core/src/main/java/com/google/protobuf/CodedInputStream.java @@ -55,7 +55,14 @@ public final class CodedInputStream { * Create a new CodedInputStream wrapping the given InputStream. */ public static CodedInputStream newInstance(final InputStream input) { - return new CodedInputStream(input); + return new CodedInputStream(input, BUFFER_SIZE); + } + + /** + * Create a new CodedInputStream wrapping the given InputStream. + */ + static CodedInputStream newInstance(final InputStream input, int bufferSize) { + return new CodedInputStream(input, bufferSize); } /** @@ -70,7 +77,15 @@ public final class CodedInputStream { */ public static CodedInputStream newInstance(final byte[] buf, final int off, final int len) { - CodedInputStream result = new CodedInputStream(buf, off, len); + return newInstance(buf, off, len, false /* bufferIsImmutable */); + } + + /** + * Create a new CodedInputStream wrapping the given byte array slice. + */ + static CodedInputStream newInstance( + final byte[] buf, final int off, final int len, final boolean bufferIsImmutable) { + CodedInputStream result = new CodedInputStream(buf, off, len, bufferIsImmutable); 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 @@ -113,31 +128,6 @@ public final class CodedInputStream { } } - /** - * 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; - } - // ----------------------------------------------------------------- /** @@ -378,6 +368,11 @@ public final class CodedInputStream { return result; } else if (size == 0) { return ""; + } else if (size <= bufferSize) { + refillBuffer(size); + String result = new String(buffer, bufferPos, size, Internal.UTF_8); + bufferPos += size; + return result; } else { // Slow path: Build a byte array first then copy it. return new String(readRawBytesSlowPath(size), Internal.UTF_8); @@ -392,14 +387,21 @@ public final class CodedInputStream { public String readStringRequireUtf8() throws IOException { final int size = readRawVarint32(); final byte[] bytes; - int pos = bufferPos; - if (size <= (bufferSize - pos) && size > 0) { + final int oldPos = bufferPos; + final int pos; + if (size <= (bufferSize - oldPos) && size > 0) { // Fast path: We already have the bytes in a contiguous buffer, so // just copy directly from it. bytes = buffer; - bufferPos = pos + size; + bufferPos = oldPos + size; + pos = oldPos; } else if (size == 0) { return ""; + } else if (size <= bufferSize) { + refillBuffer(size); + bytes = buffer; + pos = 0; + bufferPos = pos + size; } else { // Slow path: Build a byte array first then copy it. bytes = readRawBytesSlowPath(size); @@ -506,7 +508,7 @@ public final class CodedInputStream { // Fast path: We already have the bytes in a contiguous buffer, so // just copy directly from it. final ByteString result = bufferIsImmutable && enableAliasing - ? new BoundedByteString(buffer, bufferPos, size) + ? ByteString.wrap(buffer, bufferPos, size) : ByteString.copyFrom(buffer, bufferPos, size); bufferPos += size; return result; @@ -514,7 +516,7 @@ public final class CodedInputStream { return ByteString.EMPTY; } else { // Slow path: Build a byte array first then copy it. - return new LiteralByteString(readRawBytesSlowPath(size)); + return ByteString.wrap(readRawBytesSlowPath(size)); } } @@ -886,17 +888,18 @@ public final class CodedInputStream { private static final int DEFAULT_SIZE_LIMIT = 64 << 20; // 64MB private static final int BUFFER_SIZE = 4096; - private CodedInputStream(final byte[] buffer, final int off, final int len) { + private CodedInputStream( + final byte[] buffer, final int off, final int len, boolean bufferIsImmutable) { this.buffer = buffer; bufferSize = off + len; bufferPos = off; totalBytesRetired = -off; input = null; - bufferIsImmutable = false; + this.bufferIsImmutable = bufferIsImmutable; } - private CodedInputStream(final InputStream input) { - buffer = new byte[BUFFER_SIZE]; + private CodedInputStream(final InputStream input, int bufferSize) { + buffer = new byte[bufferSize]; bufferSize = 0; bufferPos = 0; totalBytesRetired = 0; @@ -904,15 +907,6 @@ public final class CodedInputStream { 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; } @@ -1056,20 +1050,6 @@ public final class CodedInputStream { 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); - } - } - - /** * 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. @@ -1180,86 +1160,97 @@ public final class CodedInputStream { } } - if (totalBytesRetired + bufferPos + size > currentLimit) { + // Verify that the message size so far has not exceeded sizeLimit. + int currentMessageSize = totalBytesRetired + bufferPos + size; + if (currentMessageSize > sizeLimit) { + throw InvalidProtocolBufferException.sizeLimitExceeded(); + } + + // Verify that the message size so far has not exceeded currentLimit. + if (currentMessageSize > currentLimit) { // Read to the end of the stream anyway. skipRawBytes(currentLimit - totalBytesRetired - bufferPos); - // Then fail. throw InvalidProtocolBufferException.truncatedMessage(); } - 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. + // We need the input stream to proceed. + if (input == null) { + throw InvalidProtocolBufferException.truncatedMessage(); + } - // First copy what we have. + final int originalBufferPos = bufferPos; + final int bufferedBytes = bufferSize - bufferPos; + + // Mark the current buffer consumed. + totalBytesRetired += bufferSize; + bufferPos = 0; + bufferSize = 0; + + // Determine the number of bytes we need to read from the input stream. + int sizeLeft = size - bufferedBytes; + // TODO(nathanmittler): Consider using a value larger than BUFFER_SIZE. + if (sizeLeft < BUFFER_SIZE || sizeLeft <= input.available()) { + // Either the bytes we need are known to be available, or the required buffer is + // within an allowed threshold - go ahead and allocate the buffer now. final byte[] bytes = new byte[size]; - int pos = bufferSize - bufferPos; - System.arraycopy(buffer, bufferPos, bytes, 0, pos); - bufferPos = bufferSize; - // 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. - ensureAvailable(size - pos); - System.arraycopy(buffer, 0, bytes, pos, size - pos); - bufferPos = size - pos; + // Copy all of the buffered bytes to the result buffer. + System.arraycopy(buffer, originalBufferPos, bytes, 0, bufferedBytes); - return bytes; - } else { - // The size is very large. For security reasons, we can't allocate the - // entire byte array yet. The size comes directly from the input, so a - // maliciously-crafted message could provide a bogus very large size in - // order to trick the app into allocating a lot of memory. We avoid this - // by allocating and reading only a small chunk at a time, so that the - // malicious message must actually *be* extremely large to cause - // problems. Meanwhile, we limit the allowed size of a message elsewhere. - - // Remember the buffer markers since we'll have to copy the bytes out of - // it later. - final int originalBufferPos = bufferPos; - final int originalBufferSize = bufferSize; - - // Mark the current buffer consumed. - totalBytesRetired += bufferSize; - bufferPos = 0; - bufferSize = 0; - - // Read all the rest of the bytes we need. - int sizeLeft = size - (originalBufferSize - originalBufferPos); - final List<byte[]> chunks = new ArrayList<byte[]>(); - - while (sizeLeft > 0) { - final byte[] chunk = new byte[Math.min(sizeLeft, BUFFER_SIZE)]; - int pos = 0; - while (pos < chunk.length) { - final int n = (input == null) ? -1 : - input.read(chunk, pos, chunk.length - pos); - if (n == -1) { - throw InvalidProtocolBufferException.truncatedMessage(); - } - totalBytesRetired += n; - pos += n; + // Fill the remaining bytes from the input stream. + int pos = bufferedBytes; + while (pos < bytes.length) { + int n = input.read(bytes, pos, size - pos); + if (n == -1) { + throw InvalidProtocolBufferException.truncatedMessage(); } - sizeLeft -= chunk.length; - chunks.add(chunk); + totalBytesRetired += n; + pos += n; } - // OK, got everything. Now concatenate it all into one buffer. - final byte[] bytes = new byte[size]; - - // Start by copying the leftover bytes from this.buffer. - int pos = originalBufferSize - originalBufferPos; - System.arraycopy(buffer, originalBufferPos, bytes, 0, pos); + return bytes; + } - // And now all the chunks. - for (final byte[] chunk : chunks) { - System.arraycopy(chunk, 0, bytes, pos, chunk.length); - pos += chunk.length; + // The size is very large. For security reasons, we can't allocate the + // entire byte array yet. The size comes directly from the input, so a + // maliciously-crafted message could provide a bogus very large size in + // order to trick the app into allocating a lot of memory. We avoid this + // by allocating and reading only a small chunk at a time, so that the + // malicious message must actually *be* extremely large to cause + // problems. Meanwhile, we limit the allowed size of a message elsewhere. + final List<byte[]> chunks = new ArrayList<byte[]>(); + + while (sizeLeft > 0) { + // TODO(nathanmittler): Consider using a value larger than BUFFER_SIZE. + final byte[] chunk = new byte[Math.min(sizeLeft, BUFFER_SIZE)]; + int pos = 0; + while (pos < chunk.length) { + final int n = input.read(chunk, pos, chunk.length - pos); + if (n == -1) { + throw InvalidProtocolBufferException.truncatedMessage(); + } + totalBytesRetired += n; + pos += n; } + sizeLeft -= chunk.length; + chunks.add(chunk); + } - // Done. - return bytes; + // OK, got everything. Now concatenate it all into one buffer. + final byte[] bytes = new byte[size]; + + // Start by copying the leftover bytes from this.buffer. + System.arraycopy(buffer, originalBufferPos, bytes, 0, bufferedBytes); + + // And now all the chunks. + int pos = bufferedBytes; + for (final byte[] chunk : chunks) { + System.arraycopy(chunk, 0, bytes, pos, chunk.length); + pos += chunk.length; } + + // Done. + return bytes; } /** diff --git a/java/core/src/main/java/com/google/protobuf/CodedOutputStream.java b/java/core/src/main/java/com/google/protobuf/CodedOutputStream.java new file mode 100644 index 00000000..ad174d0f --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/CodedOutputStream.java @@ -0,0 +1,2664 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// 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 static java.lang.Math.max; + +import com.google.protobuf.Utf8.UnpairedSurrogateException; + +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.Field; +import java.nio.BufferOverflowException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.security.AccessController; +import java.security.PrivilegedExceptionAction; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Encodes and writes protocol message fields. + * + * <p>This class contains two kinds of methods: methods that write specific + * protocol message constructs and field types (e.g. {@link #writeTag} and + * {@link #writeInt32}) and methods that write low-level values (e.g. + * {@link #writeRawVarint32} and {@link #writeRawBytes}). If you are + * writing encoded protocol messages, you should use the former methods, but if + * you are writing some other format of your own design, use the latter. + * + * <p>This class is totally unsynchronized. + */ +public abstract class CodedOutputStream extends ByteOutput { + private static final Logger logger = Logger.getLogger(CodedOutputStream.class.getName()); + private static final sun.misc.Unsafe UNSAFE = getUnsafe(); + private static final boolean HAS_UNSAFE_ARRAY_OPERATIONS = supportsUnsafeArrayOperations(); + private static final long ARRAY_BASE_OFFSET = byteArrayBaseOffset(); + + private static final int FIXED_32_SIZE = 4; + private static final int FIXED_64_SIZE = 8; + private static final int MAX_VARINT_SIZE = 10; + + /** + * @deprecated Use {@link #computeFixed32SizeNoTag(int)} instead. + */ + @Deprecated + public static final int LITTLE_ENDIAN_32_SIZE = FIXED_32_SIZE; + + /** + * The buffer size used in {@link #newInstance(OutputStream)}. + */ + public static final int DEFAULT_BUFFER_SIZE = 4096; + + /** + * Returns the buffer size to efficiently write dataLength bytes to this + * CodedOutputStream. Used by AbstractMessageLite. + * + * @return the buffer size to efficiently write dataLength bytes to this + * CodedOutputStream. + */ + static int computePreferredBufferSize(int dataLength) { + if (dataLength > DEFAULT_BUFFER_SIZE) { + return DEFAULT_BUFFER_SIZE; + } + return dataLength; + } + + /** + * Create a new {@code CodedOutputStream} wrapping the given {@code OutputStream}. + * + * <p> NOTE: The provided {@link OutputStream} <strong>MUST NOT</strong> retain access or + * modify the provided byte arrays. Doing so may result in corrupted data, which would be + * difficult to debug. + */ + public static CodedOutputStream newInstance(final OutputStream output) { + return newInstance(output, DEFAULT_BUFFER_SIZE); + } + + /** + * Create a new {@code CodedOutputStream} wrapping the given {@code OutputStream} with a given + * buffer size. + * + * <p> NOTE: The provided {@link OutputStream} <strong>MUST NOT</strong> retain access or + * modify the provided byte arrays. Doing so may result in corrupted data, which would be + * difficult to debug. + */ + public static CodedOutputStream newInstance(final OutputStream output, final int bufferSize) { + return new OutputStreamEncoder(output, bufferSize); + } + + /** + * Create a new {@code CodedOutputStream} that writes directly to the given + * byte array. If more bytes are written than fit in the array, + * {@link OutOfSpaceException} will be thrown. Writing directly to a flat + * array is faster than writing to an {@code OutputStream}. See also + * {@link ByteString#newCodedBuilder}. + */ + public static CodedOutputStream newInstance(final byte[] flatArray) { + return newInstance(flatArray, 0, flatArray.length); + } + + /** + * Create a new {@code CodedOutputStream} that writes directly to the given + * byte array slice. If more bytes are written than fit in the slice, + * {@link OutOfSpaceException} will be thrown. Writing directly to a flat + * array is faster than writing to an {@code OutputStream}. See also + * {@link ByteString#newCodedBuilder}. + */ + public static CodedOutputStream newInstance( + final byte[] flatArray, final int offset, final int length) { + return new ArrayEncoder(flatArray, offset, length); + } + + /** + * Create a new {@code CodedOutputStream} that writes to the given {@link ByteBuffer}. + */ + public static CodedOutputStream newInstance(ByteBuffer byteBuffer) { + if (byteBuffer.hasArray()) { + return new NioHeapEncoder(byteBuffer); + } + return new NioEncoder(byteBuffer); + } + + /** + * Create a new {@code CodedOutputStream} that writes to the given {@link ByteBuffer}. + * + * @deprecated the size parameter is no longer used since use of an internal buffer is useless + * (and wasteful) when writing to a {@link ByteBuffer}. Use {@link #newInstance(ByteBuffer)} + * instead. + */ + @Deprecated + public static CodedOutputStream newInstance(ByteBuffer byteBuffer, + @SuppressWarnings("unused") int unused) { + return newInstance(byteBuffer); + } + + /** + * Create a new {@code CodedOutputStream} that writes to the provided {@link ByteOutput}. + * + * <p> NOTE: The {@link ByteOutput} <strong>MUST NOT</strong> modify the provided buffers. Doing + * so may result in corrupted data, which would be difficult to debug. + * + * @param byteOutput the output target for encoded bytes. + * @param bufferSize the size of the internal scratch buffer to be used for string encoding. + * Setting this to {@code 0} will disable buffering, requiring an allocation for each encoded + * string. + */ + static CodedOutputStream newInstance(ByteOutput byteOutput, int bufferSize) { + if (bufferSize < 0) { + throw new IllegalArgumentException("bufferSize must be positive"); + } + + return new ByteOutputEncoder(byteOutput, bufferSize); + } + + // Disallow construction outside of this class. + private CodedOutputStream() { + } + + // ----------------------------------------------------------------- + + /** Encode and write a tag. */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeTag(int fieldNumber, int wireType) throws IOException; + + /** Write an {@code int32} field, including tag, to the stream. */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeInt32(int fieldNumber, int value) throws IOException; + + /** Write a {@code uint32} field, including tag, to the stream. */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeUInt32(int fieldNumber, int value) throws IOException; + + /** Write a {@code sint32} field, including tag, to the stream. */ + public final void writeSInt32(final int fieldNumber, final int value) throws IOException { + writeUInt32(fieldNumber, encodeZigZag32(value)); + } + + /** Write a {@code fixed32} field, including tag, to the stream. */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeFixed32(int fieldNumber, int value) throws IOException; + + /** Write an {@code sfixed32} field, including tag, to the stream. */ + public final void writeSFixed32(final int fieldNumber, final int value) throws IOException { + writeFixed32(fieldNumber, value); + } + + /** Write an {@code int64} field, including tag, to the stream. */ + public final void writeInt64(final int fieldNumber, final long value) throws IOException { + writeUInt64(fieldNumber, value); + } + + /** Write a {@code uint64} field, including tag, to the stream. */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeUInt64(int fieldNumber, long value) throws IOException; + + /** Write an {@code sint64} field, including tag, to the stream. */ + public final void writeSInt64(final int fieldNumber, final long value) throws IOException { + writeUInt64(fieldNumber, encodeZigZag64(value)); + } + + /** Write a {@code fixed64} field, including tag, to the stream. */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeFixed64(int fieldNumber, long value) throws IOException; + + /** Write an {@code sfixed64} field, including tag, to the stream. */ + public final void writeSFixed64(final int fieldNumber, final long value) throws IOException { + writeFixed64(fieldNumber, value); + } + + /** Write a {@code float} field, including tag, to the stream. */ + public final void writeFloat(final int fieldNumber, final float value) throws IOException { + writeFixed32(fieldNumber, Float.floatToRawIntBits(value)); + } + + /** Write a {@code double} field, including tag, to the stream. */ + public final void writeDouble(final int fieldNumber, final double value) throws IOException { + writeFixed64(fieldNumber, Double.doubleToRawLongBits(value)); + } + + /** Write a {@code bool} field, including tag, to the stream. */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeBool(int fieldNumber, boolean value) throws IOException; + + /** + * Write an enum field, including tag, to the stream. The provided value is the numeric + * value used to represent the enum value on the wire (not the enum ordinal value). + */ + public final void writeEnum(final int fieldNumber, final int value) throws IOException { + writeInt32(fieldNumber, value); + } + + /** Write a {@code string} field, including tag, to the stream. */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeString(int fieldNumber, String value) throws IOException; + + /** Write a {@code bytes} field, including tag, to the stream. */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeBytes(int fieldNumber, ByteString value) throws IOException; + + /** Write a {@code bytes} field, including tag, to the stream. */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeByteArray(int fieldNumber, byte[] value) throws IOException; + + /** Write a {@code bytes} field, including tag, to the stream. */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeByteArray(int fieldNumber, byte[] value, int offset, int length) + throws IOException; + + /** + * 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())}. + */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeByteBuffer(int fieldNumber, ByteBuffer value) throws IOException; + + /** + * Write a single byte. + */ + public final void writeRawByte(final byte value) throws IOException { + write(value); + } + + /** Write a single byte, represented by an integer value. */ + public final void writeRawByte(final int value) throws IOException { + write((byte) value); + } + + /** Write an array of bytes. */ + public final void writeRawBytes(final byte[] value) throws IOException { + write(value, 0, value.length); + } + + /** + * Write part of an array of bytes. + */ + public final void writeRawBytes(final byte[] value, int offset, int length) throws IOException { + write(value, offset, length); + } + + /** Write a byte string. */ + public final void writeRawBytes(final ByteString value) throws IOException { + value.writeTo(this); + } + + /** + * 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())}. + */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeRawBytes(final ByteBuffer value) throws IOException; + + /** Write an embedded message field, including tag, to the stream. */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeMessage(final int fieldNumber, final MessageLite value) + throws IOException; + + /** + * Write a MessageSet extension field to the stream. For historical reasons, + * the wire format differs from normal fields. + */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeMessageSetExtension(final int fieldNumber, final MessageLite value) + throws IOException; + + /** + * Write an unparsed MessageSet extension field to the stream. For + * historical reasons, the wire format differs from normal fields. + */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeRawMessageSetExtension(final int fieldNumber, final ByteString value) + throws IOException; + + // ----------------------------------------------------------------- + + /** Write an {@code int32} field to the stream. */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeInt32NoTag(final int value) throws IOException; + + /** Write a {@code uint32} field to the stream. */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeUInt32NoTag(int value) throws IOException; + + /** Write a {@code sint32} field to the stream. */ + public final void writeSInt32NoTag(final int value) throws IOException { + writeUInt32NoTag(encodeZigZag32(value)); + } + + /** Write a {@code fixed32} field to the stream. */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeFixed32NoTag(int value) throws IOException; + + /** Write a {@code sfixed32} field to the stream. */ + public final void writeSFixed32NoTag(final int value) throws IOException { + writeFixed32NoTag(value); + } + + /** Write an {@code int64} field to the stream. */ + public final void writeInt64NoTag(final long value) throws IOException { + writeUInt64NoTag(value); + } + + /** Write a {@code uint64} field to the stream. */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeUInt64NoTag(long value) throws IOException; + + /** Write a {@code sint64} field to the stream. */ + public final void writeSInt64NoTag(final long value) throws IOException { + writeUInt64NoTag(encodeZigZag64(value)); + } + + /** Write a {@code fixed64} field to the stream. */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeFixed64NoTag(long value) throws IOException; + + /** Write a {@code sfixed64} field to the stream. */ + public final void writeSFixed64NoTag(final long value) throws IOException { + writeFixed64NoTag(value); + } + + /** Write a {@code float} field to the stream. */ + public final void writeFloatNoTag(final float value) throws IOException { + writeFixed32NoTag(Float.floatToRawIntBits(value)); + } + + /** Write a {@code double} field to the stream. */ + public final void writeDoubleNoTag(final double value) throws IOException { + writeFixed64NoTag(Double.doubleToRawLongBits(value)); + } + + /** Write a {@code bool} field to the stream. */ + public final void writeBoolNoTag(final boolean value) throws IOException { + write((byte) (value ? 1 : 0)); + } + + /** + * Write an enum field to the stream. The provided value is the numeric + * value used to represent the enum value on the wire (not the enum ordinal value). + */ + public final void writeEnumNoTag(final int value) throws IOException { + writeInt32NoTag(value); + } + + /** Write a {@code string} field to the stream. */ + // TODO(dweis): Document behavior on ill-formed UTF-16 input. + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeStringNoTag(String value) throws IOException; + + /** Write a {@code bytes} field to the stream. */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeBytesNoTag(final ByteString value) throws IOException; + + /** Write a {@code bytes} field to the stream. */ + public final void writeByteArrayNoTag(final byte[] value) throws IOException { + writeByteArrayNoTag(value, 0, value.length); + } + + /** Write an embedded message field to the stream. */ + // Abstract to avoid overhead of additional virtual method calls. + public abstract void writeMessageNoTag(final MessageLite value) throws IOException; + + //================================================================= + + @ExperimentalApi + @Override + public abstract void write(byte value) throws IOException; + + @ExperimentalApi + @Override + public abstract void write(byte[] value, int offset, int length) throws IOException; + + @ExperimentalApi + @Override + public abstract void writeLazy(byte[] value, int offset, int length) throws IOException; + + @Override + public abstract void write(ByteBuffer value) throws IOException; + + @ExperimentalApi + @Override + public abstract void writeLazy(ByteBuffer value) throws IOException; + + // ================================================================= + // ================================================================= + + /** + * Compute the number of bytes that would be needed to encode an + * {@code int32} field, including tag. + */ + public static int computeInt32Size(final int fieldNumber, final int value) { + return computeTagSize(fieldNumber) + computeInt32SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code uint32} field, including tag. + */ + public static int computeUInt32Size(final int fieldNumber, final int value) { + return computeTagSize(fieldNumber) + computeUInt32SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code sint32} field, including tag. + */ + public static int computeSInt32Size(final int fieldNumber, final int value) { + return computeTagSize(fieldNumber) + computeSInt32SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code fixed32} field, including tag. + */ + public static int computeFixed32Size(final int fieldNumber, final int value) { + return computeTagSize(fieldNumber) + computeFixed32SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code sfixed32} field, including tag. + */ + public static int computeSFixed32Size(final int fieldNumber, final int value) { + return computeTagSize(fieldNumber) + computeSFixed32SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code int64} field, including tag. + */ + public static int computeInt64Size(final int fieldNumber, final long value) { + return computeTagSize(fieldNumber) + computeInt64SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code uint64} field, including tag. + */ + public static int computeUInt64Size(final int fieldNumber, final long value) { + return computeTagSize(fieldNumber) + computeUInt64SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code sint64} field, including tag. + */ + public static int computeSInt64Size(final int fieldNumber, final long value) { + return computeTagSize(fieldNumber) + computeSInt64SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code fixed64} field, including tag. + */ + public static int computeFixed64Size(final int fieldNumber, final long value) { + return computeTagSize(fieldNumber) + computeFixed64SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code sfixed64} field, including tag. + */ + public static int computeSFixed64Size(final int fieldNumber, final long value) { + return computeTagSize(fieldNumber) + computeSFixed64SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code float} field, including tag. + */ + public static int computeFloatSize(final int fieldNumber, final float value) { + return computeTagSize(fieldNumber) + computeFloatSizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code double} field, including tag. + */ + public static int computeDoubleSize(final int fieldNumber, final double value) { + return computeTagSize(fieldNumber) + computeDoubleSizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code bool} field, including tag. + */ + public static int computeBoolSize(final int fieldNumber, final boolean value) { + return computeTagSize(fieldNumber) + computeBoolSizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * enum field, including tag. The provided value is the numeric + * value used to represent the enum value on the wire (not the enum ordinal value). + */ + public static int computeEnumSize(final int fieldNumber, final int value) { + return computeTagSize(fieldNumber) + computeEnumSizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code string} field, including tag. + */ + public static int computeStringSize(final int fieldNumber, final String value) { + return computeTagSize(fieldNumber) + computeStringSizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code bytes} field, including tag. + */ + public static int computeBytesSize(final int fieldNumber, final ByteString value) { + 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 LazyFieldLite value) { + return computeTagSize(fieldNumber) + computeLazyFieldSizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * embedded message field, including tag. + */ + public static int computeMessageSize(final int fieldNumber, final MessageLite value) { + return computeTagSize(fieldNumber) + computeMessageSizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * MessageSet extension to the stream. For historical reasons, + * the wire format differs from normal fields. + */ + public static int computeMessageSetExtensionSize(final int fieldNumber, final MessageLite value) { + return computeTagSize(WireFormat.MESSAGE_SET_ITEM) * 2 + + computeUInt32Size(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber) + + computeMessageSize(WireFormat.MESSAGE_SET_MESSAGE, value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * unparsed MessageSet extension field to the stream. For + * historical reasons, the wire format differs from normal fields. + */ + public static int computeRawMessageSetExtensionSize( + final int fieldNumber, final ByteString value) { + return computeTagSize(WireFormat.MESSAGE_SET_ITEM) * 2 + + computeUInt32Size(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber) + + computeBytesSize(WireFormat.MESSAGE_SET_MESSAGE, value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * lazily parsed MessageSet extension field to the stream. For + * historical reasons, the wire format differs from normal fields. + */ + public static int computeLazyFieldMessageSetExtensionSize( + 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); + } + + // ----------------------------------------------------------------- + + /** Compute the number of bytes that would be needed to encode a tag. */ + public static int computeTagSize(final int fieldNumber) { + return computeUInt32SizeNoTag(WireFormat.makeTag(fieldNumber, 0)); + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code int32} field, including tag. + */ + public static int computeInt32SizeNoTag(final int value) { + if (value >= 0) { + return computeUInt32SizeNoTag(value); + } else { + // Must sign-extend. + return MAX_VARINT_SIZE; + } + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code uint32} field. + */ + public static int computeUInt32SizeNoTag(final int value) { + if ((value & (~0 << 7)) == 0) { + return 1; + } + if ((value & (~0 << 14)) == 0) { + return 2; + } + if ((value & (~0 << 21)) == 0) { + return 3; + } + if ((value & (~0 << 28)) == 0) { + return 4; + } + return 5; + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code sint32} field. + */ + public static int computeSInt32SizeNoTag(final int value) { + return computeUInt32SizeNoTag(encodeZigZag32(value)); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code fixed32} field. + */ + public static int computeFixed32SizeNoTag(@SuppressWarnings("unused") final int unused) { + return FIXED_32_SIZE; + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code sfixed32} field. + */ + public static int computeSFixed32SizeNoTag(@SuppressWarnings("unused") final int unused) { + return FIXED_32_SIZE; + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code int64} field, including tag. + */ + public static int computeInt64SizeNoTag(final long value) { + return computeUInt64SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code uint64} field, including tag. + */ + public static int computeUInt64SizeNoTag(long value) { + // handle two popular special cases up front ... + if ((value & (~0L << 7)) == 0L) { + return 1; + } + if (value < 0L) { + return 10; + } + // ... leaving us with 8 remaining, which we can divide and conquer + int n = 2; + if ((value & (~0L << 35)) != 0L) { + n += 4; value >>>= 28; + } + if ((value & (~0L << 21)) != 0L) { + n += 2; value >>>= 14; + } + if ((value & (~0L << 14)) != 0L) { + n += 1; + } + return n; + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code sint64} field. + */ + public static int computeSInt64SizeNoTag(final long value) { + return computeUInt64SizeNoTag(encodeZigZag64(value)); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code fixed64} field. + */ + public static int computeFixed64SizeNoTag(@SuppressWarnings("unused") final long unused) { + return FIXED_64_SIZE; + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code sfixed64} field. + */ + public static int computeSFixed64SizeNoTag(@SuppressWarnings("unused") final long unused) { + return FIXED_64_SIZE; + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code float} field, including tag. + */ + public static int computeFloatSizeNoTag(@SuppressWarnings("unused") final float unused) { + return FIXED_32_SIZE; + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code double} field, including tag. + */ + public static int computeDoubleSizeNoTag(@SuppressWarnings("unused") final double unused) { + return FIXED_64_SIZE; + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code bool} field. + */ + public static int computeBoolSizeNoTag(@SuppressWarnings("unused") final boolean unused) { + return 1; + } + + /** + * Compute the number of bytes that would be needed to encode an enum field. + * The provided value is the numeric value used to represent the enum value on the wire + * (not the enum ordinal value). + */ + public static int computeEnumSizeNoTag(final int value) { + return computeInt32SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code string} field. + */ + public static int computeStringSizeNoTag(final String value) { + int length; + try { + length = Utf8.encodedLength(value); + } catch (UnpairedSurrogateException e) { + // TODO(dweis): Consider using nio Charset methods instead. + final byte[] bytes = value.getBytes(Internal.UTF_8); + length = bytes.length; + } + + return computeLengthDelimitedFieldSize(length); + } + + /** + * Compute the number of bytes that would be needed to encode an embedded + * message stored in lazy field. + */ + public static int computeLazyFieldSizeNoTag(final LazyFieldLite value) { + return computeLengthDelimitedFieldSize(value.getSerializedSize()); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code bytes} field. + */ + public static int computeBytesSizeNoTag(final ByteString value) { + return computeLengthDelimitedFieldSize(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 computeLengthDelimitedFieldSize(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 computeLengthDelimitedFieldSize(value.capacity()); + } + + /** + * Compute the number of bytes that would be needed to encode an embedded + * message field. + */ + public static int computeMessageSizeNoTag(final MessageLite value) { + return computeLengthDelimitedFieldSize(value.getSerializedSize()); + } + + private static int computeLengthDelimitedFieldSize(int fieldLength) { + return computeUInt32SizeNoTag(fieldLength) + fieldLength; + } + + /** + * Encode a ZigZag-encoded 32-bit value. ZigZag encodes signed integers + * into values that can be efficiently encoded with varint. (Otherwise, + * negative values must be sign-extended to 64 bits to be varint encoded, + * thus always taking 10 bytes on the wire.) + * + * @param n A signed 32-bit integer. + * @return An unsigned 32-bit integer, stored in a signed int because + * Java has no explicit unsigned support. + */ + public static int encodeZigZag32(final int n) { + // Note: the right-shift must be arithmetic + return (n << 1) ^ (n >> 31); + } + + /** + * Encode a ZigZag-encoded 64-bit value. ZigZag encodes signed integers + * into values that can be efficiently encoded with varint. (Otherwise, + * negative values must be sign-extended to 64 bits to be varint encoded, + * thus always taking 10 bytes on the wire.) + * + * @param n A signed 64-bit integer. + * @return An unsigned 64-bit integer, stored in a signed int because + * Java has no explicit unsigned support. + */ + public static long encodeZigZag64(final long n) { + // Note: the right-shift must be arithmetic + return (n << 1) ^ (n >> 63); + } + + // ================================================================= + + /** + * Flushes the stream and forces any buffered bytes to be written. This + * does not flush the underlying OutputStream. + */ + public abstract void flush() throws IOException; + + /** + * If writing to a flat array, return the space left in the array. + * Otherwise, throws {@code UnsupportedOperationException}. + */ + public abstract int spaceLeft(); + + /** + * Verifies that {@link #spaceLeft()} returns zero. It's common to create + * a byte array that is exactly big enough to hold a message, then write to + * it with a {@code CodedOutputStream}. Calling {@code checkNoSpaceLeft()} + * after writing verifies that the message was actually as big as expected, + * which can help catch bugs. + */ + public final void checkNoSpaceLeft() { + if (spaceLeft() != 0) { + throw new IllegalStateException("Did not write as much data as expected."); + } + } + + /** + * If you create a CodedOutputStream around a simple flat array, you must + * not attempt to write more bytes than the array has space. Otherwise, + * this exception will be thrown. + */ + public static class OutOfSpaceException extends IOException { + private static final long serialVersionUID = -6947486886997889499L; + + private static final String MESSAGE = + "CodedOutputStream was writing to a flat byte array and ran out of space."; + + OutOfSpaceException() { + super(MESSAGE); + } + + OutOfSpaceException(Throwable cause) { + super(MESSAGE, cause); + } + } + + /** + * 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 abstract int getTotalBytesWritten(); + + // ================================================================= + + /** Write a {@code bytes} field to the stream. Visible for testing. */ + abstract void writeByteArrayNoTag(final byte[] value, final int offset, final int length) + throws IOException; + + final void inefficientWriteStringNoTag(String value, UnpairedSurrogateException cause) + throws IOException { + logger.log(Level.WARNING, + "Converting ill-formed UTF-16. Your Protocol Buffer will not round trip correctly!", cause); + + // Unfortunately there does not appear to be any way to tell Java to encode + // UTF-8 directly into our buffer, so we have to let it create its own byte + // array and then copy. + // TODO(dweis): Consider using nio Charset methods instead. + final byte[] bytes = value.getBytes(Internal.UTF_8); + try { + writeUInt32NoTag(bytes.length); + writeLazy(bytes, 0, bytes.length); + } catch (IndexOutOfBoundsException e) { + throw new OutOfSpaceException(e); + } catch (OutOfSpaceException e) { + throw e; + } + } + + // ================================================================= + + /** + * Write a {@code group} field, including tag, to the stream. + * + * @deprecated groups are deprecated. + */ + @Deprecated + public final void writeGroup(final int fieldNumber, final MessageLite value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_START_GROUP); + writeGroupNoTag(value); + writeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP); + } + + /** + * Write a {@code group} field to the stream. + * + * @deprecated groups are deprecated. + */ + @Deprecated + public final void writeGroupNoTag(final MessageLite value) throws IOException { + value.writeTo(this); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code group} field, including tag. + * + * @deprecated groups are deprecated. + */ + @Deprecated + public static int computeGroupSize(final int fieldNumber, final MessageLite value) { + return computeTagSize(fieldNumber) * 2 + computeGroupSizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code group} field. + */ + @Deprecated + public static int computeGroupSizeNoTag(final MessageLite value) { + return value.getSerializedSize(); + } + + /** + * Encode and write a varint. {@code value} is treated as + * unsigned, so it won't be sign-extended if negative. + * + * @deprecated use {@link #writeUInt32NoTag} instead. + */ + @Deprecated + public final void writeRawVarint32(int value) throws IOException { + writeUInt32NoTag(value); + } + + /** + * Encode and write a varint. + * + * @deprecated use {@link #writeUInt64NoTag} instead. + */ + @Deprecated + public final void writeRawVarint64(long value) throws IOException { + writeUInt64NoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a varint. + * {@code value} is treated as unsigned, so it won't be sign-extended if + * negative. + * + * @deprecated use {@link #computeUInt32SizeNoTag(int)} instead. + */ + @Deprecated + public static int computeRawVarint32Size(final int value) { + return computeUInt32SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a varint. + * + * @deprecated use {@link #computeUInt64SizeNoTag(long)} instead. + */ + @Deprecated + public static int computeRawVarint64Size(long value) { + return computeUInt64SizeNoTag(value); + } + + /** + * Write a little-endian 32-bit integer. + * + * @deprecated Use {@link #writeFixed32NoTag} instead. + */ + @Deprecated + public final void writeRawLittleEndian32(final int value) throws IOException { + writeFixed32NoTag(value); + } + + /** + * Write a little-endian 64-bit integer. + * + * @deprecated Use {@link #writeFixed64NoTag} instead. + */ + @Deprecated + public final void writeRawLittleEndian64(final long value) throws IOException { + writeFixed64NoTag(value); + } + + // ================================================================= + + /** + * A {@link CodedOutputStream} that writes directly to a byte array. + */ + private static class ArrayEncoder extends CodedOutputStream { + private final byte[] buffer; + private final int offset; + private final int limit; + private int position; + + ArrayEncoder(byte[] buffer, int offset, int length) { + if (buffer == null) { + throw new NullPointerException("buffer"); + } + if ((offset | length | (buffer.length - (offset + length))) < 0) { + throw new IllegalArgumentException(String.format( + "Array range is invalid. Buffer.length=%d, offset=%d, length=%d", + buffer.length, offset, length)); + } + this.buffer = buffer; + this.offset = offset; + position = offset; + limit = offset + length; + } + + @Override + public final void writeTag(final int fieldNumber, final int wireType) throws IOException { + writeUInt32NoTag(WireFormat.makeTag(fieldNumber, wireType)); + } + + @Override + public final void writeInt32(final int fieldNumber, final int value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + writeInt32NoTag(value); + } + + @Override + public final void writeUInt32(final int fieldNumber, final int value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + writeUInt32NoTag(value); + } + + @Override + public final void writeFixed32(final int fieldNumber, final int value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED32); + writeFixed32NoTag(value); + } + + @Override + public final void writeUInt64(final int fieldNumber, final long value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + writeUInt64NoTag(value); + } + + @Override + public final void writeFixed64(final int fieldNumber, final long value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED64); + writeFixed64NoTag(value); + } + + @Override + public final void writeBool(final int fieldNumber, final boolean value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + write((byte) (value ? 1 : 0)); + } + + @Override + public final void writeString(final int fieldNumber, final String value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeStringNoTag(value); + } + + @Override + public final void writeBytes(final int fieldNumber, final ByteString value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeBytesNoTag(value); + } + + @Override + public final void writeByteArray(final int fieldNumber, final byte[] value) throws IOException { + writeByteArray(fieldNumber, value, 0, value.length); + } + + @Override + public final 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); + } + + @Override + public final void writeByteBuffer(final int fieldNumber, final ByteBuffer value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeUInt32NoTag(value.capacity()); + writeRawBytes(value); + } + + @Override + public final void writeBytesNoTag(final ByteString value) throws IOException { + writeUInt32NoTag(value.size()); + value.writeTo(this); + } + + @Override + public final void writeByteArrayNoTag(final byte[] value, int offset, int length) + throws IOException { + writeUInt32NoTag(length); + write(value, offset, length); + } + + @Override + public final void writeRawBytes(final ByteBuffer value) throws IOException { + if (value.hasArray()) { + write(value.array(), value.arrayOffset(), value.capacity()); + } else { + ByteBuffer duplicated = value.duplicate(); + duplicated.clear(); + write(duplicated); + } + } + + @Override + public final void writeMessage(final int fieldNumber, final MessageLite value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeMessageNoTag(value); + } + + @Override + public final void writeMessageSetExtension(final int fieldNumber, final MessageLite value) + throws IOException { + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_START_GROUP); + writeUInt32(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber); + writeMessage(WireFormat.MESSAGE_SET_MESSAGE, value); + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_END_GROUP); + } + + @Override + public final void writeRawMessageSetExtension(final int fieldNumber, final ByteString value) + throws IOException { + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_START_GROUP); + writeUInt32(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber); + writeBytes(WireFormat.MESSAGE_SET_MESSAGE, value); + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_END_GROUP); + } + + @Override + public final void writeMessageNoTag(final MessageLite value) throws IOException { + writeUInt32NoTag(value.getSerializedSize()); + value.writeTo(this); + } + + @Override + public final void write(byte value) throws IOException { + try { + buffer[position++] = value; + } catch (IndexOutOfBoundsException e) { + throw new OutOfSpaceException(new IndexOutOfBoundsException( + String.format("Pos: %d, limit: %d, len: %d", position, limit, 1))); + } + } + + @Override + public final void writeInt32NoTag(int value) throws IOException { + if (value >= 0) { + writeUInt32NoTag(value); + } else { + // Must sign-extend. + writeUInt64NoTag(value); + } + } + + @Override + public final void writeUInt32NoTag(int value) throws IOException { + if (HAS_UNSAFE_ARRAY_OPERATIONS && spaceLeft() >= MAX_VARINT_SIZE) { + long pos = ARRAY_BASE_OFFSET + position; + while (true) { + if ((value & ~0x7F) == 0) { + UNSAFE.putByte(buffer, pos++, (byte) value); + position++; + return; + } else { + UNSAFE.putByte(buffer, pos++, (byte) ((value & 0x7F) | 0x80)); + position++; + value >>>= 7; + } + } + } else { + try { + while (true) { + if ((value & ~0x7F) == 0) { + buffer[position++] = (byte) value; + return; + } else { + buffer[position++] = (byte) ((value & 0x7F) | 0x80); + value >>>= 7; + } + } + } catch (IndexOutOfBoundsException e) { + throw new OutOfSpaceException( + new IndexOutOfBoundsException( + String.format("Pos: %d, limit: %d, len: %d", position, limit, 1))); + } + } + } + + @Override + public final void writeFixed32NoTag(int value) throws IOException { + try { + buffer[position++] = (byte) (value & 0xFF); + buffer[position++] = (byte) ((value >> 8) & 0xFF); + buffer[position++] = (byte) ((value >> 16) & 0xFF); + buffer[position++] = (byte) ((value >> 24) & 0xFF); + } catch (IndexOutOfBoundsException e) { + throw new OutOfSpaceException( + new IndexOutOfBoundsException( + String.format("Pos: %d, limit: %d, len: %d", position, limit, 1))); + } + } + + @Override + public final void writeUInt64NoTag(long value) throws IOException { + if (HAS_UNSAFE_ARRAY_OPERATIONS && spaceLeft() >= MAX_VARINT_SIZE) { + long pos = ARRAY_BASE_OFFSET + position; + while (true) { + if ((value & ~0x7FL) == 0) { + UNSAFE.putByte(buffer, pos++, (byte) value); + position++; + return; + } else { + UNSAFE.putByte(buffer, pos++, (byte) (((int) value & 0x7F) | 0x80)); + position++; + value >>>= 7; + } + } + } else { + try { + while (true) { + if ((value & ~0x7FL) == 0) { + buffer[position++] = (byte) value; + return; + } else { + buffer[position++] = (byte) (((int) value & 0x7F) | 0x80); + value >>>= 7; + } + } + } catch (IndexOutOfBoundsException e) { + throw new OutOfSpaceException( + new IndexOutOfBoundsException( + String.format("Pos: %d, limit: %d, len: %d", position, limit, 1))); + } + } + } + + @Override + public final void writeFixed64NoTag(long value) throws IOException { + try { + buffer[position++] = (byte) ((int) (value) & 0xFF); + buffer[position++] = (byte) ((int) (value >> 8) & 0xFF); + buffer[position++] = (byte) ((int) (value >> 16) & 0xFF); + buffer[position++] = (byte) ((int) (value >> 24) & 0xFF); + buffer[position++] = (byte) ((int) (value >> 32) & 0xFF); + buffer[position++] = (byte) ((int) (value >> 40) & 0xFF); + buffer[position++] = (byte) ((int) (value >> 48) & 0xFF); + buffer[position++] = (byte) ((int) (value >> 56) & 0xFF); + } catch (IndexOutOfBoundsException e) { + throw new OutOfSpaceException( + new IndexOutOfBoundsException( + String.format("Pos: %d, limit: %d, len: %d", position, limit, 1))); + } + } + + @Override + public final void write(byte[] value, int offset, int length) throws IOException { + try { + System.arraycopy(value, offset, buffer, position, length); + position += length; + } catch (IndexOutOfBoundsException e) { + throw new OutOfSpaceException( + new IndexOutOfBoundsException( + String.format("Pos: %d, limit: %d, len: %d", position, limit, length))); + } + } + + @Override + public final void writeLazy(byte[] value, int offset, int length) throws IOException { + write(value, offset, length); + } + + @Override + public final void write(ByteBuffer value) throws IOException { + final int length = value.remaining(); + try { + value.get(buffer, position, length); + position += length; + } catch (IndexOutOfBoundsException e) { + throw new OutOfSpaceException( + new IndexOutOfBoundsException( + String.format("Pos: %d, limit: %d, len: %d", position, limit, length))); + } + } + + @Override + public final void writeLazy(ByteBuffer value) throws IOException { + write(value); + } + + @Override + public final void writeStringNoTag(String value) throws IOException { + final int oldPosition = position; + try { + // UTF-8 byte length of the string is at least its UTF-16 code unit length (value.length()), + // and at most 3 times of it. We take advantage of this in both branches below. + final int maxLength = value.length() * Utf8.MAX_BYTES_PER_CHAR; + final int maxLengthVarIntSize = computeUInt32SizeNoTag(maxLength); + final int minLengthVarIntSize = computeUInt32SizeNoTag(value.length()); + if (minLengthVarIntSize == maxLengthVarIntSize) { + position = oldPosition + minLengthVarIntSize; + int newPosition = Utf8.encode(value, buffer, position, spaceLeft()); + // Since this class is stateful and tracks the position, we rewind and store the state, + // prepend the length, then reset it back to the end of the string. + position = oldPosition; + int length = newPosition - oldPosition - minLengthVarIntSize; + writeUInt32NoTag(length); + position = newPosition; + } else { + int length = Utf8.encodedLength(value); + writeUInt32NoTag(length); + position = Utf8.encode(value, buffer, position, spaceLeft()); + } + } catch (UnpairedSurrogateException e) { + // Roll back the change - we fall back to inefficient path. + position = oldPosition; + + // TODO(nathanmittler): We should throw an IOException here instead. + inefficientWriteStringNoTag(value, e); + } catch (IndexOutOfBoundsException e) { + throw new OutOfSpaceException(e); + } + } + + @Override + public void flush() { + // Do nothing. + } + + @Override + public final int spaceLeft() { + return limit - position; + } + + @Override + public final int getTotalBytesWritten() { + return position - offset; + } + } + + /** + * A {@link CodedOutputStream} that writes directly to a heap {@link ByteBuffer}. Writes are + * done directly to the underlying array. The buffer position is only updated after a flush. + */ + private static final class NioHeapEncoder extends ArrayEncoder { + private final ByteBuffer byteBuffer; + private int initialPosition; + + NioHeapEncoder(ByteBuffer byteBuffer) { + super(byteBuffer.array(), byteBuffer.arrayOffset() + byteBuffer.position(), + byteBuffer.remaining()); + this.byteBuffer = byteBuffer; + this.initialPosition = byteBuffer.position(); + } + + @Override + public void flush() { + // Update the position on the buffer. + byteBuffer.position(initialPosition + getTotalBytesWritten()); + } + } + + /** + * A {@link CodedOutputStream} that writes directly to a {@link ByteBuffer}. + */ + private static final class NioEncoder extends CodedOutputStream { + private final ByteBuffer originalBuffer; + private final ByteBuffer buffer; + private final int initialPosition; + + NioEncoder(ByteBuffer buffer) { + this.originalBuffer = buffer; + this.buffer = buffer.duplicate().order(ByteOrder.LITTLE_ENDIAN); + initialPosition = buffer.position(); + } + + @Override + public void writeTag(final int fieldNumber, final int wireType) throws IOException { + writeUInt32NoTag(WireFormat.makeTag(fieldNumber, wireType)); + } + + @Override + public void writeInt32(final int fieldNumber, final int value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + writeInt32NoTag(value); + } + + @Override + public void writeUInt32(final int fieldNumber, final int value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + writeUInt32NoTag(value); + } + + @Override + public void writeFixed32(final int fieldNumber, final int value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED32); + writeFixed32NoTag(value); + } + + @Override + public void writeUInt64(final int fieldNumber, final long value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + writeUInt64NoTag(value); + } + + @Override + public void writeFixed64(final int fieldNumber, final long value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED64); + writeFixed64NoTag(value); + } + + @Override + public void writeBool(final int fieldNumber, final boolean value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + write((byte) (value ? 1 : 0)); + } + + @Override + public void writeString(final int fieldNumber, final String value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeStringNoTag(value); + } + + @Override + public void writeBytes(final int fieldNumber, final ByteString value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeBytesNoTag(value); + } + + @Override + public void writeByteArray(final int fieldNumber, final byte[] value) throws IOException { + writeByteArray(fieldNumber, value, 0, value.length); + } + + @Override + 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); + } + + @Override + public void writeByteBuffer(final int fieldNumber, final ByteBuffer value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeUInt32NoTag(value.capacity()); + writeRawBytes(value); + } + + @Override + public void writeMessage(final int fieldNumber, final MessageLite value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeMessageNoTag(value); + } + + @Override + public void writeMessageSetExtension(final int fieldNumber, final MessageLite value) + throws IOException { + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_START_GROUP); + writeUInt32(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber); + writeMessage(WireFormat.MESSAGE_SET_MESSAGE, value); + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_END_GROUP); + } + + @Override + public void writeRawMessageSetExtension(final int fieldNumber, final ByteString value) + throws IOException { + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_START_GROUP); + writeUInt32(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber); + writeBytes(WireFormat.MESSAGE_SET_MESSAGE, value); + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_END_GROUP); + } + + @Override + public void writeMessageNoTag(final MessageLite value) throws IOException { + writeUInt32NoTag(value.getSerializedSize()); + value.writeTo(this); + } + + @Override + public void write(byte value) throws IOException { + try { + buffer.put(value); + } catch (BufferOverflowException e) { + throw new OutOfSpaceException(e); + } + } + + @Override + public void writeBytesNoTag(final ByteString value) throws IOException { + writeUInt32NoTag(value.size()); + value.writeTo(this); + } + + @Override + public void writeByteArrayNoTag(final byte[] value, int offset, int length) throws IOException { + writeUInt32NoTag(length); + write(value, offset, length); + } + + @Override + public void writeRawBytes(final ByteBuffer value) throws IOException { + if (value.hasArray()) { + write(value.array(), value.arrayOffset(), value.capacity()); + } else { + ByteBuffer duplicated = value.duplicate(); + duplicated.clear(); + write(duplicated); + } + } + + @Override + public void writeInt32NoTag(int value) throws IOException { + if (value >= 0) { + writeUInt32NoTag(value); + } else { + // Must sign-extend. + writeUInt64NoTag(value); + } + } + + @Override + public void writeUInt32NoTag(int value) throws IOException { + try { + while (true) { + if ((value & ~0x7F) == 0) { + buffer.put((byte) value); + return; + } else { + buffer.put((byte) ((value & 0x7F) | 0x80)); + value >>>= 7; + } + } + } catch (BufferOverflowException e) { + throw new OutOfSpaceException(e); + } + } + + @Override + public void writeFixed32NoTag(int value) throws IOException { + try { + buffer.putInt(value); + } catch (BufferOverflowException e) { + throw new OutOfSpaceException(e); + } + } + + @Override + public void writeUInt64NoTag(long value) throws IOException { + try { + while (true) { + if ((value & ~0x7FL) == 0) { + buffer.put((byte) value); + return; + } else { + buffer.put((byte) (((int) value & 0x7F) | 0x80)); + value >>>= 7; + } + } + } catch (BufferOverflowException e) { + throw new OutOfSpaceException(e); + } + } + + @Override + public void writeFixed64NoTag(long value) throws IOException { + try { + buffer.putLong(value); + } catch (BufferOverflowException e) { + throw new OutOfSpaceException(e); + } + } + + @Override + public void write(byte[] value, int offset, int length) throws IOException { + try { + buffer.put(value, offset, length); + } catch (IndexOutOfBoundsException e) { + throw new OutOfSpaceException(e); + } catch (BufferOverflowException e) { + throw new OutOfSpaceException(e); + } + } + + @Override + public void writeLazy(byte[] value, int offset, int length) throws IOException { + write(value, offset, length); + } + + @Override + public void write(ByteBuffer value) throws IOException { + try { + buffer.put(value); + } catch (BufferOverflowException e) { + throw new OutOfSpaceException(e); + } + } + + @Override + public void writeLazy(ByteBuffer value) throws IOException { + write(value); + } + + @Override + public void writeStringNoTag(String value) throws IOException { + final int startPos = buffer.position(); + try { + // UTF-8 byte length of the string is at least its UTF-16 code unit length (value.length()), + // and at most 3 times of it. We take advantage of this in both branches below. + final int maxEncodedSize = value.length() * Utf8.MAX_BYTES_PER_CHAR; + final int maxLengthVarIntSize = computeUInt32SizeNoTag(maxEncodedSize); + final int minLengthVarIntSize = computeUInt32SizeNoTag(value.length()); + if (minLengthVarIntSize == maxLengthVarIntSize) { + // Save the current position and increment past the length field. We'll come back + // and write the length field after the encoding is complete. + final int startOfBytes = buffer.position() + minLengthVarIntSize; + buffer.position(startOfBytes); + + // Encode the string. + encode(value); + + // Now go back to the beginning and write the length. + int endOfBytes = buffer.position(); + buffer.position(startPos); + writeUInt32NoTag(endOfBytes - startOfBytes); + + // Reposition the buffer past the written data. + buffer.position(endOfBytes); + } else { + final int length = Utf8.encodedLength(value); + writeUInt32NoTag(length); + encode(value); + } + } catch (UnpairedSurrogateException e) { + // Roll back the change and convert to an IOException. + buffer.position(startPos); + + // TODO(nathanmittler): We should throw an IOException here instead. + inefficientWriteStringNoTag(value, e); + } catch (IllegalArgumentException e) { + // Thrown by buffer.position() if out of range. + throw new OutOfSpaceException(e); + } + } + + @Override + public void flush() { + // Update the position of the original buffer. + originalBuffer.position(buffer.position()); + } + + @Override + public int spaceLeft() { + return buffer.remaining(); + } + + @Override + public int getTotalBytesWritten() { + return buffer.position() - initialPosition; + } + + private void encode(String value) throws IOException { + try { + Utf8.encodeUtf8(value, buffer); + } catch (IndexOutOfBoundsException e) { + throw new OutOfSpaceException(e); + } + } + } + + /** + * Abstract base class for buffered encoders. + */ + private abstract static class AbstractBufferedEncoder extends CodedOutputStream { + final byte[] buffer; + final int limit; + int position; + int totalBytesWritten; + + AbstractBufferedEncoder(int bufferSize) { + if (bufferSize < 0) { + throw new IllegalArgumentException("bufferSize must be >= 0"); + } + // As an optimization, we require that the buffer be able to store at least 2 + // varints so that we can buffer any integer write (tag + value). This reduces the + // number of range checks for a single write to 1 (i.e. if there is not enough space + // to buffer the tag+value, flush and then buffer it). + this.buffer = new byte[max(bufferSize, MAX_VARINT_SIZE * 2)]; + this.limit = buffer.length; + } + + @Override + public final int spaceLeft() { + throw new UnsupportedOperationException( + "spaceLeft() can only be called on CodedOutputStreams that are " + + "writing to a flat array or ByteBuffer."); + } + + @Override + public final int getTotalBytesWritten() { + return totalBytesWritten; + } + + /** + * This method does not perform bounds checking on the array. Checking array bounds is the + * responsibility of the caller. + */ + final void buffer(byte value) { + buffer[position++] = value; + totalBytesWritten++; + } + + /** + * This method does not perform bounds checking on the array. Checking array bounds is the + * responsibility of the caller. + */ + final void bufferTag(final int fieldNumber, final int wireType) { + bufferUInt32NoTag(WireFormat.makeTag(fieldNumber, wireType)); + } + + /** + * This method does not perform bounds checking on the array. Checking array bounds is the + * responsibility of the caller. + */ + final void bufferInt32NoTag(final int value) { + if (value >= 0) { + bufferUInt32NoTag(value); + } else { + // Must sign-extend. + bufferUInt64NoTag(value); + } + } + + /** + * This method does not perform bounds checking on the array. Checking array bounds is the + * responsibility of the caller. + */ + final void bufferUInt32NoTag(int value) { + if (HAS_UNSAFE_ARRAY_OPERATIONS) { + final long originalPos = ARRAY_BASE_OFFSET + position; + long pos = originalPos; + while (true) { + if ((value & ~0x7F) == 0) { + UNSAFE.putByte(buffer, pos++, (byte) value); + break; + } else { + UNSAFE.putByte(buffer, pos++, (byte) ((value & 0x7F) | 0x80)); + value >>>= 7; + } + } + int delta = (int) (pos - originalPos); + position += delta; + totalBytesWritten += delta; + } else { + while (true) { + if ((value & ~0x7F) == 0) { + buffer[position++] = (byte) value; + totalBytesWritten++; + return; + } else { + buffer[position++] = (byte) ((value & 0x7F) | 0x80); + totalBytesWritten++; + value >>>= 7; + } + } + } + } + + /** + * This method does not perform bounds checking on the array. Checking array bounds is the + * responsibility of the caller. + */ + final void bufferUInt64NoTag(long value) { + if (HAS_UNSAFE_ARRAY_OPERATIONS) { + final long originalPos = ARRAY_BASE_OFFSET + position; + long pos = originalPos; + while (true) { + if ((value & ~0x7FL) == 0) { + UNSAFE.putByte(buffer, pos++, (byte) value); + break; + } else { + UNSAFE.putByte(buffer, pos++, (byte) (((int) value & 0x7F) | 0x80)); + value >>>= 7; + } + } + int delta = (int) (pos - originalPos); + position += delta; + totalBytesWritten += delta; + } else { + while (true) { + if ((value & ~0x7FL) == 0) { + buffer[position++] = (byte) value; + totalBytesWritten++; + return; + } else { + buffer[position++] = (byte) (((int) value & 0x7F) | 0x80); + totalBytesWritten++; + value >>>= 7; + } + } + } + } + + /** + * This method does not perform bounds checking on the array. Checking array bounds is the + * responsibility of the caller. + */ + final void bufferFixed32NoTag(int value) { + buffer[position++] = (byte) (value & 0xFF); + buffer[position++] = (byte) ((value >> 8) & 0xFF); + buffer[position++] = (byte) ((value >> 16) & 0xFF); + buffer[position++] = (byte) ((value >> 24) & 0xFF); + totalBytesWritten += FIXED_32_SIZE; + } + + /** + * This method does not perform bounds checking on the array. Checking array bounds is the + * responsibility of the caller. + */ + final void bufferFixed64NoTag(long value) { + buffer[position++] = (byte) (value & 0xFF); + buffer[position++] = (byte) ((value >> 8) & 0xFF); + buffer[position++] = (byte) ((value >> 16) & 0xFF); + buffer[position++] = (byte) ((value >> 24) & 0xFF); + buffer[position++] = (byte) ((int) (value >> 32) & 0xFF); + buffer[position++] = (byte) ((int) (value >> 40) & 0xFF); + buffer[position++] = (byte) ((int) (value >> 48) & 0xFF); + buffer[position++] = (byte) ((int) (value >> 56) & 0xFF); + totalBytesWritten += FIXED_64_SIZE; + } + } + + /** + * A {@link CodedOutputStream} that decorates a {@link ByteOutput}. It internal buffer only to + * support string encoding operations. All other writes are just passed through to the + * {@link ByteOutput}. + */ + private static final class ByteOutputEncoder extends AbstractBufferedEncoder { + private final ByteOutput out; + + ByteOutputEncoder(ByteOutput out, int bufferSize) { + super(bufferSize); + if (out == null) { + throw new NullPointerException("out"); + } + this.out = out; + } + + @Override + public void writeTag(final int fieldNumber, final int wireType) throws IOException { + writeUInt32NoTag(WireFormat.makeTag(fieldNumber, wireType)); + } + + @Override + public void writeInt32(final int fieldNumber, final int value) throws IOException { + flushIfNotAvailable(MAX_VARINT_SIZE * 2); + bufferTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + bufferInt32NoTag(value); + } + + @Override + public void writeUInt32(final int fieldNumber, final int value) throws IOException { + flushIfNotAvailable(MAX_VARINT_SIZE * 2); + bufferTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + bufferUInt32NoTag(value); + } + + @Override + public void writeFixed32(final int fieldNumber, final int value) throws IOException { + flushIfNotAvailable(MAX_VARINT_SIZE + FIXED_32_SIZE); + bufferTag(fieldNumber, WireFormat.WIRETYPE_FIXED32); + bufferFixed32NoTag(value); + } + + @Override + public void writeUInt64(final int fieldNumber, final long value) throws IOException { + flushIfNotAvailable(MAX_VARINT_SIZE * 2); + bufferTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + bufferUInt64NoTag(value); + } + + @Override + public void writeFixed64(final int fieldNumber, final long value) throws IOException { + flushIfNotAvailable(MAX_VARINT_SIZE + FIXED_64_SIZE); + bufferTag(fieldNumber, WireFormat.WIRETYPE_FIXED64); + bufferFixed64NoTag(value); + } + + @Override + public void writeBool(final int fieldNumber, final boolean value) throws IOException { + flushIfNotAvailable(MAX_VARINT_SIZE + 1); + bufferTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + buffer((byte) (value ? 1 : 0)); + } + + @Override + public void writeString(final int fieldNumber, final String value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeStringNoTag(value); + } + + @Override + public void writeBytes(final int fieldNumber, final ByteString value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeBytesNoTag(value); + } + + @Override + public void writeByteArray(final int fieldNumber, final byte[] value) throws IOException { + writeByteArray(fieldNumber, value, 0, value.length); + } + + @Override + 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); + } + + @Override + public void writeByteBuffer(final int fieldNumber, final ByteBuffer value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeUInt32NoTag(value.capacity()); + writeRawBytes(value); + } + + @Override + public void writeBytesNoTag(final ByteString value) throws IOException { + writeUInt32NoTag(value.size()); + value.writeTo(this); + } + + @Override + public void writeByteArrayNoTag(final byte[] value, int offset, int length) throws IOException { + writeUInt32NoTag(length); + write(value, offset, length); + } + + @Override + public void writeRawBytes(final ByteBuffer value) throws IOException { + if (value.hasArray()) { + write(value.array(), value.arrayOffset(), value.capacity()); + } else { + ByteBuffer duplicated = value.duplicate(); + duplicated.clear(); + write(duplicated); + } + } + + @Override + public void writeMessage(final int fieldNumber, final MessageLite value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeMessageNoTag(value); + } + + @Override + public void writeMessageSetExtension(final int fieldNumber, final MessageLite value) + throws IOException { + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_START_GROUP); + writeUInt32(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber); + writeMessage(WireFormat.MESSAGE_SET_MESSAGE, value); + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_END_GROUP); + } + + @Override + public void writeRawMessageSetExtension(final int fieldNumber, final ByteString value) + throws IOException { + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_START_GROUP); + writeUInt32(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber); + writeBytes(WireFormat.MESSAGE_SET_MESSAGE, value); + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_END_GROUP); + } + + @Override + public void writeMessageNoTag(final MessageLite value) throws IOException { + writeUInt32NoTag(value.getSerializedSize()); + value.writeTo(this); + } + + @Override + public void write(byte value) throws IOException { + if (position == limit) { + doFlush(); + } + + buffer(value); + } + + @Override + public void writeInt32NoTag(int value) throws IOException { + if (value >= 0) { + writeUInt32NoTag(value); + } else { + // Must sign-extend. + writeUInt64NoTag(value); + } + } + + @Override + public void writeUInt32NoTag(int value) throws IOException { + flushIfNotAvailable(MAX_VARINT_SIZE); + bufferUInt32NoTag(value); + } + + @Override + public void writeFixed32NoTag(final int value) throws IOException { + flushIfNotAvailable(FIXED_32_SIZE); + bufferFixed32NoTag(value); + } + + @Override + public void writeUInt64NoTag(long value) throws IOException { + flushIfNotAvailable(MAX_VARINT_SIZE); + bufferUInt64NoTag(value); + } + + @Override + public void writeFixed64NoTag(final long value) throws IOException { + flushIfNotAvailable(FIXED_64_SIZE); + bufferFixed64NoTag(value); + } + + @Override + public void writeStringNoTag(String value) throws IOException { + // UTF-8 byte length of the string is at least its UTF-16 code unit length (value.length()), + // and at most 3 times of it. We take advantage of this in both branches below. + final int maxLength = value.length() * Utf8.MAX_BYTES_PER_CHAR; + final int maxLengthVarIntSize = computeUInt32SizeNoTag(maxLength); + + // If we are streaming and the potential length is too big to fit in our buffer, we take the + // slower path. + if (maxLengthVarIntSize + maxLength > limit) { + // Allocate a byte[] that we know can fit the string and encode into it. String.getBytes() + // does the same internally and then does *another copy* to return a byte[] of exactly the + // right size. We can skip that copy and just writeRawBytes up to the actualLength of the + // UTF-8 encoded bytes. + final byte[] encodedBytes = new byte[maxLength]; + int actualLength = Utf8.encode(value, encodedBytes, 0, maxLength); + writeUInt32NoTag(actualLength); + writeLazy(encodedBytes, 0, actualLength); + return; + } + + // Fast path: we have enough space available in our buffer for the string... + if (maxLengthVarIntSize + maxLength > limit - position) { + // Flush to free up space. + doFlush(); + } + + final int oldPosition = position; + try { + // Optimize for the case where we know this length results in a constant varint length as + // this saves a pass for measuring the length of the string. + final int minLengthVarIntSize = computeUInt32SizeNoTag(value.length()); + + if (minLengthVarIntSize == maxLengthVarIntSize) { + position = oldPosition + minLengthVarIntSize; + int newPosition = Utf8.encode(value, buffer, position, limit - position); + // Since this class is stateful and tracks the position, we rewind and store the state, + // prepend the length, then reset it back to the end of the string. + position = oldPosition; + int length = newPosition - oldPosition - minLengthVarIntSize; + bufferUInt32NoTag(length); + position = newPosition; + totalBytesWritten += length; + } else { + int length = Utf8.encodedLength(value); + bufferUInt32NoTag(length); + position = Utf8.encode(value, buffer, position, length); + totalBytesWritten += length; + } + } catch (UnpairedSurrogateException e) { + // Roll back the change and convert to an IOException. + totalBytesWritten -= position - oldPosition; + position = oldPosition; + + // TODO(nathanmittler): We should throw an IOException here instead. + inefficientWriteStringNoTag(value, e); + } catch (IndexOutOfBoundsException e) { + throw new OutOfSpaceException(e); + } + } + + @Override + public void flush() throws IOException { + if (position > 0) { + // Flush the buffer. + doFlush(); + } + } + + @Override + public void write(byte[] value, int offset, int length) throws IOException { + flush(); + out.write(value, offset, length); + totalBytesWritten += length; + } + + @Override + public void writeLazy(byte[] value, int offset, int length) throws IOException { + flush(); + out.writeLazy(value, offset, length); + totalBytesWritten += length; + } + + @Override + public void write(ByteBuffer value) throws IOException { + flush(); + int length = value.remaining(); + out.write(value); + totalBytesWritten += length; + } + + @Override + public void writeLazy(ByteBuffer value) throws IOException { + flush(); + int length = value.remaining(); + out.writeLazy(value); + totalBytesWritten += length; + } + + private void flushIfNotAvailable(int requiredSize) throws IOException { + if (limit - position < requiredSize) { + doFlush(); + } + } + + private void doFlush() throws IOException { + out.write(buffer, 0, position); + position = 0; + } + } + + /** + * An {@link CodedOutputStream} that decorates an {@link OutputStream}. It performs internal + * buffering to optimize writes to the {@link OutputStream}. + */ + private static final class OutputStreamEncoder extends AbstractBufferedEncoder { + private final OutputStream out; + + OutputStreamEncoder(OutputStream out, int bufferSize) { + super(bufferSize); + if (out == null) { + throw new NullPointerException("out"); + } + this.out = out; + } + + @Override + public void writeTag(final int fieldNumber, final int wireType) throws IOException { + writeUInt32NoTag(WireFormat.makeTag(fieldNumber, wireType)); + } + + @Override + public void writeInt32(final int fieldNumber, final int value) throws IOException { + flushIfNotAvailable(MAX_VARINT_SIZE * 2); + bufferTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + bufferInt32NoTag(value); + } + + @Override + public void writeUInt32(final int fieldNumber, final int value) throws IOException { + flushIfNotAvailable(MAX_VARINT_SIZE * 2); + bufferTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + bufferUInt32NoTag(value); + } + + @Override + public void writeFixed32(final int fieldNumber, final int value) throws IOException { + flushIfNotAvailable(MAX_VARINT_SIZE + FIXED_32_SIZE); + bufferTag(fieldNumber, WireFormat.WIRETYPE_FIXED32); + bufferFixed32NoTag(value); + } + + @Override + public void writeUInt64(final int fieldNumber, final long value) throws IOException { + flushIfNotAvailable(MAX_VARINT_SIZE * 2); + bufferTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + bufferUInt64NoTag(value); + } + + @Override + public void writeFixed64(final int fieldNumber, final long value) throws IOException { + flushIfNotAvailable(MAX_VARINT_SIZE + FIXED_64_SIZE); + bufferTag(fieldNumber, WireFormat.WIRETYPE_FIXED64); + bufferFixed64NoTag(value); + } + + @Override + public void writeBool(final int fieldNumber, final boolean value) throws IOException { + flushIfNotAvailable(MAX_VARINT_SIZE + 1); + bufferTag(fieldNumber, WireFormat.WIRETYPE_VARINT); + buffer((byte) (value ? 1 : 0)); + } + + @Override + public void writeString(final int fieldNumber, final String value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeStringNoTag(value); + } + + @Override + public void writeBytes(final int fieldNumber, final ByteString value) throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeBytesNoTag(value); + } + + @Override + public void writeByteArray(final int fieldNumber, final byte[] value) throws IOException { + writeByteArray(fieldNumber, value, 0, value.length); + } + + @Override + 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); + } + + @Override + public void writeByteBuffer(final int fieldNumber, final ByteBuffer value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeUInt32NoTag(value.capacity()); + writeRawBytes(value); + } + + @Override + public void writeBytesNoTag(final ByteString value) throws IOException { + writeUInt32NoTag(value.size()); + value.writeTo(this); + } + + @Override + public void writeByteArrayNoTag(final byte[] value, int offset, int length) throws IOException { + writeUInt32NoTag(length); + write(value, offset, length); + } + + @Override + public void writeRawBytes(final ByteBuffer value) throws IOException { + if (value.hasArray()) { + write(value.array(), value.arrayOffset(), value.capacity()); + } else { + ByteBuffer duplicated = value.duplicate(); + duplicated.clear(); + write(duplicated); + } + } + + @Override + public void writeMessage(final int fieldNumber, final MessageLite value) + throws IOException { + writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + writeMessageNoTag(value); + } + + @Override + public void writeMessageSetExtension(final int fieldNumber, final MessageLite value) + throws IOException { + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_START_GROUP); + writeUInt32(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber); + writeMessage(WireFormat.MESSAGE_SET_MESSAGE, value); + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_END_GROUP); + } + + @Override + public void writeRawMessageSetExtension(final int fieldNumber, final ByteString value) + throws IOException { + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_START_GROUP); + writeUInt32(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber); + writeBytes(WireFormat.MESSAGE_SET_MESSAGE, value); + writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_END_GROUP); + } + + @Override + public void writeMessageNoTag(final MessageLite value) throws IOException { + writeUInt32NoTag(value.getSerializedSize()); + value.writeTo(this); + } + + @Override + public void write(byte value) throws IOException { + if (position == limit) { + doFlush(); + } + + buffer(value); + } + + @Override + public void writeInt32NoTag(int value) throws IOException { + if (value >= 0) { + writeUInt32NoTag(value); + } else { + // Must sign-extend. + writeUInt64NoTag(value); + } + } + + @Override + public void writeUInt32NoTag(int value) throws IOException { + flushIfNotAvailable(MAX_VARINT_SIZE); + bufferUInt32NoTag(value); + } + + @Override + public void writeFixed32NoTag(final int value) throws IOException { + flushIfNotAvailable(FIXED_32_SIZE); + bufferFixed32NoTag(value); + } + + @Override + public void writeUInt64NoTag(long value) throws IOException { + flushIfNotAvailable(MAX_VARINT_SIZE); + bufferUInt64NoTag(value); + } + + @Override + public void writeFixed64NoTag(final long value) throws IOException { + flushIfNotAvailable(FIXED_64_SIZE); + bufferFixed64NoTag(value); + } + + @Override + public void writeStringNoTag(String value) throws IOException { + try { + // UTF-8 byte length of the string is at least its UTF-16 code unit length (value.length()), + // and at most 3 times of it. We take advantage of this in both branches below. + final int maxLength = value.length() * Utf8.MAX_BYTES_PER_CHAR; + final int maxLengthVarIntSize = computeUInt32SizeNoTag(maxLength); + + // If we are streaming and the potential length is too big to fit in our buffer, we take the + // slower path. + if (maxLengthVarIntSize + maxLength > limit) { + // Allocate a byte[] that we know can fit the string and encode into it. String.getBytes() + // does the same internally and then does *another copy* to return a byte[] of exactly the + // right size. We can skip that copy and just writeRawBytes up to the actualLength of the + // UTF-8 encoded bytes. + final byte[] encodedBytes = new byte[maxLength]; + int actualLength = Utf8.encode(value, encodedBytes, 0, maxLength); + writeUInt32NoTag(actualLength); + writeLazy(encodedBytes, 0, actualLength); + return; + } + + // Fast path: we have enough space available in our buffer for the string... + if (maxLengthVarIntSize + maxLength > limit - position) { + // Flush to free up space. + doFlush(); + } + + // Optimize for the case where we know this length results in a constant varint length as + // this saves a pass for measuring the length of the string. + final int minLengthVarIntSize = computeUInt32SizeNoTag(value.length()); + int oldPosition = position; + final int length; + try { + if (minLengthVarIntSize == maxLengthVarIntSize) { + position = oldPosition + minLengthVarIntSize; + int newPosition = Utf8.encode(value, buffer, position, limit - position); + // Since this class is stateful and tracks the position, we rewind and store the + // state, prepend the length, then reset it back to the end of the string. + position = oldPosition; + length = newPosition - oldPosition - minLengthVarIntSize; + bufferUInt32NoTag(length); + position = newPosition; + } else { + length = Utf8.encodedLength(value); + bufferUInt32NoTag(length); + position = Utf8.encode(value, buffer, position, length); + } + totalBytesWritten += length; + } catch (UnpairedSurrogateException e) { + // Be extra careful and restore the original position for retrying the write with the + // less efficient path. + totalBytesWritten -= position - oldPosition; + position = oldPosition; + throw e; + } catch (ArrayIndexOutOfBoundsException e) { + throw new OutOfSpaceException(e); + } + } catch (UnpairedSurrogateException e) { + inefficientWriteStringNoTag(value, e); + } + } + + @Override + public void flush() throws IOException { + if (position > 0) { + // Flush the buffer. + doFlush(); + } + } + + @Override + public void write(byte[] value, int offset, int length) + throws IOException { + if (limit - position >= length) { + // We have room in the current buffer. + System.arraycopy(value, offset, buffer, position, length); + position += length; + totalBytesWritten += length; + } else { + // Write extends past current buffer. Fill the rest of this buffer and + // flush. + final int bytesWritten = limit - position; + System.arraycopy(value, offset, buffer, position, bytesWritten); + offset += bytesWritten; + length -= bytesWritten; + position = limit; + totalBytesWritten += bytesWritten; + doFlush(); + + // Now deal with the rest. + // Since we have an output stream, this is our buffer + // and buffer offset == 0 + if (length <= limit) { + // Fits in new buffer. + System.arraycopy(value, offset, buffer, 0, length); + position = length; + } else { + // Write is very big. Let's do it all at once. + out.write(value, offset, length); + } + totalBytesWritten += length; + } + } + + @Override + public void writeLazy(byte[] value, int offset, int length) throws IOException { + write(value, offset, length); + } + + @Override + public void write(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; + doFlush(); + + // 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. + value.get(buffer, 0, limit); + out.write(buffer, 0, limit); + length -= limit; + totalBytesWritten += limit; + } + value.get(buffer, 0, length); + position = length; + totalBytesWritten += length; + } + } + + @Override + public void writeLazy(ByteBuffer value) throws IOException { + write(value); + } + + private void flushIfNotAvailable(int requiredSize) throws IOException { + if (limit - position < requiredSize) { + doFlush(); + } + } + + private void doFlush() throws IOException { + out.write(buffer, 0, position); + position = 0; + } + } + + /** + * Gets the {@code sun.misc.Unsafe} instance, or {@code null} if not available on this + * platform. + */ + private static sun.misc.Unsafe getUnsafe() { + sun.misc.Unsafe unsafe = null; + try { + unsafe = AccessController.doPrivileged(new PrivilegedExceptionAction<sun.misc.Unsafe>() { + @Override + public sun.misc.Unsafe run() throws Exception { + Class<sun.misc.Unsafe> k = sun.misc.Unsafe.class; + + for (Field f : k.getDeclaredFields()) { + f.setAccessible(true); + Object x = f.get(null); + if (k.isInstance(x)) { + return k.cast(x); + } + } + // The sun.misc.Unsafe field does not exist. + return null; + } + }); + } catch (Throwable e) { + // Catching Throwable here due to the fact that Google AppEngine raises NoClassDefFoundError + // for Unsafe. + } + + logger.log(Level.FINEST, "sun.misc.Unsafe: {}", + unsafe != null ? "available" : "unavailable"); + return unsafe; + } + + /** + * Indicates whether or not unsafe array operations are supported on this platform. + */ + // TODO(nathanmittler): Add support for Android's MemoryBlock. + private static boolean supportsUnsafeArrayOperations() { + boolean supported = false; + if (UNSAFE != null) { + try { + UNSAFE.getClass().getMethod("arrayBaseOffset", Class.class); + UNSAFE.getClass().getMethod("putByte", Object.class, long.class, byte.class); + supported = true; + } catch (Throwable e) { + // Do nothing. + } + } + logger.log(Level.FINEST, "Unsafe array operations: {}", + supported ? "available" : "unavailable"); + return supported; + } + + /** + * Get the base offset for byte arrays, or {@code -1} if {@code sun.misc.Unsafe} is not + * available. + */ + private static <T> int byteArrayBaseOffset() { + return HAS_UNSAFE_ARRAY_OPERATIONS ? UNSAFE.arrayBaseOffset(byte[].class) : -1; + } +} diff --git a/java/src/main/java/com/google/protobuf/Descriptors.java b/java/core/src/main/java/com/google/protobuf/Descriptors.java index 7cfc47f7..e00ea342 100644 --- a/java/src/main/java/com/google/protobuf/Descriptors.java +++ b/java/core/src/main/java/com/google/protobuf/Descriptors.java @@ -74,16 +74,28 @@ public final class Descriptors { */ public static final class FileDescriptor extends GenericDescriptor { /** Convert the descriptor to its protocol message representation. */ - public FileDescriptorProto toProto() { return proto; } + @Override + public FileDescriptorProto toProto() { + return proto; + } /** Get the file name. */ - public String getName() { return proto.getName(); } + @Override + public String getName() { + return proto.getName(); + } /** Returns this object. */ - public FileDescriptor getFile() { return this; } + @Override + public FileDescriptor getFile() { + return this; + } /** Returns the same as getName(). */ - public String getFullName() { return proto.getName(); } + @Override + public String getFullName() { + return proto.getName(); + } /** * Get the proto package name. This is the package name given by the @@ -272,7 +284,7 @@ public final class Descriptors { * because a field has an undefined type or because two messages * were defined with the same name. */ - private static FileDescriptor buildFrom( + public static FileDescriptor buildFrom( final FileDescriptorProto proto, final FileDescriptor[] dependencies, final boolean allowUnknownDependencies) throws DescriptorValidationException { @@ -582,10 +594,16 @@ public final class Descriptors { public int getIndex() { return index; } /** Convert the descriptor to its protocol message representation. */ - public DescriptorProto toProto() { return proto; } + @Override + public DescriptorProto toProto() { + return proto; + } /** Get the type's unqualified name. */ - public String getName() { return proto.getName(); } + @Override + public String getName() { + return proto.getName(); + } /** * Get the type's fully-qualified name, within the proto language's @@ -598,10 +616,16 @@ public final class Descriptors { * </pre> * {@code Baz}'s full name is "foo.bar.Baz". */ - public String getFullName() { return fullName; } + @Override + public String getFullName() { + return fullName; + } /** Get the {@link FileDescriptor} containing this descriptor. */ - public FileDescriptor getFile() { return file; } + @Override + public FileDescriptor getFile() { + return file; + } /** If this is a nested type, get the outer descriptor, otherwise null. */ public Descriptor getContainingType() { return containingType; } @@ -875,19 +899,36 @@ public final class Descriptors { public int getIndex() { return index; } /** Convert the descriptor to its protocol message representation. */ - public FieldDescriptorProto toProto() { return proto; } + @Override + public FieldDescriptorProto toProto() { + return proto; + } /** Get the field's unqualified name. */ - public String getName() { return proto.getName(); } + @Override + public String getName() { + return proto.getName(); + } /** Get the field's number. */ - public int getNumber() { return proto.getNumber(); } + @Override + public int getNumber() { + return proto.getNumber(); + } /** * Get the field's fully-qualified name. * @see Descriptors.Descriptor#getFullName() */ - public String getFullName() { return fullName; } + @Override + public String getFullName() { + return fullName; + } + + /** Get the JSON name of this field. */ + public String getJsonName() { + return jsonName; + } /** * Get the field's java type. This is just for convenience. Every @@ -896,17 +937,22 @@ public final class Descriptors { public JavaType getJavaType() { return type.getJavaType(); } /** For internal use only. */ + @Override public WireFormat.JavaType getLiteJavaType() { return getLiteType().getJavaType(); } /** Get the {@code FileDescriptor} containing this descriptor. */ - public FileDescriptor getFile() { return file; } + @Override + public FileDescriptor getFile() { + return file; + } /** Get the field's declared type. */ public Type getType() { return type; } /** For internal use only. */ + @Override public WireFormat.FieldType getLiteType() { return table[type.ordinal()]; } @@ -948,6 +994,7 @@ public final class Descriptors { } /** Is this field declared repeated? */ + @Override public boolean isRepeated() { return proto.getLabel() == FieldDescriptorProto.Label.LABEL_REPEATED; } @@ -955,6 +1002,7 @@ public final class Descriptors { /** Does this field have the {@code [packed = true]} option or is this field * packable in proto3 and not explicitly setted to unpacked? */ + @Override public boolean isPacked() { if (!isPackable()) { return false; @@ -1043,6 +1091,7 @@ public final class Descriptors { } /** For enum fields, gets the field's type. */ + @Override public EnumDescriptor getEnumType() { if (getJavaType() != JavaType.ENUM) { throw new UnsupportedOperationException( @@ -1061,6 +1110,7 @@ public final class Descriptors { * @return negative, zero, or positive if {@code this} is less than, * equal to, or greater than {@code other}, respectively. */ + @Override public int compareTo(final FieldDescriptor other) { if (other.containingType != containingType) { throw new IllegalArgumentException( @@ -1079,6 +1129,7 @@ public final class Descriptors { private FieldDescriptorProto proto; private final String fullName; + private final String jsonName; private final FileDescriptor file; private final Descriptor extensionScope; @@ -1117,7 +1168,7 @@ public final class Descriptors { private JavaType javaType; public FieldDescriptorProto.Type toProto() { - return FieldDescriptorProto.Type.valueOf(ordinal() + 1); + return FieldDescriptorProto.Type.forNumber(ordinal() + 1); } public JavaType getJavaType() { return javaType; } @@ -1157,6 +1208,38 @@ public final class Descriptors { private final Object defaultDefault; } + // TODO(xiaofeng): Implement it consistently across different languages. See b/24751348. + private static String fieldNameToLowerCamelCase(String name) { + StringBuilder result = new StringBuilder(name.length()); + boolean isNextUpperCase = false; + for (int i = 0; i < name.length(); i++) { + Character ch = name.charAt(i); + if (Character.isLowerCase(ch)) { + if (isNextUpperCase) { + result.append(Character.toUpperCase(ch)); + } else { + result.append(ch); + } + isNextUpperCase = false; + } else if (Character.isUpperCase(ch)) { + if (i == 0) { + // Force first letter to lower-case. + result.append(Character.toLowerCase(ch)); + } else { + // Capital letters after the first are left as-is. + result.append(ch); + } + isNextUpperCase = false; + } else if (Character.isDigit(ch)) { + result.append(ch); + isNextUpperCase = false; + } else { + isNextUpperCase = true; + } + } + return result.toString(); + } + private FieldDescriptor(final FieldDescriptorProto proto, final FileDescriptor file, final Descriptor parent, @@ -1167,6 +1250,11 @@ public final class Descriptors { this.proto = proto; fullName = computeFullName(file, parent, proto.getName()); this.file = file; + if (proto.hasJsonName()) { + jsonName = proto.getJsonName(); + } else { + jsonName = fieldNameToLowerCamelCase(proto.getName()); + } if (proto.hasType()) { type = Type.valueOf(proto.getType()); @@ -1423,8 +1511,8 @@ public final class Descriptors { * For internal use only. This is to satisfy the FieldDescriptorLite * interface. */ - public MessageLite.Builder internalMergeFrom( - MessageLite.Builder to, MessageLite from) { + @Override + public MessageLite.Builder internalMergeFrom(MessageLite.Builder to, MessageLite from) { // FieldDescriptors are only used with non-lite messages so we can just // down-cast and call mergeFrom directly. return ((Message.Builder) to).mergeFrom((Message) from); @@ -1444,19 +1532,31 @@ public final class Descriptors { public int getIndex() { return index; } /** Convert the descriptor to its protocol message representation. */ - public EnumDescriptorProto toProto() { return proto; } + @Override + public EnumDescriptorProto toProto() { + return proto; + } /** Get the type's unqualified name. */ - public String getName() { return proto.getName(); } + @Override + public String getName() { + return proto.getName(); + } /** * Get the type's fully-qualified name. * @see Descriptors.Descriptor#getFullName() */ - public String getFullName() { return fullName; } + @Override + public String getFullName() { + return fullName; + } /** Get the {@link FileDescriptor} containing this descriptor. */ - public FileDescriptor getFile() { return file; } + @Override + public FileDescriptor getFile() { + return file; + } /** If this is a nested type, get the outer descriptor, otherwise null. */ public Descriptor getContainingType() { return containingType; } @@ -1490,6 +1590,7 @@ public final class Descriptors { * @param number The value's number. * @return the value's descriptor, or {@code null} if not found. */ + @Override public EnumValueDescriptor findValueByNumber(final int number) { return file.pool.enumValuesByNumber.get( new DescriptorPool.DescriptorIntPair(this, number)); @@ -1616,13 +1717,22 @@ public final class Descriptors { public int getIndex() { return index; } /** Convert the descriptor to its protocol message representation. */ - public EnumValueDescriptorProto toProto() { return proto; } + @Override + public EnumValueDescriptorProto toProto() { + return proto; + } /** Get the value's unqualified name. */ - public String getName() { return proto.getName(); } + @Override + public String getName() { + return proto.getName(); + } /** Get the value's number. */ - public int getNumber() { return proto.getNumber(); } + @Override + public int getNumber() { + return proto.getNumber(); + } @Override public String toString() { return proto.getName(); } @@ -1631,10 +1741,16 @@ public final class Descriptors { * Get the value's fully-qualified name. * @see Descriptors.Descriptor#getFullName() */ - public String getFullName() { return fullName; } + @Override + public String getFullName() { + return fullName; + } /** Get the {@link FileDescriptor} containing this descriptor. */ - public FileDescriptor getFile() { return file; } + @Override + public FileDescriptor getFile() { + return file; + } /** Get the value's enum type. */ public EnumDescriptor getType() { return type; } @@ -1702,19 +1818,31 @@ public final class Descriptors { public int getIndex() { return index; } /** Convert the descriptor to its protocol message representation. */ - public ServiceDescriptorProto toProto() { return proto; } + @Override + public ServiceDescriptorProto toProto() { + return proto; + } /** Get the type's unqualified name. */ - public String getName() { return proto.getName(); } + @Override + public String getName() { + return proto.getName(); + } /** * Get the type's fully-qualified name. * @see Descriptors.Descriptor#getFullName() */ - public String getFullName() { return fullName; } + @Override + public String getFullName() { + return fullName; + } /** Get the {@link FileDescriptor} containing this descriptor. */ - public FileDescriptor getFile() { return file; } + @Override + public FileDescriptor getFile() { + return file; + } /** Get the {@code ServiceOptions}, defined in {@code descriptor.proto}. */ public ServiceOptions getOptions() { return proto.getOptions(); } @@ -1792,19 +1920,31 @@ public final class Descriptors { public int getIndex() { return index; } /** Convert the descriptor to its protocol message representation. */ - public MethodDescriptorProto toProto() { return proto; } + @Override + public MethodDescriptorProto toProto() { + return proto; + } /** Get the method's unqualified name. */ - public String getName() { return proto.getName(); } + @Override + public String getName() { + return proto.getName(); + } /** * Get the method's fully-qualified name. * @see Descriptors.Descriptor#getFullName() */ - public String getFullName() { return fullName; } + @Override + public String getFullName() { + return fullName; + } /** Get the {@link FileDescriptor} containing this descriptor. */ - public FileDescriptor getFile() { return file; } + @Override + public FileDescriptor getFile() { + return file; + } /** Get the method's service type. */ public ServiceDescriptor getService() { return service; } @@ -2205,10 +2345,22 @@ public final class Descriptors { * that has the same name as an existing package. */ private static final class PackageDescriptor extends GenericDescriptor { - public Message toProto() { return file.toProto(); } - public String getName() { return name; } - public String getFullName() { return fullName; } - public FileDescriptor getFile() { return file; } + @Override + public Message toProto() { + return file.toProto(); + } + @Override + public String getName() { + return name; + } + @Override + public String getFullName() { + return fullName; + } + @Override + public FileDescriptor getFile() { + return file; + } PackageDescriptor(final String name, final String fullName, final FileDescriptor file) { diff --git a/java/src/main/java/com/google/protobuf/DoubleArrayList.java b/java/core/src/main/java/com/google/protobuf/DoubleArrayList.java index 90ebe109..a9543b83 100644 --- a/java/src/main/java/com/google/protobuf/DoubleArrayList.java +++ b/java/core/src/main/java/com/google/protobuf/DoubleArrayList.java @@ -34,7 +34,6 @@ import com.google.protobuf.Internal.DoubleList; import java.util.Arrays; import java.util.Collection; -import java.util.List; import java.util.RandomAccess; /** @@ -45,8 +44,6 @@ import java.util.RandomAccess; final class DoubleArrayList extends AbstractProtobufList<Double> implements DoubleList, RandomAccess { - private static final int DEFAULT_CAPACITY = 10; - private static final DoubleArrayList EMPTY_LIST = new DoubleArrayList(); static { EMPTY_LIST.makeImmutable(); @@ -68,28 +65,59 @@ final class DoubleArrayList private int size; /** - * Constructs a new mutable {@code DoubleArrayList}. + * Constructs a new mutable {@code DoubleArrayList} with default capacity. */ DoubleArrayList() { - array = new double[DEFAULT_CAPACITY]; - size = 0; + this(new double[DEFAULT_CAPACITY], 0); } /** * Constructs a new mutable {@code DoubleArrayList} containing the same elements as {@code other}. */ - DoubleArrayList(List<Double> other) { - if (other instanceof DoubleArrayList) { - DoubleArrayList list = (DoubleArrayList) other; - array = list.array.clone(); - size = list.size; - } else { - size = other.size(); - array = new double[size]; - for (int i = 0; i < size; i++) { - array[i] = other.get(i); + private DoubleArrayList(double[] array, int size) { + this.array = array; + this.size = size; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof DoubleArrayList)) { + return super.equals(o); + } + DoubleArrayList other = (DoubleArrayList) o; + if (size != other.size) { + return false; + } + + final double[] arr = other.array; + for (int i = 0; i < size; i++) { + if (array[i] != arr[i]) { + return false; } } + + return true; + } + + @Override + public int hashCode() { + int result = 1; + for (int i = 0; i < size; i++) { + long bits = Double.doubleToLongBits(array[i]); + result = (31 * result) + Internal.hashLong(bits); + } + return result; + } + + @Override + public DoubleList mutableCopyWithCapacity(int capacity) { + if (capacity < size) { + throw new IllegalArgumentException(); + } + return new DoubleArrayList(Arrays.copyOf(array, capacity), size); } @Override diff --git a/java/src/main/java/com/google/protobuf/DynamicMessage.java b/java/core/src/main/java/com/google/protobuf/DynamicMessage.java index 3ea1b688..859a9e8f 100644 --- a/java/src/main/java/com/google/protobuf/DynamicMessage.java +++ b/java/core/src/main/java/com/google/protobuf/DynamicMessage.java @@ -156,18 +156,22 @@ public final class DynamicMessage extends AbstractMessage { // ----------------------------------------------------------------- // Implementation of Message interface. + @Override public Descriptor getDescriptorForType() { return type; } + @Override public DynamicMessage getDefaultInstanceForType() { return getDefaultInstance(type); } + @Override public Map<FieldDescriptor, Object> getAllFields() { return fields.getAllFields(); } + @Override public boolean hasOneof(OneofDescriptor oneof) { verifyOneofContainingType(oneof); FieldDescriptor field = oneofCases[oneof.getIndex()]; @@ -177,16 +181,19 @@ public final class DynamicMessage extends AbstractMessage { return true; } + @Override public FieldDescriptor getOneofFieldDescriptor(OneofDescriptor oneof) { verifyOneofContainingType(oneof); return oneofCases[oneof.getIndex()]; } + @Override public boolean hasField(FieldDescriptor field) { verifyContainingType(field); return fields.hasField(field); } + @Override public Object getField(FieldDescriptor field) { verifyContainingType(field); Object result = fields.getField(field); @@ -202,16 +209,19 @@ public final class DynamicMessage extends AbstractMessage { return result; } + @Override public int getRepeatedFieldCount(FieldDescriptor field) { verifyContainingType(field); return fields.getRepeatedFieldCount(field); } + @Override public Object getRepeatedField(FieldDescriptor field, int index) { verifyContainingType(field); return fields.getRepeatedField(field, index); } + @Override public UnknownFieldSet getUnknownFields() { return unknownFields; } @@ -264,19 +274,22 @@ public final class DynamicMessage extends AbstractMessage { return size; } + @Override public Builder newBuilderForType() { return new Builder(type); } + @Override public Builder toBuilder() { return newBuilderForType().mergeFrom(this); } + @Override public Parser<DynamicMessage> getParserForType() { return new AbstractParser<DynamicMessage>() { + @Override public DynamicMessage parsePartialFrom( - CodedInputStream input, - ExtensionRegistryLite extensionRegistry) + CodedInputStream input, ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { Builder builder = newBuilder(type); try { @@ -370,6 +383,7 @@ public final class DynamicMessage extends AbstractMessage { } } + @Override public DynamicMessage build() { if (!isInitialized()) { throw newUninitializedMessageException( @@ -394,6 +408,7 @@ public final class DynamicMessage extends AbstractMessage { return buildPartial(); } + @Override public DynamicMessage buildPartial() { fields.makeImmutable(); DynamicMessage result = @@ -411,22 +426,27 @@ public final class DynamicMessage extends AbstractMessage { return result; } + @Override public boolean isInitialized() { return DynamicMessage.isInitialized(type, fields); } + @Override public Descriptor getDescriptorForType() { return type; } + @Override public DynamicMessage getDefaultInstanceForType() { return getDefaultInstance(type); } + @Override public Map<FieldDescriptor, Object> getAllFields() { return fields.getAllFields(); } + @Override public Builder newBuilderForField(FieldDescriptor field) { verifyContainingType(field); @@ -438,6 +458,7 @@ public final class DynamicMessage extends AbstractMessage { return new Builder(field.getMessageType()); } + @Override public boolean hasOneof(OneofDescriptor oneof) { verifyOneofContainingType(oneof); FieldDescriptor field = oneofCases[oneof.getIndex()]; @@ -447,11 +468,13 @@ public final class DynamicMessage extends AbstractMessage { return true; } + @Override public FieldDescriptor getOneofFieldDescriptor(OneofDescriptor oneof) { verifyOneofContainingType(oneof); return oneofCases[oneof.getIndex()]; } + @Override public Builder clearOneof(OneofDescriptor oneof) { verifyOneofContainingType(oneof); FieldDescriptor field = oneofCases[oneof.getIndex()]; @@ -461,11 +484,13 @@ public final class DynamicMessage extends AbstractMessage { return this; } + @Override public boolean hasField(FieldDescriptor field) { verifyContainingType(field); return fields.hasField(field); } + @Override public Object getField(FieldDescriptor field) { verifyContainingType(field); Object result = fields.getField(field); @@ -481,6 +506,7 @@ public final class DynamicMessage extends AbstractMessage { return result; } + @Override public Builder setField(FieldDescriptor field, Object value) { verifyContainingType(field); ensureIsMutable(); @@ -505,6 +531,7 @@ public final class DynamicMessage extends AbstractMessage { return this; } + @Override public Builder clearField(FieldDescriptor field) { verifyContainingType(field); ensureIsMutable(); @@ -519,24 +546,27 @@ public final class DynamicMessage extends AbstractMessage { return this; } + @Override public int getRepeatedFieldCount(FieldDescriptor field) { verifyContainingType(field); return fields.getRepeatedFieldCount(field); } + @Override public Object getRepeatedField(FieldDescriptor field, int index) { verifyContainingType(field); return fields.getRepeatedField(field, index); } - public Builder setRepeatedField(FieldDescriptor field, - int index, Object value) { + @Override + public Builder setRepeatedField(FieldDescriptor field, int index, Object value) { verifyContainingType(field); ensureIsMutable(); fields.setRepeatedField(field, index, value); return this; } + @Override public Builder addRepeatedField(FieldDescriptor field, Object value) { verifyContainingType(field); ensureIsMutable(); @@ -544,10 +574,12 @@ public final class DynamicMessage extends AbstractMessage { return this; } + @Override public UnknownFieldSet getUnknownFields() { return unknownFields; } + @Override public Builder setUnknownFields(UnknownFieldSet unknownFields) { if (getDescriptorForType().getFile().getSyntax() == Descriptors.FileDescriptor.Syntax.PROTO3) { diff --git a/java/core/src/main/java/com/google/protobuf/ExperimentalApi.java b/java/core/src/main/java/com/google/protobuf/ExperimentalApi.java new file mode 100644 index 00000000..3cd4c884 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/ExperimentalApi.java @@ -0,0 +1,66 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// 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.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates a public API that can change at any time, and has no guarantee of API stability and + * backward-compatibility. + * + * <p>Usage guidelines: + * <ol> + * <li>This annotation is used only on public API. Internal interfaces should not use it.</li> + * <li>This annotation should only be added to new APIs. Adding it to an existing API is + * considered API-breaking.</li> + * <li>Removing this annotation from an API gives it stable status.</li> + * </ol> + */ +@Retention(RetentionPolicy.SOURCE) +@Target({ + ElementType.ANNOTATION_TYPE, + ElementType.CONSTRUCTOR, + ElementType.FIELD, + ElementType.METHOD, + ElementType.PACKAGE, + ElementType.TYPE}) +@Documented +public @interface ExperimentalApi { + /** + * Context information such as links to discussion thread, tracking issue etc. + */ + String value() default ""; +} + diff --git a/java/src/main/java/com/google/protobuf/Extension.java b/java/core/src/main/java/com/google/protobuf/Extension.java index 68d29f33..08ec5b45 100644 --- a/java/src/main/java/com/google/protobuf/Extension.java +++ b/java/core/src/main/java/com/google/protobuf/Extension.java @@ -42,6 +42,7 @@ public abstract class Extension<ContainingType extends MessageLite, Type> public abstract Descriptors.FieldDescriptor getDescriptor(); /** Returns whether or not this extension is a Lite Extension. */ + @Override final boolean isLite() { return false; } diff --git a/java/src/main/java/com/google/protobuf/ExtensionLite.java b/java/core/src/main/java/com/google/protobuf/ExtensionLite.java index f8f5bd2c..f8f5bd2c 100644 --- a/java/src/main/java/com/google/protobuf/ExtensionLite.java +++ b/java/core/src/main/java/com/google/protobuf/ExtensionLite.java diff --git a/java/src/main/java/com/google/protobuf/ExtensionRegistry.java b/java/core/src/main/java/com/google/protobuf/ExtensionRegistry.java index 0067392f..0067392f 100644 --- a/java/src/main/java/com/google/protobuf/ExtensionRegistry.java +++ b/java/core/src/main/java/com/google/protobuf/ExtensionRegistry.java diff --git a/java/src/main/java/com/google/protobuf/ExtensionRegistryLite.java b/java/core/src/main/java/com/google/protobuf/ExtensionRegistryLite.java index 65cf7385..65cf7385 100644 --- a/java/src/main/java/com/google/protobuf/ExtensionRegistryLite.java +++ b/java/core/src/main/java/com/google/protobuf/ExtensionRegistryLite.java diff --git a/java/src/main/java/com/google/protobuf/FieldSet.java b/java/core/src/main/java/com/google/protobuf/FieldSet.java index 47924b65..4e89709f 100644 --- a/java/src/main/java/com/google/protobuf/FieldSet.java +++ b/java/core/src/main/java/com/google/protobuf/FieldSet.java @@ -120,6 +120,25 @@ final class FieldSet<FieldDescriptorType extends public boolean isImmutable() { return isImmutable; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof FieldSet)) { + return false; + } + + FieldSet<?> other = (FieldSet<?>) o; + return other.fields.equals(other.fields); + } + + @Override + public int hashCode() { + return fields.hashCode(); + } /** * Clones the FieldSet. The returned FieldSet will be mutable even if the diff --git a/java/src/main/java/com/google/protobuf/FloatArrayList.java b/java/core/src/main/java/com/google/protobuf/FloatArrayList.java index 293eaff6..63cb6d77 100644 --- a/java/src/main/java/com/google/protobuf/FloatArrayList.java +++ b/java/core/src/main/java/com/google/protobuf/FloatArrayList.java @@ -34,7 +34,6 @@ import com.google.protobuf.Internal.FloatList; import java.util.Arrays; import java.util.Collection; -import java.util.List; import java.util.RandomAccess; /** @@ -44,8 +43,6 @@ import java.util.RandomAccess; */ final class FloatArrayList extends AbstractProtobufList<Float> implements FloatList, RandomAccess { - private static final int DEFAULT_CAPACITY = 10; - private static final FloatArrayList EMPTY_LIST = new FloatArrayList(); static { EMPTY_LIST.makeImmutable(); @@ -67,28 +64,58 @@ final class FloatArrayList extends AbstractProtobufList<Float> implements FloatL private int size; /** - * Constructs a new mutable {@code FloatArrayList}. + * Constructs a new mutable {@code FloatArrayList} with default capacity. */ FloatArrayList() { - array = new float[DEFAULT_CAPACITY]; - size = 0; + this(new float[DEFAULT_CAPACITY], 0); } /** * Constructs a new mutable {@code FloatArrayList} containing the same elements as {@code other}. */ - FloatArrayList(List<Float> other) { - if (other instanceof FloatArrayList) { - FloatArrayList list = (FloatArrayList) other; - array = list.array.clone(); - size = list.size; - } else { - size = other.size(); - array = new float[size]; - for (int i = 0; i < size; i++) { - array[i] = other.get(i); + private FloatArrayList(float[] array, int size) { + this.array = array; + this.size = size; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof FloatArrayList)) { + return super.equals(o); + } + FloatArrayList other = (FloatArrayList) o; + if (size != other.size) { + return false; + } + + final float[] arr = other.array; + for (int i = 0; i < size; i++) { + if (array[i] != arr[i]) { + return false; } } + + return true; + } + + @Override + public int hashCode() { + int result = 1; + for (int i = 0; i < size; i++) { + result = (31 * result) + Float.floatToIntBits(array[i]); + } + return result; + } + + @Override + public FloatList mutableCopyWithCapacity(int capacity) { + if (capacity < size) { + throw new IllegalArgumentException(); + } + return new FloatArrayList(Arrays.copyOf(array, capacity), size); } @Override diff --git a/java/src/main/java/com/google/protobuf/GeneratedMessage.java b/java/core/src/main/java/com/google/protobuf/GeneratedMessage.java index d84fa75c..790cb622 100644 --- a/java/src/main/java/com/google/protobuf/GeneratedMessage.java +++ b/java/core/src/main/java/com/google/protobuf/GeneratedMessage.java @@ -36,15 +36,13 @@ import com.google.protobuf.Descriptors.EnumValueDescriptor; import com.google.protobuf.Descriptors.FieldDescriptor; import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.Descriptors.OneofDescriptor; -import com.google.protobuf.GeneratedMessageLite.ExtendableMessage; -import com.google.protobuf.GeneratedMessageLite.GeneratedExtension; import java.io.IOException; +import java.io.InputStream; import java.io.ObjectStreamException; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; @@ -82,6 +80,7 @@ public abstract class GeneratedMessage extends AbstractMessage unknownFields = builder.getUnknownFields(); } + @Override public Parser<? extends GeneratedMessage> getParserForType() { throw new UnsupportedOperationException( "This is supposed to be overridden by subclasses."); @@ -104,7 +103,7 @@ public abstract class GeneratedMessage extends AbstractMessage */ protected abstract FieldAccessorTable internalGetFieldAccessorTable(); - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public Descriptor getDescriptorForType() { return internalGetFieldAccessorTable().descriptor; } @@ -193,7 +192,7 @@ public abstract class GeneratedMessage extends AbstractMessage return true; } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public Map<FieldDescriptor, Object> getAllFields() { return Collections.unmodifiableMap( getAllFieldsMutable(/* getBytesForString = */ false)); @@ -214,22 +213,22 @@ public abstract class GeneratedMessage extends AbstractMessage getAllFieldsMutable(/* getBytesForString = */ true)); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public boolean hasOneof(final OneofDescriptor oneof) { return internalGetFieldAccessorTable().getOneof(oneof).has(this); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public FieldDescriptor getOneofFieldDescriptor(final OneofDescriptor oneof) { return internalGetFieldAccessorTable().getOneof(oneof).get(this); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public boolean hasField(final FieldDescriptor field) { return internalGetFieldAccessorTable().getField(field).has(this); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public Object getField(final FieldDescriptor field) { return internalGetFieldAccessorTable().getField(field).get(this); } @@ -246,19 +245,19 @@ public abstract class GeneratedMessage extends AbstractMessage return internalGetFieldAccessorTable().getField(field).getRaw(this); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public int getRepeatedFieldCount(final FieldDescriptor field) { return internalGetFieldAccessorTable().getField(field) .getRepeatedCount(this); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public Object getRepeatedField(final FieldDescriptor field, final int index) { return internalGetFieldAccessorTable().getField(field) .getRepeated(this, index); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public UnknownFieldSet getUnknownFields() { throw new UnsupportedOperationException( "This is supposed to be overridden by subclasses."); @@ -276,6 +275,60 @@ public abstract class GeneratedMessage extends AbstractMessage return unknownFields.mergeFieldFrom(tag, input); } + protected static <M extends Message> M parseWithIOException(Parser<M> parser, InputStream input) + throws IOException { + try { + return parser.parseFrom(input); + } catch (InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } + } + + protected static <M extends Message> M parseWithIOException(Parser<M> parser, InputStream input, + ExtensionRegistryLite extensions) throws IOException { + try { + return parser.parseFrom(input, extensions); + } catch (InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } + } + + protected static <M extends Message> M parseWithIOException(Parser<M> parser, + CodedInputStream input) throws IOException { + try { + return parser.parseFrom(input); + } catch (InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } + } + + protected static <M extends Message> M parseWithIOException(Parser<M> parser, + CodedInputStream input, ExtensionRegistryLite extensions) throws IOException { + try { + return parser.parseFrom(input, extensions); + } catch (InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } + } + + protected static <M extends Message> M parseDelimitedWithIOException(Parser<M> parser, + InputStream input) throws IOException { + try { + return parser.parseDelimitedFrom(input); + } catch (InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } + } + + protected static <M extends Message> M parseDelimitedWithIOException(Parser<M> parser, + InputStream input, ExtensionRegistryLite extensions) throws IOException { + try { + return parser.parseDelimitedFrom(input, extensions); + } catch (InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } + } + @Override public void writeTo(final CodedOutputStream output) throws IOException { MessageReflection.writeMessageTo(this, getAllFieldsRaw(), output, false); @@ -328,7 +381,7 @@ public abstract class GeneratedMessage extends AbstractMessage } @SuppressWarnings("unchecked") - public abstract static class Builder <BuilderType extends Builder> + public abstract static class Builder <BuilderType extends Builder<BuilderType>> extends AbstractMessage.Builder<BuilderType> { private BuilderParent builderParent; @@ -392,6 +445,7 @@ public abstract class GeneratedMessage extends AbstractMessage * Called by the initialization and clear code paths to allow subclasses to * reset any of their builtin fields back to the initial values. */ + @Override public BuilderType clear() { unknownFields = UnknownFieldSet.getDefaultInstance(); onChanged(); @@ -405,12 +459,12 @@ public abstract class GeneratedMessage extends AbstractMessage */ protected abstract FieldAccessorTable internalGetFieldAccessorTable(); - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public Descriptor getDescriptorForType() { return internalGetFieldAccessorTable().descriptor; } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public Map<FieldDescriptor, Object> getAllFields() { return Collections.unmodifiableMap(getAllFieldsMutable()); } @@ -458,39 +512,38 @@ public abstract class GeneratedMessage extends AbstractMessage return result; } - public Message.Builder newBuilderForField( - final FieldDescriptor field) { + @Override + public Message.Builder newBuilderForField(final FieldDescriptor field) { return internalGetFieldAccessorTable().getField(field).newBuilder(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public Message.Builder getFieldBuilder(final FieldDescriptor field) { return internalGetFieldAccessorTable().getField(field).getBuilder(this); } - //@Override (Java 1.6 override semantics, but we must support 1.5) - public Message.Builder getRepeatedFieldBuilder(final FieldDescriptor field, - int index) { + @Override + public Message.Builder getRepeatedFieldBuilder(final FieldDescriptor field, int index) { return internalGetFieldAccessorTable().getField(field).getRepeatedBuilder( this, index); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public boolean hasOneof(final OneofDescriptor oneof) { return internalGetFieldAccessorTable().getOneof(oneof).has(this); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public FieldDescriptor getOneofFieldDescriptor(final OneofDescriptor oneof) { return internalGetFieldAccessorTable().getOneof(oneof).get(this); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public boolean hasField(final FieldDescriptor field) { return internalGetFieldAccessorTable().getField(field).has(this); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public Object getField(final FieldDescriptor field) { Object object = internalGetFieldAccessorTable().getField(field).get(this); if (field.isRepeated()) { @@ -502,52 +555,52 @@ public abstract class GeneratedMessage extends AbstractMessage } } - public BuilderType setField(final FieldDescriptor field, - final Object value) { + @Override + public BuilderType setField(final FieldDescriptor field, final Object value) { internalGetFieldAccessorTable().getField(field).set(this, value); return (BuilderType) this; } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public BuilderType clearField(final FieldDescriptor field) { internalGetFieldAccessorTable().getField(field).clear(this); return (BuilderType) this; } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override 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) + @Override public int getRepeatedFieldCount(final FieldDescriptor field) { return internalGetFieldAccessorTable().getField(field) .getRepeatedCount(this); } - //@Override (Java 1.6 override semantics, but we must support 1.5) - public Object getRepeatedField(final FieldDescriptor field, - final int index) { + @Override + public Object getRepeatedField(final FieldDescriptor field, final int index) { return internalGetFieldAccessorTable().getField(field) .getRepeated(this, index); } - public BuilderType setRepeatedField(final FieldDescriptor field, - final int index, final Object value) { + @Override + public BuilderType setRepeatedField( + final FieldDescriptor field, final int index, final Object value) { internalGetFieldAccessorTable().getField(field) .setRepeated(this, index, value); return (BuilderType) this; } - public BuilderType addRepeatedField(final FieldDescriptor field, - final Object value) { + @Override + public BuilderType addRepeatedField(final FieldDescriptor field, final Object value) { internalGetFieldAccessorTable().getField(field).addRepeated(this, value); return (BuilderType) this; } - public BuilderType setUnknownFields( - final UnknownFieldSet unknownFields) { + @Override + public BuilderType setUnknownFields(final UnknownFieldSet unknownFields) { this.unknownFields = unknownFields; onChanged(); return (BuilderType) this; @@ -564,7 +617,7 @@ public abstract class GeneratedMessage extends AbstractMessage return (BuilderType) this; } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public boolean isInitialized() { for (final FieldDescriptor field : getDescriptorForType().getFields()) { // Check that all required fields are present. @@ -594,7 +647,7 @@ public abstract class GeneratedMessage extends AbstractMessage return true; } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public final UnknownFieldSet getUnknownFields() { return unknownFields; } @@ -618,7 +671,7 @@ public abstract class GeneratedMessage extends AbstractMessage */ private class BuilderParentImpl implements BuilderParent { - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public void markDirty() { onChanged(); } @@ -667,7 +720,7 @@ public abstract class GeneratedMessage extends AbstractMessage "No map fields found in " + getClass().getName()); } - /** Like {@link internalGetMapField} but return a mutable version. */ + /** Like {@link #internalGetMapField} but return a mutable version. */ @SuppressWarnings({"unused", "rawtypes"}) protected MapField internalGetMutableMapField(int fieldNumber) { // Note that we can't use descriptor names here because this method will @@ -683,6 +736,7 @@ public abstract class GeneratedMessage extends AbstractMessage public interface ExtendableMessageOrBuilder< MessageType extends ExtendableMessage> extends MessageOrBuilder { // Re-define for return type covariance. + @Override Message getDefaultInstanceForType(); /** Check if a singular extension is present. */ @@ -743,6 +797,8 @@ public abstract class GeneratedMessage extends AbstractMessage extends GeneratedMessage implements ExtendableMessageOrBuilder<MessageType> { + private static final long serialVersionUID = 1L; + private final FieldSet<FieldDescriptor> extensions; protected ExtendableMessage() { @@ -769,9 +825,8 @@ 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 <Type> boolean hasExtension( - final ExtensionLite<MessageType, Type> extensionLite) { + @Override + public final <Type> boolean hasExtension(final ExtensionLite<MessageType, Type> extensionLite) { Extension<MessageType, Type> extension = checkNotLite(extensionLite); verifyExtensionContainingType(extension); @@ -779,7 +834,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) + @Override public final <Type> int getExtensionCount( final ExtensionLite<MessageType, List<Type>> extensionLite) { Extension<MessageType, List<Type>> extension = checkNotLite(extensionLite); @@ -790,10 +845,9 @@ public abstract class GeneratedMessage extends AbstractMessage } /** Get the value of an extension. */ - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override @SuppressWarnings("unchecked") - public final <Type> Type getExtension( - final ExtensionLite<MessageType, Type> extensionLite) { + public final <Type> Type getExtension(final ExtensionLite<MessageType, Type> extensionLite) { Extension<MessageType, Type> extension = checkNotLite(extensionLite); verifyExtensionContainingType(extension); @@ -815,11 +869,10 @@ 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) + @Override @SuppressWarnings("unchecked") public final <Type> Type getExtension( - final ExtensionLite<MessageType, List<Type>> extensionLite, - final int index) { + final ExtensionLite<MessageType, List<Type>> extensionLite, final int index) { Extension<MessageType, List<Type>> extension = checkNotLite(extensionLite); verifyExtensionContainingType(extension); @@ -967,7 +1020,9 @@ public abstract class GeneratedMessage extends AbstractMessage verifyContainingType(field); final Object value = extensions.getField(field); if (value == null) { - if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + if (field.isRepeated()) { + return Collections.emptyList(); + } else if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { // Lacking an ExtensionRegistry, we have no way to determine the // extension's real type, so we return a DynamicMessage. return DynamicMessage.getDefaultInstance(field.getMessageType()); @@ -1051,7 +1106,7 @@ public abstract class GeneratedMessage extends AbstractMessage @SuppressWarnings("unchecked") public abstract static class ExtendableBuilder< MessageType extends ExtendableMessage, - BuilderType extends ExtendableBuilder> + BuilderType extends ExtendableBuilder<MessageType, BuilderType>> extends Builder<BuilderType> implements ExtendableMessageOrBuilder<MessageType> { @@ -1103,9 +1158,8 @@ 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 <Type> boolean hasExtension( - final ExtensionLite<MessageType, Type> extensionLite) { + @Override + public final <Type> boolean hasExtension(final ExtensionLite<MessageType, Type> extensionLite) { Extension<MessageType, Type> extension = checkNotLite(extensionLite); verifyExtensionContainingType(extension); @@ -1113,7 +1167,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) + @Override public final <Type> int getExtensionCount( final ExtensionLite<MessageType, List<Type>> extensionLite) { Extension<MessageType, List<Type>> extension = checkNotLite(extensionLite); @@ -1124,9 +1178,8 @@ 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> Type getExtension( - final ExtensionLite<MessageType, Type> extensionLite) { + @Override + public final <Type> Type getExtension(final ExtensionLite<MessageType, Type> extensionLite) { Extension<MessageType, Type> extension = checkNotLite(extensionLite); verifyExtensionContainingType(extension); @@ -1148,10 +1201,9 @@ 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) + @Override public final <Type> Type getExtension( - final ExtensionLite<MessageType, List<Type>> extensionLite, - final int index) { + final ExtensionLite<MessageType, List<Type>> extensionLite, final int index) { Extension<MessageType, List<Type>> extension = checkNotLite(extensionLite); verifyExtensionContainingType(extension); @@ -1404,10 +1456,9 @@ public abstract class GeneratedMessage extends AbstractMessage // obtained. return new GeneratedExtension<ContainingType, Type>( new CachedDescriptorRetriever() { - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public FieldDescriptor loadDescriptor() { - return scope.getDescriptorForType().getExtensions() - .get(descriptorIndex); + return scope.getDescriptorForType().getExtensions().get(descriptorIndex); } }, singularType, @@ -1435,6 +1486,7 @@ public abstract class GeneratedMessage extends AbstractMessage private volatile FieldDescriptor descriptor; protected abstract FieldDescriptor loadDescriptor(); + @Override public FieldDescriptor getDescriptor() { if (descriptor == null) { synchronized (this) { @@ -1464,6 +1516,7 @@ public abstract class GeneratedMessage extends AbstractMessage // obtained. return new GeneratedExtension<ContainingType, Type>( new CachedDescriptorRetriever() { + @Override protected FieldDescriptor loadDescriptor() { return scope.getDescriptorForType().findFieldByName(name); } @@ -1490,17 +1543,18 @@ public abstract class GeneratedMessage extends AbstractMessage // used to obtain the extension's FieldDescriptor. return new GeneratedExtension<ContainingType, Type>( new CachedDescriptorRetriever() { + @Override protected FieldDescriptor loadDescriptor() { try { - Class clazz = - singularType.getClassLoader().loadClass(descriptorOuterClass); - FileDescriptor file = - (FileDescriptor) clazz.getField("descriptor").get(null); + 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); + "Cannot load descriptors: " + + descriptorOuterClass + + " is not a valid descriptor class name", + e); } } }, @@ -1582,12 +1636,13 @@ public abstract class GeneratedMessage extends AbstractMessage if (descriptorRetriever != null) { throw new IllegalStateException("Already initialized."); } - descriptorRetriever = new ExtensionDescriptorRetriever() { - //@Override (Java 1.6 override semantics, but we must support 1.5) - public FieldDescriptor getDescriptor() { - return descriptor; - } - }; + descriptorRetriever = + new ExtensionDescriptorRetriever() { + @Override + public FieldDescriptor getDescriptor() { + return descriptor; + } + }; } private ExtensionDescriptorRetriever descriptorRetriever; @@ -1597,6 +1652,7 @@ public abstract class GeneratedMessage extends AbstractMessage private final Method enumGetValueDescriptor; private final ExtensionType extensionType; + @Override public FieldDescriptor getDescriptor() { if (descriptorRetriever == null) { throw new IllegalStateException( @@ -1609,10 +1665,12 @@ public abstract class GeneratedMessage extends AbstractMessage * If the extension is an embedded message or group, returns the default * instance of the message. */ + @Override public Message getMessageDefaultInstance() { return messageDefaultInstance; } + @Override protected ExtensionType getExtensionType() { return extensionType; } @@ -1623,7 +1681,7 @@ public abstract class GeneratedMessage extends AbstractMessage * EnumValueDescriptors but the native accessors use the generated enum * type. */ - // @Override + @Override @SuppressWarnings("unchecked") protected Object fromReflectionType(final Object value) { FieldDescriptor descriptor = getDescriptor(); @@ -1648,7 +1706,7 @@ public abstract class GeneratedMessage extends AbstractMessage * Like {@link #fromReflectionType(Object)}, but if the type is a repeated * type, this converts a single element. */ - // @Override + @Override protected Object singularFromReflectionType(final Object value) { FieldDescriptor descriptor = getDescriptor(); switch (descriptor.getJavaType()) { @@ -1672,7 +1730,7 @@ public abstract class GeneratedMessage extends AbstractMessage * EnumValueDescriptors but the native accessors use the generated enum * type. */ - // @Override + @Override @SuppressWarnings("unchecked") protected Object toReflectionType(final Object value) { FieldDescriptor descriptor = getDescriptor(); @@ -1696,7 +1754,7 @@ public abstract class GeneratedMessage extends AbstractMessage * Like {@link #toReflectionType(Object)}, but if the type is a repeated * type, this converts a single element. */ - // @Override + @Override protected Object singularToReflectionType(final Object value) { FieldDescriptor descriptor = getDescriptor(); switch (descriptor.getJavaType()) { @@ -1707,22 +1765,22 @@ public abstract class GeneratedMessage extends AbstractMessage } } - // @Override + @Override public int getNumber() { return getDescriptor().getNumber(); } - // @Override + @Override public WireFormat.FieldType getLiteType() { return getDescriptor().getLiteType(); } - // @Override + @Override public boolean isRepeated() { return getDescriptor().isRepeated(); } - // @Override + @Override @SuppressWarnings("unchecked") public Type getDefaultValue() { if (isRepeated()) { @@ -2072,49 +2130,57 @@ public abstract class GeneratedMessage extends AbstractMessage return ((Internal.EnumLite) invokeOrDie(caseMethodBuilder, builder)).getNumber(); } + @Override public Object get(final GeneratedMessage message) { return invokeOrDie(getMethod, message); } + @Override public Object get(GeneratedMessage.Builder builder) { return invokeOrDie(getMethodBuilder, builder); } + @Override public Object getRaw(final GeneratedMessage message) { return get(message); } + @Override public Object getRaw(GeneratedMessage.Builder builder) { return get(builder); } + @Override public void set(final Builder builder, final Object value) { invokeOrDie(setMethod, builder, value); } - public Object getRepeated(final GeneratedMessage message, - final int index) { + @Override + public Object getRepeated(final GeneratedMessage message, final int index) { throw new UnsupportedOperationException( "getRepeatedField() called on a singular field."); } - public Object getRepeatedRaw(final GeneratedMessage message, - final int index) { + @Override + public Object getRepeatedRaw(final GeneratedMessage message, final int index) { throw new UnsupportedOperationException( "getRepeatedFieldRaw() called on a singular field."); } + @Override public Object getRepeated(GeneratedMessage.Builder builder, int index) { throw new UnsupportedOperationException( "getRepeatedField() called on a singular field."); } - public Object getRepeatedRaw(GeneratedMessage.Builder builder, - int index) { + @Override + public Object getRepeatedRaw(GeneratedMessage.Builder builder, int index) { throw new UnsupportedOperationException( "getRepeatedFieldRaw() called on a singular field."); } - public void setRepeated(final Builder builder, final int index, - final Object value) { + @Override + public void setRepeated(final Builder builder, final int index, final Object value) { throw new UnsupportedOperationException( "setRepeatedField() called on a singular field."); } + @Override public void addRepeated(final Builder builder, final Object value) { throw new UnsupportedOperationException( "addRepeatedField() called on a singular field."); } + @Override public boolean has(final GeneratedMessage message) { if (!hasHasMethod) { if (isOneofField) { @@ -2124,6 +2190,7 @@ public abstract class GeneratedMessage extends AbstractMessage } return (Boolean) invokeOrDie(hasMethod, message); } + @Override public boolean has(GeneratedMessage.Builder builder) { if (!hasHasMethod) { if (isOneofField) { @@ -2133,27 +2200,32 @@ public abstract class GeneratedMessage extends AbstractMessage } return (Boolean) invokeOrDie(hasMethodBuilder, builder); } + @Override public int getRepeatedCount(final GeneratedMessage message) { throw new UnsupportedOperationException( "getRepeatedFieldSize() called on a singular field."); } + @Override public int getRepeatedCount(GeneratedMessage.Builder builder) { throw new UnsupportedOperationException( "getRepeatedFieldSize() called on a singular field."); } + @Override public void clear(final Builder builder) { invokeOrDie(clearMethod, builder); } + @Override public Message.Builder newBuilder() { throw new UnsupportedOperationException( "newBuilderForField() called on a non-Message type."); } + @Override public Message.Builder getBuilder(GeneratedMessage.Builder builder) { throw new UnsupportedOperationException( "getFieldBuilder() called on a non-Message type."); } - public Message.Builder getRepeatedBuilder(GeneratedMessage.Builder builder, - int index) { + @Override + public Message.Builder getRepeatedBuilder(GeneratedMessage.Builder builder, int index) { throw new UnsupportedOperationException( "getRepeatedFieldBuilder() called on a non-Message type."); } @@ -2197,18 +2269,23 @@ public abstract class GeneratedMessage extends AbstractMessage clearMethod = getMethodOrDie(builderClass, "clear" + camelCaseName); } + @Override public Object get(final GeneratedMessage message) { return invokeOrDie(getMethod, message); } + @Override public Object get(GeneratedMessage.Builder builder) { return invokeOrDie(getMethodBuilder, builder); } + @Override public Object getRaw(final GeneratedMessage message) { return get(message); } + @Override public Object getRaw(GeneratedMessage.Builder builder) { return get(builder); } + @Override public void set(final Builder builder, final Object value) { // Add all the elements individually. This serves two purposes: // 1) Verifies that each element has the correct type. @@ -2219,54 +2296,64 @@ public abstract class GeneratedMessage extends AbstractMessage addRepeated(builder, element); } } - public Object getRepeated(final GeneratedMessage message, - final int index) { + @Override + public Object getRepeated(final GeneratedMessage message, final int index) { return invokeOrDie(getRepeatedMethod, message, index); } + @Override public Object getRepeated(GeneratedMessage.Builder builder, int index) { return invokeOrDie(getRepeatedMethodBuilder, builder, index); } + @Override public Object getRepeatedRaw(GeneratedMessage message, int index) { return getRepeated(message, index); } - public Object getRepeatedRaw(GeneratedMessage.Builder builder, - int index) { + @Override + public Object getRepeatedRaw(GeneratedMessage.Builder builder, int index) { return getRepeated(builder, index); } - public void setRepeated(final Builder builder, - final int index, final Object value) { + @Override + public void setRepeated(final Builder builder, final int index, final Object value) { invokeOrDie(setRepeatedMethod, builder, index, value); } + @Override public void addRepeated(final Builder builder, final Object value) { invokeOrDie(addRepeatedMethod, builder, value); } + @Override public boolean has(final GeneratedMessage message) { throw new UnsupportedOperationException( "hasField() called on a repeated field."); } + @Override public boolean has(GeneratedMessage.Builder builder) { throw new UnsupportedOperationException( "hasField() called on a repeated field."); } + @Override public int getRepeatedCount(final GeneratedMessage message) { return (Integer) invokeOrDie(getCountMethod, message); } + @Override public int getRepeatedCount(GeneratedMessage.Builder builder) { return (Integer) invokeOrDie(getCountMethodBuilder, builder); } + @Override public void clear(final Builder builder) { invokeOrDie(clearMethod, builder); } + @Override public Message.Builder newBuilder() { throw new UnsupportedOperationException( "newBuilderForField() called on a non-Message type."); } + @Override public Message.Builder getBuilder(GeneratedMessage.Builder builder) { throw new UnsupportedOperationException( "getFieldBuilder() called on a non-Message type."); } - public Message.Builder getRepeatedBuilder(GeneratedMessage.Builder builder, - int index) { + @Override + public Message.Builder getRepeatedBuilder(GeneratedMessage.Builder builder, int index) { throw new UnsupportedOperationException( "getRepeatedFieldBuilder() called on a non-Message type."); } @@ -2303,6 +2390,7 @@ public abstract class GeneratedMessage extends AbstractMessage field.getNumber()); } + @Override public Object get(GeneratedMessage message) { List result = new ArrayList(); for (int i = 0; i < getRepeatedCount(message); i++) { @@ -2311,6 +2399,7 @@ public abstract class GeneratedMessage extends AbstractMessage return Collections.unmodifiableList(result); } + @Override public Object get(Builder builder) { List result = new ArrayList(); for (int i = 0; i < getRepeatedCount(builder); i++) { @@ -2319,14 +2408,17 @@ public abstract class GeneratedMessage extends AbstractMessage return Collections.unmodifiableList(result); } + @Override public Object getRaw(GeneratedMessage message) { return get(message); } + @Override public Object getRaw(GeneratedMessage.Builder builder) { return get(builder); } + @Override public void set(Builder builder, Object value) { clear(builder); for (Object entry : (List) value) { @@ -2334,63 +2426,76 @@ public abstract class GeneratedMessage extends AbstractMessage } } + @Override public Object getRepeated(GeneratedMessage message, int index) { return getMapField(message).getList().get(index); } + @Override public Object getRepeated(Builder builder, int index) { return getMapField(builder).getList().get(index); } + @Override public Object getRepeatedRaw(GeneratedMessage message, int index) { return getRepeated(message, index); } + @Override public Object getRepeatedRaw(Builder builder, int index) { return getRepeated(builder, index); } + @Override public void setRepeated(Builder builder, int index, Object value) { getMutableMapField(builder).getMutableList().set(index, (Message) value); } + @Override public void addRepeated(Builder builder, Object value) { getMutableMapField(builder).getMutableList().add((Message) value); } + @Override public boolean has(GeneratedMessage message) { throw new UnsupportedOperationException( "hasField() is not supported for repeated fields."); } + @Override public boolean has(Builder builder) { throw new UnsupportedOperationException( "hasField() is not supported for repeated fields."); } + @Override public int getRepeatedCount(GeneratedMessage message) { return getMapField(message).getList().size(); } + @Override public int getRepeatedCount(Builder builder) { return getMapField(builder).getList().size(); } + @Override public void clear(Builder builder) { getMutableMapField(builder).getMutableList().clear(); } + @Override public com.google.protobuf.Message.Builder newBuilder() { return mapEntryMessageDefaultInstance.newBuilderForType(); } + @Override public com.google.protobuf.Message.Builder getBuilder(Builder builder) { throw new UnsupportedOperationException( "Nested builder not supported for map fields."); } - public com.google.protobuf.Message.Builder getRepeatedBuilder( - Builder builder, int index) { + @Override + public com.google.protobuf.Message.Builder getRepeatedBuilder(Builder builder, int index) { throw new UnsupportedOperationException( "Nested builder not supported for map fields."); } diff --git a/java/src/main/java/com/google/protobuf/GeneratedMessageLite.java b/java/core/src/main/java/com/google/protobuf/GeneratedMessageLite.java index 4316efee..c5adc5ad 100644 --- a/java/src/main/java/com/google/protobuf/GeneratedMessageLite.java +++ b/java/core/src/main/java/com/google/protobuf/GeneratedMessageLite.java @@ -30,6 +30,8 @@ package com.google.protobuf; +import com.google.protobuf.AbstractMessageLite.Builder.LimitedInputStream; +import com.google.protobuf.GeneratedMessageLite.EqualsVisitor.NotEqualsException; import com.google.protobuf.Internal.BooleanList; import com.google.protobuf.Internal.DoubleList; import com.google.protobuf.Internal.FloatList; @@ -39,6 +41,7 @@ import com.google.protobuf.Internal.ProtobufList; import com.google.protobuf.WireFormat.FieldType; import java.io.IOException; +import java.io.InputStream; import java.io.ObjectStreamException; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; @@ -57,32 +60,108 @@ import java.util.Map; public abstract class GeneratedMessageLite< MessageType extends GeneratedMessageLite<MessageType, BuilderType>, BuilderType extends GeneratedMessageLite.Builder<MessageType, BuilderType>> - extends AbstractMessageLite - implements Serializable { - - private static final long serialVersionUID = 1L; + extends AbstractMessageLite<MessageType, BuilderType> { /** For use by generated code only. Lazily initialized to reduce allocations. */ - protected UnknownFieldSetLite unknownFields = null; + protected UnknownFieldSetLite unknownFields = UnknownFieldSetLite.getDefaultInstance(); /** For use by generated code only. */ protected int memoizedSerializedSize = -1; + @Override @SuppressWarnings("unchecked") // Guaranteed by runtime. public final Parser<MessageType> getParserForType() { return (Parser<MessageType>) dynamicMethod(MethodToInvoke.GET_PARSER); } + @Override @SuppressWarnings("unchecked") // Guaranteed by runtime. public final MessageType getDefaultInstanceForType() { return (MessageType) dynamicMethod(MethodToInvoke.GET_DEFAULT_INSTANCE); } + @Override @SuppressWarnings("unchecked") // Guaranteed by runtime. public final BuilderType newBuilderForType() { return (BuilderType) dynamicMethod(MethodToInvoke.NEW_BUILDER); } + /** + * A reflective toString function. This is primarily intended as a developer aid, while keeping + * binary size down. The first line of the {@code toString()} representation includes a commented + * version of {@code super.toString()} to act as an indicator that this should not be relied on + * for comparisons. + * <p> + * NOTE: This method relies on the field getter methods not being stripped or renamed by proguard. + * If they are, the fields will not be included in the returned string representation. + * <p> + * NOTE: This implementation is liable to change in the future, and should not be relied on in + * code. + */ + @Override + public String toString() { + return MessageLiteToString.toString(this, super.toString()); + } + + @SuppressWarnings("unchecked") // Guaranteed by runtime + @Override + public int hashCode() { + if (memoizedHashCode == 0) { + HashCodeVisitor visitor = new HashCodeVisitor(); + visit(visitor, (MessageType) this); + memoizedHashCode = visitor.hashCode; + } + return memoizedHashCode; + } + + @SuppressWarnings("unchecked") // Guaranteed by runtime + int hashCode(HashCodeVisitor visitor) { + if (memoizedHashCode == 0) { + int inProgressHashCode = visitor.hashCode; + visitor.hashCode = 0; + visit(visitor, (MessageType) this); + memoizedHashCode = visitor.hashCode; + visitor.hashCode = inProgressHashCode; + } + return memoizedHashCode; + } + + @SuppressWarnings("unchecked") // Guaranteed by isInstance + runtime + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (!getDefaultInstanceForType().getClass().isInstance(other)) { + return false; + } + + try { + visit(EqualsVisitor.INSTANCE, (MessageType) other); + } catch (NotEqualsException e) { + return false; + } + return true; + } + + /** + * Same as {@link #equals(Object)} but throws {@code NotEqualsException}. + */ + @SuppressWarnings("unchecked") // Guaranteed by isInstance + runtime + boolean equals(EqualsVisitor visitor, MessageLite other) { + if (this == other) { + return true; + } + + if (!getDefaultInstanceForType().getClass().isInstance(other)) { + return false; + } + + visit(visitor, (MessageType) other); + return true; + } + // The general strategy for unknown fields is to use an UnknownFieldSetLite that is treated as // mutable during the parsing constructor and immutable after. This allows us to avoid // any unnecessary intermediary allocations while reducing the generated code size. @@ -91,7 +170,7 @@ public abstract class GeneratedMessageLite< * Lazily initializes unknown fields. */ private final void ensureUnknownFieldsInitialized() { - if (unknownFields == null) { + if (unknownFields == UnknownFieldSetLite.getDefaultInstance()) { unknownFields = UnknownFieldSetLite.newInstance(); } } @@ -102,6 +181,11 @@ public abstract class GeneratedMessageLite< * @return {@code true} unless the tag is an end-group tag. */ protected boolean parseUnknownField(int tag, CodedInputStream input) throws IOException { + // This will avoid the allocation of unknown fields when a group tag is encountered. + if (WireFormat.getTagWireType(tag) == WireFormat.WIRETYPE_END_GROUP) { + return false; + } + ensureUnknownFieldsInitialized(); return unknownFields.mergeFieldFrom(tag, input); } @@ -125,18 +209,18 @@ public abstract class GeneratedMessageLite< /** * Called by subclasses to complete parsing. For use by generated code only. */ - protected void doneParsing() { - if (unknownFields == null) { - unknownFields = UnknownFieldSetLite.getDefaultInstance(); - } else { - unknownFields.makeImmutable(); - } + protected void makeImmutable() { + dynamicMethod(MethodToInvoke.MAKE_IMMUTABLE); + + unknownFields.makeImmutable(); } + @Override public final boolean isInitialized() { return dynamicMethod(MethodToInvoke.IS_INITIALIZED, Boolean.TRUE) != null; } + @Override public final BuilderType toBuilder() { BuilderType builder = (BuilderType) dynamicMethod(MethodToInvoke.NEW_BUILDER); builder.mergeFrom((MessageType) this); @@ -150,11 +234,14 @@ public abstract class GeneratedMessageLite< * For use by generated code only. */ public static enum MethodToInvoke { + // Rely on/modify instance state IS_INITIALIZED, - PARSE_PARTIAL_FROM, - MERGE_FROM, + VISIT, + MERGE_FROM_STREAM, MAKE_IMMUTABLE, - NEW_INSTANCE, + + // Rely on static state + NEW_MUTABLE_INSTANCE, NEW_BUILDER, GET_DEFAULT_INSTANCE, GET_PARSER; @@ -166,17 +253,18 @@ public abstract class GeneratedMessageLite< * builders in the runtime. This method bundles those operations to reduce the generated methods * count. * <ul> - * <li>{@code PARSE_PARTIAL_FROM} is parameterized with an {@link CodedInputStream} and + * <li>{@code MERGE_FROM_STREAM} is parameterized with an {@link CodedInputStream} and * {@link ExtensionRegistryLite}. It consumes the input stream, parsing the contents into the * returned protocol buffer. If parsing throws an {@link InvalidProtocolBufferException}, the - * implementation wraps it in a RuntimeException - * <li>{@code NEW_INSTANCE} returns a new instance of the protocol buffer + * implementation wraps it in a RuntimeException. + * <li>{@code NEW_INSTANCE} returns a new instance of the protocol buffer that has not yet been + * made immutable. See {@code MAKE_IMMUTABLE}. * <li>{@code IS_INITIALIZED} is parameterized with a {@code Boolean} detailing whether to * memoize. It returns {@code null} for false and the default instance for true. We optionally * memoize to support the Builder case, where memoization is not desired. * <li>{@code NEW_BUILDER} returns a {@code BuilderType} instance. - * <li>{@code MERGE_FROM} is parameterized with a {@code MessageType} and merges the fields from - * that instance into this instance. + * <li>{@code VISIT} is parameterized with a {@code Visitor} and a {@code MessageType} and + * recursively iterates through the fields side by side between this and the instance. * <li>{@code MAKE_IMMUTABLE} sets all internal fields to an immutable state. * </ul> * This method, plus the implementation of the Builder, enables the Builder class to be proguarded @@ -200,6 +288,11 @@ public abstract class GeneratedMessageLite< return dynamicMethod(method, null, null); } + void visit(Visitor visitor, MessageType other) { + dynamicMethod(MethodToInvoke.VISIT, visitor, other); + unknownFields = visitor.visitUnknownFields(unknownFields, other.unknownFields); + } + /** * Merge some unknown fields into the {@link UnknownFieldSetLite} for this * message. @@ -214,7 +307,7 @@ public abstract class GeneratedMessageLite< public abstract static class Builder< MessageType extends GeneratedMessageLite<MessageType, BuilderType>, BuilderType extends Builder<MessageType, BuilderType>> - extends AbstractMessageLite.Builder<BuilderType> { + extends AbstractMessageLite.Builder<MessageType, BuilderType> { private final MessageType defaultInstance; protected MessageType instance; @@ -222,7 +315,8 @@ public abstract class GeneratedMessageLite< protected Builder(MessageType defaultInstance) { this.defaultInstance = defaultInstance; - this.instance = (MessageType) defaultInstance.dynamicMethod(MethodToInvoke.NEW_INSTANCE); + this.instance = + (MessageType) defaultInstance.dynamicMethod(MethodToInvoke.NEW_MUTABLE_INSTANCE); isBuilt = false; } @@ -232,26 +326,27 @@ public abstract class GeneratedMessageLite< */ protected void copyOnWrite() { if (isBuilt) { - MessageType newInstance = (MessageType) instance.dynamicMethod(MethodToInvoke.NEW_INSTANCE); - newInstance.dynamicMethod(MethodToInvoke.MERGE_FROM, instance); + MessageType newInstance = + (MessageType) instance.dynamicMethod(MethodToInvoke.NEW_MUTABLE_INSTANCE); + newInstance.visit(MergeFromVisitor.INSTANCE, instance); instance = newInstance; isBuilt = false; } } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public final boolean isInitialized() { return GeneratedMessageLite.isInitialized(instance, false /* shouldMemoize */); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public final BuilderType clear() { // No need to copy on write since we're dropping the instance anyways. - instance = (MessageType) instance.dynamicMethod(MethodToInvoke.NEW_INSTANCE); + instance = (MessageType) instance.dynamicMethod(MethodToInvoke.NEW_MUTABLE_INSTANCE); return (BuilderType) this; } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public BuilderType clone() { BuilderType builder = (BuilderType) getDefaultInstanceForType().newBuilderForType(); @@ -259,20 +354,19 @@ public abstract class GeneratedMessageLite< return builder; } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public MessageType buildPartial() { if (isBuilt) { return instance; } - instance.dynamicMethod(MethodToInvoke.MAKE_IMMUTABLE); - instance.unknownFields.makeImmutable(); + instance.makeImmutable(); isBuilt = true; return instance; } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public final MessageType build() { MessageType result = buildPartial(); if (!result.isInitialized()) { @@ -281,33 +375,36 @@ public abstract class GeneratedMessageLite< return result; } + @Override + protected BuilderType internalMergeFrom(MessageType message) { + return mergeFrom(message); + } + /** All subclasses implement this. */ public BuilderType mergeFrom(MessageType message) { copyOnWrite(); - instance.dynamicMethod(MethodToInvoke.MERGE_FROM, message); + instance.visit(MergeFromVisitor.INSTANCE, message); return (BuilderType) this; } + @Override public MessageType getDefaultInstanceForType() { return defaultInstance; } + @Override public BuilderType mergeFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - MessageType parsedMessage = null; + throws IOException { + copyOnWrite(); try { - parsedMessage = - (MessageType) getDefaultInstanceForType().getParserForType().parsePartialFrom( - input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (MessageType) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); + instance.dynamicMethod(MethodToInvoke.MERGE_FROM_STREAM, input, extensionRegistry); + } catch (RuntimeException e) { + if (e.getCause() instanceof IOException) { + throw (IOException) e.getCause(); } + throw e; } return (BuilderType) this; } @@ -363,6 +460,12 @@ public abstract class GeneratedMessageLite< } extensions.mergeFrom(((ExtendableMessage) other).extensions); } + + @Override + final void visit(Visitor visitor, MessageType other) { + super.visit(visitor, other); + extensions = visitor.visitExtensions(extensions, other.extensions); + } /** * Parse an unknown field or an extension. For use by generated code only. @@ -500,9 +603,8 @@ public abstract class GeneratedMessageLite< } /** Check if a singular extension is present. */ - //@Override (Java 1.6 override semantics, but we must support 1.5) - public final <Type> boolean hasExtension( - final ExtensionLite<MessageType, Type> extension) { + @Override + public final <Type> boolean hasExtension(final ExtensionLite<MessageType, Type> extension) { GeneratedExtension<MessageType, Type> extensionLite = checkIsLite(extension); @@ -511,7 +613,7 @@ public abstract class GeneratedMessageLite< } /** Get the number of elements in a repeated extension. */ - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public final <Type> int getExtensionCount( final ExtensionLite<MessageType, List<Type>> extension) { GeneratedExtension<MessageType, List<Type>> extensionLite = @@ -522,10 +624,9 @@ public abstract class GeneratedMessageLite< } /** Get the value of an extension. */ - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override @SuppressWarnings("unchecked") - public final <Type> Type getExtension( - final ExtensionLite<MessageType, Type> extension) { + public final <Type> Type getExtension(final ExtensionLite<MessageType, Type> extension) { GeneratedExtension<MessageType, Type> extensionLite = checkIsLite(extension); @@ -539,11 +640,10 @@ public abstract class GeneratedMessageLite< } /** Get one element of a repeated extension. */ - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override @SuppressWarnings("unchecked") public final <Type> Type getExtension( - final ExtensionLite<MessageType, List<Type>> extension, - final int index) { + final ExtensionLite<MessageType, List<Type>> extension, final int index) { GeneratedExtension<MessageType, List<Type>> extensionLite = checkIsLite(extension); @@ -557,10 +657,9 @@ public abstract class GeneratedMessageLite< return extensions.isInitialized(); } - @Override - protected final void doneParsing() { - super.doneParsing(); + protected final void makeImmutable() { + super.makeImmutable(); extensions.makeImmutable(); } @@ -649,7 +748,7 @@ public abstract class GeneratedMessageLite< instance.extensions = extensions; } - // @Override (Java 1.6 override semantics, but we must support 1.5) + @Override protected void copyOnWrite() { if (!isBuilt) { return; @@ -659,7 +758,7 @@ public abstract class GeneratedMessageLite< instance.extensions = instance.extensions.clone(); } - // @Override (Java 1.6 override semantics, but we must support 1.5) + @Override public final MessageType buildPartial() { if (isBuilt) { return instance; @@ -681,33 +780,30 @@ public abstract class GeneratedMessageLite< } /** Check if a singular extension is present. */ - //@Override (Java 1.6 override semantics, but we must support 1.5) - public final <Type> boolean hasExtension( - final ExtensionLite<MessageType, Type> extension) { + @Override + public final <Type> boolean hasExtension(final ExtensionLite<MessageType, Type> extension) { return instance.hasExtension(extension); } /** Get the number of elements in a repeated extension. */ - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public final <Type> int getExtensionCount( final ExtensionLite<MessageType, List<Type>> extension) { return instance.getExtensionCount(extension); } /** Get the value of an extension. */ - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override @SuppressWarnings("unchecked") - public final <Type> Type getExtension( - final ExtensionLite<MessageType, Type> extension) { + public final <Type> Type getExtension(final ExtensionLite<MessageType, Type> extension) { return instance.getExtension(extension); } /** Get one element of a repeated extension. */ + @Override @SuppressWarnings("unchecked") - //@Override (Java 1.6 override semantics, but we must support 1.5) public final <Type> Type getExtension( - final ExtensionLite<MessageType, List<Type>> extension, - final int index) { + final ExtensionLite<MessageType, List<Type>> extension, final int index) { return instance.getExtension(extension, index); } @@ -839,37 +935,44 @@ public abstract class GeneratedMessageLite< final boolean isRepeated; final boolean isPacked; + @Override public int getNumber() { return number; } + @Override public WireFormat.FieldType getLiteType() { return type; } + @Override public WireFormat.JavaType getLiteJavaType() { return type.getJavaType(); } + @Override public boolean isRepeated() { return isRepeated; } + @Override public boolean isPacked() { return isPacked; } + @Override public Internal.EnumLiteMap<?> getEnumType() { return enumTypeMap; } + @Override @SuppressWarnings("unchecked") - public MessageLite.Builder internalMergeFrom( - MessageLite.Builder to, MessageLite from) { + public MessageLite.Builder internalMergeFrom(MessageLite.Builder to, MessageLite from) { return ((Builder) to).mergeFrom((GeneratedMessageLite) from); } + @Override public int compareTo(ExtensionDescriptor other) { return number - other.number; } @@ -964,6 +1067,7 @@ public abstract class GeneratedMessageLite< } /** Get the field number. */ + @Override public int getNumber() { return descriptor.getNumber(); } @@ -973,6 +1077,7 @@ public abstract class GeneratedMessageLite< * If the extension is an embedded message or group, returns the default * instance of the message. */ + @Override public MessageLite getMessageDefaultInstance() { return messageDefaultInstance; } @@ -1027,14 +1132,17 @@ public abstract class GeneratedMessageLite< } } + @Override public FieldType getLiteType() { return descriptor.getLiteType(); } + @Override public boolean isRepeated() { return descriptor.isRepeated; } + @Override public Type getDefaultValue() { return defaultValue; } @@ -1044,7 +1152,12 @@ public abstract class GeneratedMessageLite< * A serialized (serializable) form of the generated message. Stores the * message as a class name and a byte array. */ - static final class SerializedForm implements Serializable { + protected static final class SerializedForm implements Serializable { + + public static SerializedForm of(MessageLite message) { + return new SerializedForm(message); + } + private static final long serialVersionUID = 0L; private final String messageClassName; @@ -1088,16 +1201,6 @@ public abstract class GeneratedMessageLite< } } } - - /** - * Replaces this object in the output stream with a serialized form. - * Part of Java's serialization magic. Generated sub-classes must override - * this method by calling {@code return super.writeReplace();} - * @return a SerializedForm of this message - */ - protected Object writeReplace() throws ObjectStreamException { - return new SerializedForm(this); - } /** * Checks that the {@link Extension} is Lite and returns it as a @@ -1124,30 +1227,72 @@ public abstract class GeneratedMessageLite< protected static final <T extends GeneratedMessageLite<T, ?>> boolean isInitialized( T message, boolean shouldMemoize) { return message.dynamicMethod(MethodToInvoke.IS_INITIALIZED, shouldMemoize) != null; - } + } protected static final <T extends GeneratedMessageLite<T, ?>> void makeImmutable(T message) { message.dynamicMethod(MethodToInvoke.MAKE_IMMUTABLE); } + + protected static IntList emptyIntList() { + return IntArrayList.emptyList(); + } + + protected static IntList mutableCopy(IntList list) { + int size = list.size(); + return list.mutableCopyWithCapacity( + size == 0 ? AbstractProtobufList.DEFAULT_CAPACITY : size * 2); + } + + protected static LongList emptyLongList() { + return LongArrayList.emptyList(); + } - /** - * A static helper method for parsing a partial from input using the extension registry and the - * instance. - */ - static <T extends GeneratedMessageLite<T, ?>> T parsePartialFrom( - T instance, CodedInputStream input, ExtensionRegistryLite extensionRegistry) - throws InvalidProtocolBufferException { - try { - return (T) instance.dynamicMethod( - MethodToInvoke.PARSE_PARTIAL_FROM, input, extensionRegistry); - } catch (RuntimeException e) { - if (e.getCause() instanceof InvalidProtocolBufferException) { - throw (InvalidProtocolBufferException) e.getCause(); - } - throw e; - } + protected static LongList mutableCopy(LongList list) { + int size = list.size(); + return list.mutableCopyWithCapacity( + size == 0 ? AbstractProtobufList.DEFAULT_CAPACITY : size * 2); + } + + protected static FloatList emptyFloatList() { + return FloatArrayList.emptyList(); + } + + protected static FloatList mutableCopy(FloatList list) { + int size = list.size(); + return list.mutableCopyWithCapacity( + size == 0 ? AbstractProtobufList.DEFAULT_CAPACITY : size * 2); + } + + protected static DoubleList emptyDoubleList() { + return DoubleArrayList.emptyList(); } + protected static DoubleList mutableCopy(DoubleList list) { + int size = list.size(); + return list.mutableCopyWithCapacity( + size == 0 ? AbstractProtobufList.DEFAULT_CAPACITY : size * 2); + } + + protected static BooleanList emptyBooleanList() { + return BooleanArrayList.emptyList(); + } + + protected static BooleanList mutableCopy(BooleanList list) { + int size = list.size(); + return list.mutableCopyWithCapacity( + size == 0 ? AbstractProtobufList.DEFAULT_CAPACITY : size * 2); + } + + protected static <E> ProtobufList<E> emptyProtobufList() { + return ProtobufArrayList.emptyList(); + } + + protected static <E> ProtobufList<E> mutableCopy(ProtobufList<E> list) { + int size = list.size(); + return list.mutableCopyWithCapacity( + size == 0 ? AbstractProtobufList.DEFAULT_CAPACITY : size * 2); + } + /** * A {@link Parser} implementation that delegates to the default instance. * <p> @@ -1169,79 +1314,929 @@ public abstract class GeneratedMessageLite< } } - protected static IntList newIntList() { - return new IntArrayList(); + /** + * A static helper method for parsing a partial from input using the extension registry and the + * instance. + */ + // TODO(dweis): Should this verify that the last tag was 0? + static <T extends GeneratedMessageLite<T, ?>> T parsePartialFrom( + T instance, CodedInputStream input, ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + @SuppressWarnings("unchecked") // Guaranteed by protoc + T result = (T) instance.dynamicMethod(MethodToInvoke.NEW_MUTABLE_INSTANCE); + try { + result.dynamicMethod(MethodToInvoke.MERGE_FROM_STREAM, input, extensionRegistry); + result.makeImmutable(); + } catch (RuntimeException e) { + if (e.getCause() instanceof InvalidProtocolBufferException) { + throw (InvalidProtocolBufferException) e.getCause(); + } + throw e; + } + return result; } - protected static IntList newIntList(List<Integer> toCopy) { - return new IntArrayList(toCopy); + protected static <T extends GeneratedMessageLite<T, ?>> T parsePartialFrom( + T defaultInstance, + CodedInputStream input) + throws InvalidProtocolBufferException { + return parsePartialFrom(defaultInstance, input, ExtensionRegistryLite.getEmptyRegistry()); } - protected static IntList emptyIntList() { - return IntArrayList.emptyList(); + /** + * Helper method to check if message is initialized. + * + * @throws InvalidProtocolBufferException if it is not initialized. + * @return The message to check. + */ + private static <T extends GeneratedMessageLite<T, ?>> T checkMessageInitialized(T message) + throws InvalidProtocolBufferException { + if (message != null && !message.isInitialized()) { + throw message.newUninitializedMessageException() + .asInvalidProtocolBufferException() + .setUnfinishedMessage(message); + } + return message; } - - protected static LongList newLongList() { - return new LongArrayList(); + + // Validates last tag. + protected static <T extends GeneratedMessageLite<T, ?>> T parseFrom( + T defaultInstance, ByteString data) + throws InvalidProtocolBufferException { + return checkMessageInitialized( + parseFrom(defaultInstance, data, ExtensionRegistryLite.getEmptyRegistry())); } - - protected static LongList newLongList(List<Long> toCopy) { - return new LongArrayList(toCopy); + + // Validates last tag. + protected static <T extends GeneratedMessageLite<T, ?>> T parseFrom( + T defaultInstance, ByteString data, ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + return checkMessageInitialized(parsePartialFrom(defaultInstance, data, extensionRegistry)); } - protected static LongList emptyLongList() { - return LongArrayList.emptyList(); + // This is a special case since we want to verify that the last tag is 0. We assume we exhaust the + // ByteString. + private static <T extends GeneratedMessageLite<T, ?>> T parsePartialFrom( + T defaultInstance, ByteString data, ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + T message; + try { + CodedInputStream input = data.newCodedInput(); + message = parsePartialFrom(defaultInstance, input, extensionRegistry); + try { + input.checkLastTagWas(0); + } catch (InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(message); + } + return message; + } catch (InvalidProtocolBufferException e) { + throw e; + } } - protected static FloatList newFloatList() { - return new FloatArrayList(); + // This is a special case since we want to verify that the last tag is 0. We assume we exhaust the + // ByteString. + private static <T extends GeneratedMessageLite<T, ?>> T parsePartialFrom( + T defaultInstance, byte[] data, ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + T message; + try { + CodedInputStream input = CodedInputStream.newInstance(data); + message = parsePartialFrom(defaultInstance, input, extensionRegistry); + try { + input.checkLastTagWas(0); + } catch (InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(message); + } + return message; + } catch (InvalidProtocolBufferException e) { + throw e; + } } - - protected static FloatList newFloatList(List<Float> toCopy) { - return new FloatArrayList(toCopy); + + // Validates last tag. + protected static <T extends GeneratedMessageLite<T, ?>> T parseFrom( + T defaultInstance, byte[] data) + throws InvalidProtocolBufferException { + return checkMessageInitialized( + parsePartialFrom(defaultInstance, data, ExtensionRegistryLite.getEmptyRegistry())); } - - protected static FloatList emptyFloatList() { - return FloatArrayList.emptyList(); + + // Validates last tag. + protected static <T extends GeneratedMessageLite<T, ?>> T parseFrom( + T defaultInstance, byte[] data, ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + return checkMessageInitialized(parsePartialFrom(defaultInstance, data, extensionRegistry)); } - - protected static DoubleList newDoubleList() { - return new DoubleArrayList(); + + // Does not validate last tag. + protected static <T extends GeneratedMessageLite<T, ?>> T parseFrom( + T defaultInstance, InputStream input) + throws InvalidProtocolBufferException { + return checkMessageInitialized( + parsePartialFrom(defaultInstance, CodedInputStream.newInstance(input), + ExtensionRegistryLite.getEmptyRegistry())); } - - protected static DoubleList newDoubleList(List<Double> toCopy) { - return new DoubleArrayList(toCopy); + + // Does not validate last tag. + protected static <T extends GeneratedMessageLite<T, ?>> T parseFrom( + T defaultInstance, InputStream input, ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + return checkMessageInitialized( + parsePartialFrom(defaultInstance, CodedInputStream.newInstance(input), extensionRegistry)); } - - protected static DoubleList emptyDoubleList() { - return DoubleArrayList.emptyList(); + + // Does not validate last tag. + protected static <T extends GeneratedMessageLite<T, ?>> T parseFrom( + T defaultInstance, CodedInputStream input) + throws InvalidProtocolBufferException { + return parseFrom(defaultInstance, input, ExtensionRegistryLite.getEmptyRegistry()); } - - protected static BooleanList newBooleanList() { - return new BooleanArrayList(); + + // Does not validate last tag. + protected static <T extends GeneratedMessageLite<T, ?>> T parseFrom( + T defaultInstance, CodedInputStream input, ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + return checkMessageInitialized( + parsePartialFrom(defaultInstance, input, extensionRegistry)); } - - protected static BooleanList newBooleanList(List<Boolean> toCopy) { - return new BooleanArrayList(toCopy); + + // Validates last tag. + protected static <T extends GeneratedMessageLite<T, ?>> T parseDelimitedFrom( + T defaultInstance, InputStream input) + throws InvalidProtocolBufferException { + return checkMessageInitialized( + parsePartialDelimitedFrom(defaultInstance, input, + ExtensionRegistryLite.getEmptyRegistry())); } - - protected static BooleanList emptyBooleanList() { - return BooleanArrayList.emptyList(); + + // Validates last tag. + protected static <T extends GeneratedMessageLite<T, ?>> T parseDelimitedFrom( + T defaultInstance, InputStream input, ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + return checkMessageInitialized( + parsePartialDelimitedFrom(defaultInstance, input, extensionRegistry)); } - protected static <E> ProtobufList<E> newProtobufList() { - return new ProtobufArrayList<E>(); + private static <T extends GeneratedMessageLite<T, ?>> T parsePartialDelimitedFrom( + T defaultInstance, + InputStream input, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + int size; + try { + int firstByte = input.read(); + if (firstByte == -1) { + return null; + } + size = CodedInputStream.readRawVarint32(firstByte, input); + } catch (IOException e) { + throw new InvalidProtocolBufferException(e.getMessage()); + } + InputStream limitedInput = new LimitedInputStream(input, size); + CodedInputStream codedInput = CodedInputStream.newInstance(limitedInput); + T message = parsePartialFrom(defaultInstance, codedInput, extensionRegistry); + try { + codedInput.checkLastTagWas(0); + } catch (InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(message); + } + return message; } - - protected static <E> ProtobufList<E> newProtobufList(List<E> toCopy) { - return new ProtobufArrayList<E>(toCopy); + + /** + * An abstract visitor that the generated code calls into that we use to implement various + * features. Fields that are not members of oneofs are always visited. Members of a oneof are only + * visited when they are the set oneof case value on the "other" proto. The visitOneofNotSet + * method is invoked if other's oneof case is not set. + */ + protected interface Visitor { + boolean visitBoolean(boolean minePresent, boolean mine, boolean otherPresent, boolean other); + int visitInt(boolean minePresent, int mine, boolean otherPresent, int other); + double visitDouble(boolean minePresent, double mine, boolean otherPresent, double other); + float visitFloat(boolean minePresent, float mine, boolean otherPresent, float other); + long visitLong(boolean minePresent, long mine, boolean otherPresent, long other); + String visitString(boolean minePresent, String mine, boolean otherPresent, String other); + ByteString visitByteString( + boolean minePresent, ByteString mine, boolean otherPresent, ByteString other); + + Object visitOneofBoolean(boolean minePresent, Object mine, Object other); + Object visitOneofInt(boolean minePresent, Object mine, Object other); + Object visitOneofDouble(boolean minePresent, Object mine, Object other); + Object visitOneofFloat(boolean minePresent, Object mine, Object other); + Object visitOneofLong(boolean minePresent, Object mine, Object other); + Object visitOneofString(boolean minePresent, Object mine, Object other); + Object visitOneofByteString(boolean minePresent, Object mine, Object other); + Object visitOneofLazyMessage(boolean minePresent, Object mine, Object other); + Object visitOneofMessage(boolean minePresent, Object mine, Object other); + void visitOneofNotSet(boolean minePresent); + + /** + * Message fields use null sentinals. + */ + <T extends MessageLite> T visitMessage(T mine, T other); + LazyFieldLite visitLazyMessage( + boolean minePresent, LazyFieldLite mine, boolean otherPresent, LazyFieldLite other); + + <T> ProtobufList<T> visitList(ProtobufList<T> mine, ProtobufList<T> other); + BooleanList visitBooleanList(BooleanList mine, BooleanList other); + IntList visitIntList(IntList mine, IntList other); + DoubleList visitDoubleList(DoubleList mine, DoubleList other); + FloatList visitFloatList(FloatList mine, FloatList other); + LongList visitLongList(LongList mine, LongList other); + FieldSet<ExtensionDescriptor> visitExtensions( + FieldSet<ExtensionDescriptor> mine, FieldSet<ExtensionDescriptor> other); + UnknownFieldSetLite visitUnknownFields(UnknownFieldSetLite mine, UnknownFieldSetLite other); + <K, V> MapFieldLite<K, V> visitMap(MapFieldLite<K, V> mine, MapFieldLite<K, V> other); } - - protected static <E> ProtobufList<E> emptyProtobufList() { - return ProtobufArrayList.emptyList(); + + /** + * Implements equals. Throws a {@link NotEqualsException} when not equal. + */ + static class EqualsVisitor implements Visitor { + + static final class NotEqualsException extends RuntimeException {} + + static final EqualsVisitor INSTANCE = new EqualsVisitor(); + + static final NotEqualsException NOT_EQUALS = new NotEqualsException(); + + private EqualsVisitor() {} + + @Override + public boolean visitBoolean( + boolean minePresent, boolean mine, boolean otherPresent, boolean other) { + if (minePresent != otherPresent || mine != other) { + throw NOT_EQUALS; + } + return mine; + } + + @Override + public int visitInt(boolean minePresent, int mine, boolean otherPresent, int other) { + if (minePresent != otherPresent || mine != other) { + throw NOT_EQUALS; + } + return mine; + } + + @Override + public double visitDouble( + boolean minePresent, double mine, boolean otherPresent, double other) { + if (minePresent != otherPresent || mine != other) { + throw NOT_EQUALS; + } + return mine; + } + + @Override + public float visitFloat(boolean minePresent, float mine, boolean otherPresent, float other) { + if (minePresent != otherPresent || mine != other) { + throw NOT_EQUALS; + } + return mine; + } + + @Override + public long visitLong(boolean minePresent, long mine, boolean otherPresent, long other) { + if (minePresent != otherPresent || mine != other) { + throw NOT_EQUALS; + } + return mine; + } + + @Override + public String visitString( + boolean minePresent, String mine, boolean otherPresent, String other) { + if (minePresent != otherPresent || !mine.equals(other)) { + throw NOT_EQUALS; + } + return mine; + } + + @Override + public ByteString visitByteString( + boolean minePresent, ByteString mine, boolean otherPresent, ByteString other) { + if (minePresent != otherPresent || !mine.equals(other)) { + throw NOT_EQUALS; + } + return mine; + } + + @Override + public Object visitOneofBoolean(boolean minePresent, Object mine, Object other) { + if (minePresent && mine.equals(other)) { + return mine; + } + throw NOT_EQUALS; + } + + @Override + public Object visitOneofInt(boolean minePresent, Object mine, Object other) { + if (minePresent && mine.equals(other)) { + return mine; + } + throw NOT_EQUALS; + } + + @Override + public Object visitOneofDouble(boolean minePresent, Object mine, Object other) { + if (minePresent && mine.equals(other)) { + return mine; + } + throw NOT_EQUALS; + } + + @Override + public Object visitOneofFloat(boolean minePresent, Object mine, Object other) { + if (minePresent && mine.equals(other)) { + return mine; + } + throw NOT_EQUALS; + } + + @Override + public Object visitOneofLong(boolean minePresent, Object mine, Object other) { + if (minePresent && mine.equals(other)) { + return mine; + } + throw NOT_EQUALS; + } + + @Override + public Object visitOneofString(boolean minePresent, Object mine, Object other) { + if (minePresent && mine.equals(other)) { + return mine; + } + throw NOT_EQUALS; + } + + @Override + public Object visitOneofByteString(boolean minePresent, Object mine, Object other) { + if (minePresent && mine.equals(other)) { + return mine; + } + throw NOT_EQUALS; + } + + @Override + public Object visitOneofLazyMessage(boolean minePresent, Object mine, Object other) { + if (minePresent && mine.equals(other)) { + return mine; + } + throw NOT_EQUALS; + } + + @Override + public Object visitOneofMessage(boolean minePresent, Object mine, Object other) { + if (minePresent && ((GeneratedMessageLite<?, ?>) mine).equals(this, (MessageLite) other)) { + return mine; + } + throw NOT_EQUALS; + } + + @Override + public void visitOneofNotSet(boolean minePresent) { + if (minePresent) { + throw NOT_EQUALS; + } + } + + @Override + public <T extends MessageLite> T visitMessage(T mine, T other) { + if (mine == null && other == null) { + return null; + } + + if (mine == null || other == null) { + throw NOT_EQUALS; + } + + ((GeneratedMessageLite<?, ?>) mine).equals(this, other); + + return mine; + } + + @Override + public LazyFieldLite visitLazyMessage( + boolean minePresent, LazyFieldLite mine, boolean otherPresent, LazyFieldLite other) { + if (!minePresent && !otherPresent) { + return mine; + } else if (minePresent && otherPresent && mine.equals(other)) { + return mine; + } + throw NOT_EQUALS; + } + + @Override + public <T> ProtobufList<T> visitList(ProtobufList<T> mine, ProtobufList<T> other) { + if (!mine.equals(other)) { + throw NOT_EQUALS; + } + return mine; + } + + @Override + public BooleanList visitBooleanList(BooleanList mine, BooleanList other) { + if (!mine.equals(other)) { + throw NOT_EQUALS; + } + return mine; + } + + @Override + public IntList visitIntList(IntList mine, IntList other) { + if (!mine.equals(other)) { + throw NOT_EQUALS; + } + return mine; + } + + @Override + public DoubleList visitDoubleList(DoubleList mine, DoubleList other) { + if (!mine.equals(other)) { + throw NOT_EQUALS; + } + return mine; + } + + @Override + public FloatList visitFloatList(FloatList mine, FloatList other) { + if (!mine.equals(other)) { + throw NOT_EQUALS; + } + return mine; + } + + @Override + public LongList visitLongList(LongList mine, LongList other) { + if (!mine.equals(other)) { + throw NOT_EQUALS; + } + return mine; + } + + @Override + public FieldSet<ExtensionDescriptor> visitExtensions( + FieldSet<ExtensionDescriptor> mine, + FieldSet<ExtensionDescriptor> other) { + if (!mine.equals(other)) { + throw NOT_EQUALS; + } + return mine; + } + + @Override + public UnknownFieldSetLite visitUnknownFields( + UnknownFieldSetLite mine, + UnknownFieldSetLite other) { + if (!mine.equals(other)) { + throw NOT_EQUALS; + } + return mine; + } + + @Override + public <K, V> MapFieldLite<K, V> visitMap(MapFieldLite<K, V> mine, MapFieldLite<K, V> other) { + if (!mine.equals(other)) { + throw NOT_EQUALS; + } + return mine; + } } - - protected static LazyStringArrayList emptyLazyStringArrayList() { - return LazyStringArrayList.emptyList(); + + /** + * Implements hashCode by accumulating state. + */ + private static class HashCodeVisitor implements Visitor { + + // The caller must ensure that the visitor is invoked parameterized with this and this such that + // other is this. This is required due to how oneof cases are handled. See the class comment + // on Visitor for more information. + + private int hashCode = 0; + + @Override + public boolean visitBoolean( + boolean minePresent, boolean mine, boolean otherPresent, boolean other) { + hashCode = (53 * hashCode) + Internal.hashBoolean(mine); + return mine; + } + + @Override + public int visitInt(boolean minePresent, int mine, boolean otherPresent, int other) { + hashCode = (53 * hashCode) + mine; + return mine; + } + + @Override + public double visitDouble( + boolean minePresent, double mine, boolean otherPresent, double other) { + hashCode = (53 * hashCode) + Internal.hashLong(Double.doubleToLongBits(mine)); + return mine; + } + + @Override + public float visitFloat(boolean minePresent, float mine, boolean otherPresent, float other) { + hashCode = (53 * hashCode) + Float.floatToIntBits(mine); + return mine; + } + + @Override + public long visitLong(boolean minePresent, long mine, boolean otherPresent, long other) { + hashCode = (53 * hashCode) + Internal.hashLong(mine); + return mine; + } + + @Override + public String visitString( + boolean minePresent, String mine, boolean otherPresent, String other) { + hashCode = (53 * hashCode) + mine.hashCode(); + return mine; + } + + @Override + public ByteString visitByteString( + boolean minePresent, ByteString mine, boolean otherPresent, ByteString other) { + hashCode = (53 * hashCode) + mine.hashCode(); + return mine; + } + + @Override + public Object visitOneofBoolean(boolean minePresent, Object mine, Object other) { + hashCode = (53 * hashCode) + Internal.hashBoolean(((Boolean) mine)); + return mine; + } + + @Override + public Object visitOneofInt(boolean minePresent, Object mine, Object other) { + hashCode = (53 * hashCode) + (Integer) mine; + return mine; + } + + @Override + public Object visitOneofDouble(boolean minePresent, Object mine, Object other) { + hashCode = (53 * hashCode) + Internal.hashLong(Double.doubleToLongBits((Double) mine)); + return mine; + } + + @Override + public Object visitOneofFloat(boolean minePresent, Object mine, Object other) { + hashCode = (53 * hashCode) + Float.floatToIntBits((Float) mine); + return mine; + } + + @Override + public Object visitOneofLong(boolean minePresent, Object mine, Object other) { + hashCode = (53 * hashCode) + Internal.hashLong((Long) mine); + return mine; + } + + @Override + public Object visitOneofString(boolean minePresent, Object mine, Object other) { + hashCode = (53 * hashCode) + mine.hashCode(); + return mine; + } + + @Override + public Object visitOneofByteString(boolean minePresent, Object mine, Object other) { + hashCode = (53 * hashCode) + mine.hashCode(); + return mine; + } + + @Override + public Object visitOneofLazyMessage(boolean minePresent, Object mine, Object other) { + hashCode = (53 * hashCode) + mine.hashCode(); + return mine; + } + + @Override + public Object visitOneofMessage(boolean minePresent, Object mine, Object other) { + return visitMessage((MessageLite) mine, (MessageLite) other); + } + + @Override + public void visitOneofNotSet(boolean minePresent) { + if (minePresent) { + throw new IllegalStateException(); // Can't happen if other == this. + } + } + + @Override + public <T extends MessageLite> T visitMessage(T mine, T other) { + final int protoHash; + if (mine != null) { + if (mine instanceof GeneratedMessageLite) { + protoHash = ((GeneratedMessageLite) mine).hashCode(this); + } else { + protoHash = mine.hashCode(); + } + } else { + protoHash = 37; + } + hashCode = (53 * hashCode) + protoHash; + return mine; + } + + @Override + public LazyFieldLite visitLazyMessage( + boolean minePresent, LazyFieldLite mine, boolean otherPresent, LazyFieldLite other) { + hashCode = (53 * hashCode) + mine.hashCode(); + return mine; + } + + @Override + public <T> ProtobufList<T> visitList(ProtobufList<T> mine, ProtobufList<T> other) { + hashCode = (53 * hashCode) + mine.hashCode(); + return mine; + } + + @Override + public BooleanList visitBooleanList(BooleanList mine, BooleanList other) { + hashCode = (53 * hashCode) + mine.hashCode(); + return mine; + } + + @Override + public IntList visitIntList(IntList mine, IntList other) { + hashCode = (53 * hashCode) + mine.hashCode(); + return mine; + } + + @Override + public DoubleList visitDoubleList(DoubleList mine, DoubleList other) { + hashCode = (53 * hashCode) + mine.hashCode(); + return mine; + } + + @Override + public FloatList visitFloatList(FloatList mine, FloatList other) { + hashCode = (53 * hashCode) + mine.hashCode(); + return mine; + } + + @Override + public LongList visitLongList(LongList mine, LongList other) { + hashCode = (53 * hashCode) + mine.hashCode(); + return mine; + } + + @Override + public FieldSet<ExtensionDescriptor> visitExtensions( + FieldSet<ExtensionDescriptor> mine, + FieldSet<ExtensionDescriptor> other) { + hashCode = (53 * hashCode) + mine.hashCode(); + return mine; + } + + @Override + public UnknownFieldSetLite visitUnknownFields( + UnknownFieldSetLite mine, + UnknownFieldSetLite other) { + hashCode = (53 * hashCode) + mine.hashCode(); + return mine; + } + + @Override + public <K, V> MapFieldLite<K, V> visitMap(MapFieldLite<K, V> mine, MapFieldLite<K, V> other) { + hashCode = (53 * hashCode) + mine.hashCode(); + return mine; + } + } + + /** + * Implements field merging semantics over the visitor interface. + */ + protected static class MergeFromVisitor implements Visitor { + + public static final MergeFromVisitor INSTANCE = new MergeFromVisitor(); + + private MergeFromVisitor() {} + + @Override + public boolean visitBoolean( + boolean minePresent, boolean mine, boolean otherPresent, boolean other) { + return otherPresent ? other : mine; + } + + @Override + public int visitInt(boolean minePresent, int mine, boolean otherPresent, int other) { + return otherPresent ? other : mine; + } + + @Override + public double visitDouble( + boolean minePresent, double mine, boolean otherPresent, double other) { + return otherPresent ? other : mine; + } + + @Override + public float visitFloat(boolean minePresent, float mine, boolean otherPresent, float other) { + return otherPresent ? other : mine; + } + + @Override + public long visitLong(boolean minePresent, long mine, boolean otherPresent, long other) { + return otherPresent ? other : mine; + } + + @Override + public String visitString( + boolean minePresent, String mine, boolean otherPresent, String other) { + return otherPresent ? other : mine; + } + + @Override + public ByteString visitByteString( + boolean minePresent, ByteString mine, boolean otherPresent, ByteString other) { + return otherPresent ? other : mine; + } + + @Override + public Object visitOneofBoolean(boolean minePresent, Object mine, Object other) { + return other; + } + + @Override + public Object visitOneofInt(boolean minePresent, Object mine, Object other) { + return other; + } + + @Override + public Object visitOneofDouble(boolean minePresent, Object mine, Object other) { + return other; + } + + @Override + public Object visitOneofFloat(boolean minePresent, Object mine, Object other) { + return other; + } + + @Override + public Object visitOneofLong(boolean minePresent, Object mine, Object other) { + return other; + } + + @Override + public Object visitOneofString(boolean minePresent, Object mine, Object other) { + return other; + } + + @Override + public Object visitOneofByteString(boolean minePresent, Object mine, Object other) { + return other; + } + + @Override + public Object visitOneofLazyMessage(boolean minePresent, Object mine, Object other) { + if (minePresent) { + LazyFieldLite lazy = (LazyFieldLite) mine; + lazy.merge((LazyFieldLite) other); + return lazy; + } + return other; + } + + @Override + public Object visitOneofMessage(boolean minePresent, Object mine, Object other) { + if (minePresent) { + return visitMessage((MessageLite) mine, (MessageLite) other); + } + return other; + } + + @Override + public void visitOneofNotSet(boolean minePresent) { + return; + } + + @SuppressWarnings("unchecked") // Guaranteed by runtime. + @Override + public <T extends MessageLite> T visitMessage(T mine, T other) { + if (mine != null && other != null) { + return (T) mine.toBuilder().mergeFrom(other).build(); + } + + return mine != null ? mine : other; + } + + @Override + public LazyFieldLite visitLazyMessage( + boolean minePresent, LazyFieldLite mine, boolean otherPresent, LazyFieldLite other) { + // LazyFieldLite's are never null so we can just copy across. Necessary to avoid leakage + // from builder into immutable message. + // TODO(dweis): Change to null sentinels? + mine.merge(other); + return mine; + } + + @Override + public <T> ProtobufList<T> visitList(ProtobufList<T> mine, ProtobufList<T> other) { + int size = mine.size(); + int otherSize = other.size(); + if (size > 0 && otherSize > 0) { + if (!mine.isModifiable()) { + mine = mine.mutableCopyWithCapacity(size + otherSize); + } + mine.addAll(other); + } + + return size > 0 ? mine : other; + } + + @Override + public BooleanList visitBooleanList(BooleanList mine, BooleanList other) { + int size = mine.size(); + int otherSize = other.size(); + if (size > 0 && otherSize > 0) { + if (!mine.isModifiable()) { + mine = mine.mutableCopyWithCapacity(size + otherSize); + } + mine.addAll(other); + } + + return size > 0 ? mine : other; + } + + @Override + public IntList visitIntList(IntList mine, IntList other) { + int size = mine.size(); + int otherSize = other.size(); + if (size > 0 && otherSize > 0) { + if (!mine.isModifiable()) { + mine = mine.mutableCopyWithCapacity(size + otherSize); + } + mine.addAll(other); + } + + return size > 0 ? mine : other; + } + + @Override + public DoubleList visitDoubleList(DoubleList mine, DoubleList other) { + int size = mine.size(); + int otherSize = other.size(); + if (size > 0 && otherSize > 0) { + if (!mine.isModifiable()) { + mine = mine.mutableCopyWithCapacity(size + otherSize); + } + mine.addAll(other); + } + + return size > 0 ? mine : other; + } + + @Override + public FloatList visitFloatList(FloatList mine, FloatList other) { + int size = mine.size(); + int otherSize = other.size(); + if (size > 0 && otherSize > 0) { + if (!mine.isModifiable()) { + mine = mine.mutableCopyWithCapacity(size + otherSize); + } + mine.addAll(other); + } + + return size > 0 ? mine : other; + } + + @Override + public LongList visitLongList(LongList mine, LongList other) { + int size = mine.size(); + int otherSize = other.size(); + if (size > 0 && otherSize > 0) { + if (!mine.isModifiable()) { + mine = mine.mutableCopyWithCapacity(size + otherSize); + } + mine.addAll(other); + } + + return size > 0 ? mine : other; + } + + @Override + public FieldSet<ExtensionDescriptor> visitExtensions( + FieldSet<ExtensionDescriptor> mine, + FieldSet<ExtensionDescriptor> other) { + if (mine.isImmutable()) { + mine = mine.clone(); + } + mine.mergeFrom(other); + return mine; + } + + @Override + public UnknownFieldSetLite visitUnknownFields( + UnknownFieldSetLite mine, + UnknownFieldSetLite other) { + return other == UnknownFieldSetLite.getDefaultInstance() + ? mine : UnknownFieldSetLite.mutableCopyOf(mine, other); + } + + @Override + public <K, V> MapFieldLite<K, V> visitMap(MapFieldLite<K, V> mine, MapFieldLite<K, V> other) { + mine.mergeFrom(other); + return mine; + } } } diff --git a/java/src/main/java/com/google/protobuf/IntArrayList.java b/java/core/src/main/java/com/google/protobuf/IntArrayList.java index f7609cc9..6d6ece5a 100644 --- a/java/src/main/java/com/google/protobuf/IntArrayList.java +++ b/java/core/src/main/java/com/google/protobuf/IntArrayList.java @@ -34,7 +34,6 @@ import com.google.protobuf.Internal.IntList; import java.util.Arrays; import java.util.Collection; -import java.util.List; import java.util.RandomAccess; /** @@ -44,8 +43,6 @@ import java.util.RandomAccess; */ final class IntArrayList extends AbstractProtobufList<Integer> implements IntList, RandomAccess { - private static final int DEFAULT_CAPACITY = 10; - private static final IntArrayList EMPTY_LIST = new IntArrayList(); static { EMPTY_LIST.makeImmutable(); @@ -67,28 +64,58 @@ final class IntArrayList extends AbstractProtobufList<Integer> implements IntLis private int size; /** - * Constructs a new mutable {@code IntArrayList}. + * Constructs a new mutable {@code IntArrayList} with default capacity. */ IntArrayList() { - array = new int[DEFAULT_CAPACITY]; - size = 0; + this(new int[DEFAULT_CAPACITY], 0); } /** * Constructs a new mutable {@code IntArrayList} containing the same elements as {@code other}. */ - IntArrayList(List<Integer> other) { - if (other instanceof IntArrayList) { - IntArrayList list = (IntArrayList) other; - array = list.array.clone(); - size = list.size; - } else { - size = other.size(); - array = new int[size]; - for (int i = 0; i < size; i++) { - array[i] = other.get(i); + private IntArrayList(int[] array, int size) { + this.array = array; + this.size = size; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof IntArrayList)) { + return super.equals(o); + } + IntArrayList other = (IntArrayList) o; + if (size != other.size) { + return false; + } + + final int[] arr = other.array; + for (int i = 0; i < size; i++) { + if (array[i] != arr[i]) { + return false; } } + + return true; + } + + @Override + public int hashCode() { + int result = 1; + for (int i = 0; i < size; i++) { + result = (31 * result) + array[i]; + } + return result; + } + + @Override + public IntList mutableCopyWithCapacity(int capacity) { + if (capacity < size) { + throw new IllegalArgumentException(); + } + return new IntArrayList(Arrays.copyOf(array, capacity), size); } @Override diff --git a/java/src/main/java/com/google/protobuf/Internal.java b/java/core/src/main/java/com/google/protobuf/Internal.java index 11901998..d1de375e 100644 --- a/java/src/main/java/com/google/protobuf/Internal.java +++ b/java/core/src/main/java/com/google/protobuf/Internal.java @@ -41,6 +41,7 @@ import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.RandomAccess; import java.util.Set; /** @@ -51,10 +52,12 @@ import java.util.Set; * * @author kenton@google.com (Kenton Varda) */ -public class Internal { +public final class Internal { - protected static final Charset UTF_8 = Charset.forName("UTF-8"); - protected static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); + private Internal() {} + + static final Charset UTF_8 = Charset.forName("UTF-8"); + static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); /** * Helper called by generated code to construct default values for string @@ -283,10 +286,32 @@ public class Internal { // 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); + return Internal.hashCode(bytes, 0, bytes.length); + } + + /** + * Helper method for implementing {@link LiteralByteString#hashCode()}. + */ + static int hashCode(byte[] bytes, int offset, int length) { + // 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. + int h = Internal.partialHash(length, bytes, offset, length); + return h == 0 ? 1 : h; } /** + * Helper method for continuously hashing bytes. + */ + static int partialHash(int h, byte[] bytes, int offset, int length) { + for (int i = offset; i < offset + length; i++) { + h = h * 31 + bytes[i]; + } + return h; + } + + /** * Helper method for implementing {@link Message#equals(Object)} for bytes * field. */ @@ -337,8 +362,7 @@ public class Internal { public static int hashCodeByteBuffer(ByteBuffer bytes) { if (bytes.hasArray()) { // Fast path. - int h = LiteralByteString.hashCode(bytes.capacity(), bytes.array(), - bytes.arrayOffset(), bytes.capacity()); + int h = partialHash(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 @@ -353,7 +377,7 @@ public class Internal { final int length = duplicated.remaining() <= bufferSize ? duplicated.remaining() : bufferSize; duplicated.get(buffer, 0, length); - h = LiteralByteString.hashCode(h, buffer, 0, length); + h = partialHash(h, buffer, 0, length); } return h == 0 ? 1 : h; } @@ -434,10 +458,13 @@ public class Internal { public static <T extends EnumLite> Converter<Integer, T> newEnumConverter( final EnumLiteMap<T> enumMap, final T unrecognizedValue) { return new Converter<Integer, T>() { + @Override public T doForward(Integer value) { T result = enumMap.findValueByNumber(value); return result == null ? unrecognizedValue : result; } + + @Override public Integer doBackward(T value) { return value.getNumber(); } @@ -550,8 +577,10 @@ public class Internal { /** * Extends {@link List} to add the capability to make the list immutable and inspect if it is * modifiable. + * <p> + * All implementations must support efficient random access. */ - public static interface ProtobufList<E> extends List<E> { + public static interface ProtobufList<E> extends List<E>, RandomAccess { /** * Makes this list immutable. All subsequent modifications will throw an @@ -563,6 +592,11 @@ public class Internal { * Returns whether this list can be modified via the publicly accessible {@link List} methods. */ boolean isModifiable(); + + /** + * Returns a mutable clone of this list with the specified capacity. + */ + ProtobufList<E> mutableCopyWithCapacity(int capacity); } /** @@ -577,14 +611,20 @@ public class Internal { int getInt(int index); /** - * Like {@link #add(Object)} but more efficient in that it doesn't box the element. + * Like {@link #add(Integer)} but more efficient in that it doesn't box the element. */ void addInt(int element); /** - * Like {@link #set(int, Object)} but more efficient in that it doesn't box the element. + * Like {@link #set(int, Integer)} but more efficient in that it doesn't box the element. */ int setInt(int index, int element); + + /** + * Returns a mutable clone of this list with the specified capacity. + */ + @Override + IntList mutableCopyWithCapacity(int capacity); } /** @@ -599,14 +639,20 @@ public class Internal { boolean getBoolean(int index); /** - * Like {@link #add(Object)} but more efficient in that it doesn't box the element. + * Like {@link #add(Boolean)} but more efficient in that it doesn't box the element. */ void addBoolean(boolean element); /** - * Like {@link #set(int, Object)} but more efficient in that it doesn't box the element. + * Like {@link #set(int, Boolean)} but more efficient in that it doesn't box the element. */ boolean setBoolean(int index, boolean element); + + /** + * Returns a mutable clone of this list with the specified capacity. + */ + @Override + BooleanList mutableCopyWithCapacity(int capacity); } /** @@ -621,14 +667,20 @@ public class Internal { long getLong(int index); /** - * Like {@link #add(Object)} but more efficient in that it doesn't box the element. + * Like {@link #add(Long)} but more efficient in that it doesn't box the element. */ void addLong(long element); /** - * Like {@link #set(int, Object)} but more efficient in that it doesn't box the element. + * Like {@link #set(int, Long)} but more efficient in that it doesn't box the element. */ long setLong(int index, long element); + + /** + * Returns a mutable clone of this list with the specified capacity. + */ + @Override + LongList mutableCopyWithCapacity(int capacity); } /** @@ -643,14 +695,20 @@ public class Internal { double getDouble(int index); /** - * Like {@link #add(Object)} but more efficient in that it doesn't box the element. + * Like {@link #add(Double)} but more efficient in that it doesn't box the element. */ void addDouble(double element); /** - * Like {@link #set(int, Object)} but more efficient in that it doesn't box the element. + * Like {@link #set(int, Double)} but more efficient in that it doesn't box the element. */ double setDouble(int index, double element); + + /** + * Returns a mutable clone of this list with the specified capacity. + */ + @Override + DoubleList mutableCopyWithCapacity(int capacity); } /** @@ -665,13 +723,19 @@ public class Internal { float getFloat(int index); /** - * Like {@link #add(Object)} but more efficient in that it doesn't box the element. + * Like {@link #add(Float)} but more efficient in that it doesn't box the element. */ void addFloat(float element); /** - * Like {@link #set(int, Object)} but more efficient in that it doesn't box the element. + * Like {@link #set(int, Float)} but more efficient in that it doesn't box the element. */ float setFloat(int index, float element); + + /** + * Returns a mutable clone of this list with the specified capacity. + */ + @Override + FloatList mutableCopyWithCapacity(int capacity); } } diff --git a/java/src/main/java/com/google/protobuf/InvalidProtocolBufferException.java b/java/core/src/main/java/com/google/protobuf/InvalidProtocolBufferException.java index 0a761052..85ce7b24 100644 --- a/java/src/main/java/com/google/protobuf/InvalidProtocolBufferException.java +++ b/java/core/src/main/java/com/google/protobuf/InvalidProtocolBufferException.java @@ -46,6 +46,10 @@ public class InvalidProtocolBufferException extends IOException { super(description); } + public InvalidProtocolBufferException(IOException e) { + super(e.getMessage(), e); + } + /** * Attaches an unfinished message to the exception to support best-effort * parsing in {@code Parser} interface. @@ -66,6 +70,14 @@ public class InvalidProtocolBufferException extends IOException { return unfinishedMessage; } + /** + * Unwraps the underlying {@link IOException} if this exception was caused by an I/O + * problem. Otherwise, returns {@code this}. + */ + public IOException unwrapIOException() { + return getCause() instanceof IOException ? (IOException) getCause() : this; + } + static InvalidProtocolBufferException truncatedMessage() { return new InvalidProtocolBufferException( "While parsing a protocol message, the input ended unexpectedly " + diff --git a/java/src/main/java/com/google/protobuf/LazyField.java b/java/core/src/main/java/com/google/protobuf/LazyField.java index 3da8b900..98e13ca1 100644 --- a/java/src/main/java/com/google/protobuf/LazyField.java +++ b/java/core/src/main/java/com/google/protobuf/LazyField.java @@ -95,12 +95,12 @@ public class LazyField extends LazyFieldLite { this.entry = entry; } - // @Override + @Override public K getKey() { return entry.getKey(); } - // @Override + @Override public Object getValue() { LazyField field = entry.getValue(); if (field == null) { @@ -113,7 +113,7 @@ public class LazyField extends LazyFieldLite { return entry.getValue(); } - // @Override + @Override public Object setValue(Object value) { if (!(value instanceof MessageLite)) { throw new IllegalArgumentException( @@ -131,13 +131,13 @@ public class LazyField extends LazyFieldLite { this.iterator = iterator; } - // @Override + @Override public boolean hasNext() { return iterator.hasNext(); } + @Override @SuppressWarnings("unchecked") - // @Override public Entry<K, Object> next() { Entry<K, ?> entry = iterator.next(); if (entry.getValue() instanceof LazyField) { @@ -146,7 +146,7 @@ public class LazyField extends LazyFieldLite { return (Entry<K, Object>) entry; } - // @Override + @Override public void remove() { iterator.remove(); } diff --git a/java/src/main/java/com/google/protobuf/LazyFieldLite.java b/java/core/src/main/java/com/google/protobuf/LazyFieldLite.java index eea1fe3c..2febaace 100644 --- a/java/src/main/java/com/google/protobuf/LazyFieldLite.java +++ b/java/core/src/main/java/com/google/protobuf/LazyFieldLite.java @@ -30,14 +30,26 @@ package com.google.protobuf; +import java.io.IOException; + /** * LazyFieldLite encapsulates the logic of lazily parsing message fields. It stores - * the message in a ByteString initially and then parse it on-demand. + * the message in a ByteString initially and then parses it on-demand. + * + * LazyFieldLite is thread-compatible: concurrent reads are safe once the proto that this + * LazyFieldLite is a part of is no longer being mutated by its Builder. However, explicit + * synchronization is needed under read/write situations. * - * LazyField is thread-compatible e.g. concurrent read are safe, however, - * synchronizations are needed under read/write situations. + * When a LazyFieldLite is used in the context of a MessageLite object, its behavior is considered + * to be immutable and none of the setter methods in its API are expected to be invoked. All of the + * getters are expected to be thread-safe. When used in the context of a MessageLite.Builder, + * setters can be invoked, but there is no guarantee of thread safety. + * + * TODO(yatin,dweis): Consider splitting this class's functionality and put the mutable methods + * into a separate builder class to allow us to give stronger compile-time guarantees. * - * This class is internal implementation detail, so you don't need to use it directly. + * This class is internal implementation detail of the protobuf library, so you don't need to use it + * directly. * * @author xiangl@google.com (Xiang Li) */ @@ -46,8 +58,34 @@ public class LazyFieldLite { ExtensionRegistryLite.getEmptyRegistry(); /** - * A delayed-parsed version of the bytes. When this is non-null then {@code extensionRegistry } is - * also non-null and {@code value} and {@code memoizedBytes} are null. + * The value associated with the LazyFieldLite object is stored in one or more of the following + * three fields (delayedBytes, value, memoizedBytes). They should together be interpreted as + * follows. + * 1) delayedBytes can be non-null, while value and memoizedBytes is null. The object will be in + * this state while the value for the object has not yet been parsed. + * 2) Both delayedBytes and value are non-null. The object transitions to this state as soon as + * some caller needs to access the value (by invoking getValue()). + * 3) memoizedBytes is merely an optimization for calls to LazyFieldLite.toByteString() to avoid + * recomputing the ByteString representation on each call. Instead, when the value is parsed + * from delayedBytes, we will also assign the contents of delayedBytes to memoizedBytes (since + * that is the ByteString representation of value). + * 4) Finally, if the LazyFieldLite was created directly with a parsed MessageLite value, then + * delayedBytes will be null, and memoizedBytes will be initialized only upon the first call to + * LazyFieldLite.toByteString(). + * + * Given the above conditions, any caller that needs a serialized representation of this object + * must first check if the memoizedBytes or delayedBytes ByteString is non-null and use it + * directly; if both of those are null, it can look at the parsed value field. Similarly, any + * caller that needs a parsed value must first check if the value field is already non-null, if + * not it must parse the value from delayedBytes. + */ + + /** + * A delayed-parsed version of the contents of this field. When this field is non-null, then the + * "value" field is allowed to be null until the time that the value needs to be read. + * + * When delayedBytes is non-null then {@code extensionRegistry} is required to also be non-null. + * {@code value} and {@code memoizedBytes} will be initialized lazily. */ private ByteString delayedBytes; @@ -60,12 +98,15 @@ public class LazyFieldLite { private ExtensionRegistryLite extensionRegistry; /** - * The parsed value. When this is non-null then {@code delayedBytes} will be null. + * The parsed value. When this is null and a caller needs access to the MessageLite value, then + * {@code delayedBytes} will be parsed lazily at that time. */ protected volatile MessageLite value; /** - * The memoized bytes for {@code value}. Will be null when {@code value} is null. + * The memoized bytes for {@code value}. This is an optimization for the toByteString() method to + * not have to recompute its return-value on each invocation. + * TODO(yatin): Figure out whether this optimization is actually necessary. */ private volatile ByteString memoizedBytes; @@ -94,6 +135,43 @@ public class LazyFieldLite { return lf; } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof LazyFieldLite)) { + return false; + } + + LazyFieldLite other = (LazyFieldLite) o; + + // Lazy fields do not work well with equals... If both are delayedBytes, we do not have a + // mechanism to deserialize them so we rely on bytes equality. Otherwise we coerce into an + // actual message (if necessary) and call equals on the message itself. This implies that two + // messages can by unequal but then be turned equal simply be invoking a getter on a lazy field. + MessageLite value1 = value; + MessageLite value2 = other.value; + if (value1 == null && value2 == null) { + return toByteString().equals(other.toByteString()); + } else if (value1 != null && value2 != null) { + return value1.equals(value2); + } else if (value1 != null) { + return value1.equals(other.getValue(value1.getDefaultInstanceForType())); + } else { + return getValue(value2.getDefaultInstanceForType()).equals(value2); + } + } + + @Override + public int hashCode() { + // We can't provide a memoizable hash code for lazy fields. The byte strings may have different + // hash codes but evaluate to equivalent messages. And we have no facility for constructing + // a message here if we were not already holding a value. + return 1; + } + /** * Determines whether this LazyFieldLite instance represents the default instance of this type. */ @@ -230,6 +308,46 @@ public class LazyFieldLite { return; } } + + /** + * Merges another instance's contents from a stream. + * + * <p>LazyField is not thread-safe for write access. Synchronizations are needed + * under read/write situations. + */ + public void mergeFrom(CodedInputStream input, ExtensionRegistryLite extensionRegistry) + throws IOException { + if (this.containsDefaultInstance()) { + setByteString(input.readBytes(), extensionRegistry); + return; + } + + // If the other field has an extension registry but this does not, copy over the other extension + // registry. + if (this.extensionRegistry == null) { + this.extensionRegistry = extensionRegistry; + } + + // In the case that both of them are not parsed we simply concatenate the bytes to save time. In + // the (probably rare) case that they have different extension registries there is a chance that + // some of the extensions may be dropped, but the tradeoff of making this operation fast seems + // to outway the benefits of combining the extension registries, which is not normally done for + // lite protos anyways. + if (this.delayedBytes != null) { + setByteString(this.delayedBytes.concat(input.readBytes()), this.extensionRegistry); + return; + } + + // We are parsed and both contain data. We won't drop any extensions here directly, but in the + // case that the extension registries are not the same then we might in the future if we + // need to serialize and parse a message again. + try { + setValue(value.toBuilder().mergeFrom(input, extensionRegistry).build()); + } catch (InvalidProtocolBufferException e) { + // Nothing is logged and no exceptions are thrown. Clients will be unaware that a proto + // was invalid. + } + } private static MessageLite mergeValueAndBytes( MessageLite value, ByteString otherBytes, ExtensionRegistryLite extensionRegistry) { @@ -259,10 +377,12 @@ public class LazyFieldLite { * parsed. Be careful when using this method. */ public int getSerializedSize() { - if (delayedBytes != null) { - return delayedBytes.size(); - } else if (memoizedBytes != null) { + // We *must* return delayed bytes size if it was ever set because the dependent messages may + // have memoized serialized size based off of it. + if (memoizedBytes != null) { return memoizedBytes.size(); + } else if (delayedBytes != null) { + return delayedBytes.size(); } else if (value != null) { return value.getSerializedSize(); } else { @@ -274,12 +394,14 @@ public class LazyFieldLite { * Returns a BytesString for this field in a thread-safe way. */ public ByteString toByteString() { - if (delayedBytes != null) { - return delayedBytes; - } if (memoizedBytes != null) { return memoizedBytes; } + // We *must* return delayed bytes if it was set because the dependent messages may have + // memoized serialized size based off of it. + if (delayedBytes != null) { + return delayedBytes; + } synchronized (this) { if (memoizedBytes != null) { return memoizedBytes; @@ -311,18 +433,15 @@ public class LazyFieldLite { .parseFrom(delayedBytes, extensionRegistry); this.value = parsedValue; this.memoizedBytes = delayedBytes; - this.delayedBytes = null; } else { this.value = defaultInstance; this.memoizedBytes = ByteString.EMPTY; - this.delayedBytes = null; } } catch (InvalidProtocolBufferException e) { // Nothing is logged and no exceptions are thrown. Clients will be unaware that this proto // was invalid. this.value = defaultInstance; this.memoizedBytes = ByteString.EMPTY; - this.delayedBytes = null; } } } diff --git a/java/src/main/java/com/google/protobuf/LazyStringArrayList.java b/java/core/src/main/java/com/google/protobuf/LazyStringArrayList.java index c3be3cca..d474c51e 100644 --- a/java/src/main/java/com/google/protobuf/LazyStringArrayList.java +++ b/java/core/src/main/java/com/google/protobuf/LazyStringArrayList.java @@ -30,12 +30,12 @@ package com.google.protobuf; -import java.util.Arrays; -import java.util.List; import java.util.AbstractList; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.RandomAccess; /** @@ -64,7 +64,7 @@ import java.util.RandomAccess; */ public class LazyStringArrayList extends AbstractProtobufList<String> implements LazyStringList, RandomAccess { - + private static final LazyStringArrayList EMPTY_LIST = new LazyStringArrayList(); static { EMPTY_LIST.makeImmutable(); @@ -80,11 +80,11 @@ public class LazyStringArrayList extends AbstractProtobufList<String> private final List<Object> list; public LazyStringArrayList() { - list = new ArrayList<Object>(); + this(DEFAULT_CAPACITY); } public LazyStringArrayList(int intialCapacity) { - list = new ArrayList<Object>(intialCapacity); + this(new ArrayList<Object>(intialCapacity)); } public LazyStringArrayList(LazyStringList from) { @@ -93,7 +93,21 @@ public class LazyStringArrayList extends AbstractProtobufList<String> } public LazyStringArrayList(List<String> from) { - list = new ArrayList<Object>(from); + this(new ArrayList<Object>(from)); + } + + private LazyStringArrayList(ArrayList<Object> list) { + this.list = list; + } + + @Override + public LazyStringArrayList mutableCopyWithCapacity(int capacity) { + if (capacity < size()) { + throw new IllegalArgumentException(); + } + ArrayList<Object> newList = new ArrayList<Object>(capacity); + newList.addAll(list); + return new LazyStringArrayList(newList); } @Override @@ -170,7 +184,7 @@ public class LazyStringArrayList extends AbstractProtobufList<String> return ret; } - // @Override + @Override public boolean addAllByteString(Collection<? extends ByteString> values) { ensureIsMutable(); boolean ret = list.addAll(values); @@ -178,7 +192,7 @@ public class LazyStringArrayList extends AbstractProtobufList<String> return ret; } - // @Override + @Override public boolean addAllByteArray(Collection<byte[]> c) { ensureIsMutable(); boolean ret = list.addAll(c); @@ -201,14 +215,14 @@ public class LazyStringArrayList extends AbstractProtobufList<String> modCount++; } - // @Override + @Override public void add(ByteString element) { ensureIsMutable(); list.add(element); modCount++; } - // @Override + @Override public void add(byte[] element) { ensureIsMutable(); list.add(element); @@ -220,7 +234,7 @@ public class LazyStringArrayList extends AbstractProtobufList<String> return list.get(index); } - // @Override + @Override public ByteString getByteString(int index) { Object o = list.get(index); ByteString b = asByteString(o); @@ -230,7 +244,7 @@ public class LazyStringArrayList extends AbstractProtobufList<String> return b; } - // @Override + @Override public byte[] getByteArray(int index) { Object o = list.get(index); byte[] b = asByteArray(o); @@ -240,7 +254,7 @@ public class LazyStringArrayList extends AbstractProtobufList<String> return b; } - // @Override + @Override public void set(int index, ByteString s) { setAndReturn(index, s); } @@ -250,7 +264,7 @@ public class LazyStringArrayList extends AbstractProtobufList<String> return list.set(index, s); } - // @Override + @Override public void set(int index, byte[] s) { setAndReturn(index, s); } @@ -290,12 +304,12 @@ public class LazyStringArrayList extends AbstractProtobufList<String> } } - // @Override + @Override public List<?> getUnderlyingElements() { return Collections.unmodifiableList(list); } - // @Override + @Override public void mergeFrom(LazyStringList other) { ensureIsMutable(); for (Object o : other.getUnderlyingElements()) { @@ -349,7 +363,7 @@ public class LazyStringArrayList extends AbstractProtobufList<String> } } - // @Override + @Override public List<byte[]> asByteArrayList() { return new ByteArrayListView(this); } @@ -393,12 +407,12 @@ public class LazyStringArrayList extends AbstractProtobufList<String> } } - // @Override + @Override public List<ByteString> asByteStringList() { return new ByteStringListView(this); } - // @Override + @Override public LazyStringList getUnmodifiableView() { if (isModifiable()) { return new UnmodifiableLazyStringList(this); diff --git a/java/src/main/java/com/google/protobuf/LazyStringList.java b/java/core/src/main/java/com/google/protobuf/LazyStringList.java index 3eeedca1..3eeedca1 100644 --- a/java/src/main/java/com/google/protobuf/LazyStringList.java +++ b/java/core/src/main/java/com/google/protobuf/LazyStringList.java diff --git a/java/src/main/java/com/google/protobuf/LongArrayList.java b/java/core/src/main/java/com/google/protobuf/LongArrayList.java index 298617ff..bc4475d1 100644 --- a/java/src/main/java/com/google/protobuf/LongArrayList.java +++ b/java/core/src/main/java/com/google/protobuf/LongArrayList.java @@ -34,7 +34,6 @@ import com.google.protobuf.Internal.LongList; import java.util.Arrays; import java.util.Collection; -import java.util.List; import java.util.RandomAccess; /** @@ -44,8 +43,6 @@ import java.util.RandomAccess; */ final class LongArrayList extends AbstractProtobufList<Long> implements LongList, RandomAccess { - private static final int DEFAULT_CAPACITY = 10; - private static final LongArrayList EMPTY_LIST = new LongArrayList(); static { EMPTY_LIST.makeImmutable(); @@ -67,28 +64,58 @@ final class LongArrayList extends AbstractProtobufList<Long> implements LongList private int size; /** - * Constructs a new mutable {@code LongArrayList}. + * Constructs a new mutable {@code LongArrayList} with default capacity. */ LongArrayList() { - array = new long[DEFAULT_CAPACITY]; - size = 0; + this(new long[DEFAULT_CAPACITY], 0); } /** * Constructs a new mutable {@code LongArrayList} containing the same elements as {@code other}. */ - LongArrayList(List<Long> other) { - if (other instanceof LongArrayList) { - LongArrayList list = (LongArrayList) other; - array = list.array.clone(); - size = list.size; - } else { - size = other.size(); - array = new long[size]; - for (int i = 0; i < size; i++) { - array[i] = other.get(i); + private LongArrayList(long[] array, int size) { + this.array = array; + this.size = size; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof IntArrayList)) { + return super.equals(o); + } + LongArrayList other = (LongArrayList) o; + if (size != other.size) { + return false; + } + + final long[] arr = other.array; + for (int i = 0; i < size; i++) { + if (array[i] != arr[i]) { + return false; } } + + return true; + } + + @Override + public int hashCode() { + int result = 1; + for (int i = 0; i < size; i++) { + result = (31 * result) + Internal.hashLong(array[i]); + } + return result; + } + + @Override + public LongList mutableCopyWithCapacity(int capacity) { + if (capacity < size) { + throw new IllegalArgumentException(); + } + return new LongArrayList(Arrays.copyOf(array, capacity), size); } @Override diff --git a/java/src/main/java/com/google/protobuf/MapEntry.java b/java/core/src/main/java/com/google/protobuf/MapEntry.java index 31414bb4..31414bb4 100644 --- a/java/src/main/java/com/google/protobuf/MapEntry.java +++ b/java/core/src/main/java/com/google/protobuf/MapEntry.java diff --git a/java/src/main/java/com/google/protobuf/MapEntryLite.java b/java/core/src/main/java/com/google/protobuf/MapEntryLite.java index bcffa946..12c64abb 100644 --- a/java/src/main/java/com/google/protobuf/MapEntryLite.java +++ b/java/core/src/main/java/com/google/protobuf/MapEntryLite.java @@ -41,7 +41,8 @@ import java.io.IOException; * * Protobuf internal. Users shouldn't use. */ -public class MapEntryLite<K, V> extends AbstractMessageLite { +public class MapEntryLite<K, V> + extends AbstractMessageLite<MapEntryLite<K, V>, MapEntryLite.Builder<K, V>> { private static class Metadata<K, V> { public final MapEntryLite<K, V> defaultInstance; public final WireFormat.FieldType keyType; @@ -233,7 +234,7 @@ public class MapEntryLite<K, V> extends AbstractMessageLite { * Builder used to create {@link MapEntryLite} messages. */ public static class Builder<K, V> - extends AbstractMessageLite.Builder<Builder<K, V>> { + extends AbstractMessageLite.Builder<MapEntryLite<K, V>, Builder<K, V>> { private final Metadata<K, V> metadata; private K key; private V value; @@ -327,5 +328,10 @@ public class MapEntryLite<K, V> extends AbstractMessageLite { this.value = entry.value; return this; } + + @Override + protected Builder<K, V> internalMergeFrom(MapEntryLite<K, V> message) { + throw new UnsupportedOperationException(); + } } } diff --git a/java/src/main/java/com/google/protobuf/MapField.java b/java/core/src/main/java/com/google/protobuf/MapField.java index b290993c..907f0f71 100644 --- a/java/src/main/java/com/google/protobuf/MapField.java +++ b/java/core/src/main/java/com/google/protobuf/MapField.java @@ -93,15 +93,18 @@ public class MapField<K, V> implements MutabilityOracle { this.defaultEntry = defaultEntry; } + @Override public Message convertKeyAndValueToMessage(K key, V value) { return defaultEntry.newBuilderForType().setKey(key).setValue(value).buildPartial(); } + @Override public void convertMessageToKeyAndValue(Message message, Map<K, V> map) { MapEntry<K, V> entry = (MapEntry<K, V>) message; map.put(entry.getKey(), entry.getValue()); } + @Override public Message getMessageDefaultInstance() { return defaultEntry; } diff --git a/java/src/main/java/com/google/protobuf/MapFieldLite.java b/java/core/src/main/java/com/google/protobuf/MapFieldLite.java index c17fa7b1..960b6339 100644 --- a/java/src/main/java/com/google/protobuf/MapFieldLite.java +++ b/java/core/src/main/java/com/google/protobuf/MapFieldLite.java @@ -30,6 +30,8 @@ package com.google.protobuf; +import com.google.protobuf.Internal.EnumLite; + import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -44,7 +46,7 @@ import java.util.Set; * This class is a protobuf implementation detail. Users shouldn't use this * class directly. */ -public class MapFieldLite<K, V> implements MutabilityOracle { +public final class MapFieldLite<K, V> implements MutabilityOracle { private MutatabilityAwareMap<K, V> mapData; private boolean isMutable; @@ -134,10 +136,11 @@ public class MapFieldLite<K, V> implements MutabilityOracle { private static int calculateHashCodeForObject(Object a) { if (a instanceof byte[]) { - return LiteralByteString.hashCode((byte[]) a); + return Internal.hashCode((byte[]) a); } - if (a instanceof Internal.EnumLite) { - return Internal.hashEnum((Internal.EnumLite) a); + // Enums should be stored as integers internally. + if (a instanceof EnumLite) { + throw new UnsupportedOperationException(); } return a.hashCode(); } diff --git a/java/src/main/java/com/google/protobuf/Message.java b/java/core/src/main/java/com/google/protobuf/Message.java index 9516d71f..94590fb9 100644 --- a/java/src/main/java/com/google/protobuf/Message.java +++ b/java/core/src/main/java/com/google/protobuf/Message.java @@ -51,6 +51,7 @@ import java.util.Map; public interface Message extends MessageLite, MessageOrBuilder { // (From MessageLite, re-declared here only for return type covariance.) + @Override Parser<? extends Message> getParserForType(); @@ -97,7 +98,10 @@ public interface Message extends MessageLite, MessageOrBuilder { // Builders // (From MessageLite, re-declared here only for return type covariance.) + @Override Builder newBuilderForType(); + + @Override Builder toBuilder(); /** @@ -106,6 +110,7 @@ public interface Message extends MessageLite, MessageOrBuilder { interface Builder extends MessageLite.Builder, MessageOrBuilder { // (From MessageLite.Builder, re-declared here only for return type // covariance.) + @Override Builder clear(); /** @@ -131,18 +136,27 @@ public interface Message extends MessageLite, MessageOrBuilder { // (From MessageLite.Builder, re-declared here only for return type // covariance.) + @Override Message build(); + + @Override Message buildPartial(); + + @Override Builder clone(); + + @Override Builder mergeFrom(CodedInputStream input) throws IOException; - Builder mergeFrom(CodedInputStream input, - ExtensionRegistryLite extensionRegistry) - throws IOException; + + @Override + Builder mergeFrom(CodedInputStream input, ExtensionRegistryLite extensionRegistry) + throws IOException; /** * Get the message's type's descriptor. * See {@link Message#getDescriptorForType()}. */ + @Override Descriptors.Descriptor getDescriptorForType(); /** @@ -240,27 +254,39 @@ public interface Message extends MessageLite, MessageOrBuilder { // (From MessageLite.Builder, re-declared here only for return type // covariance.) + @Override Builder mergeFrom(ByteString data) throws InvalidProtocolBufferException; - Builder mergeFrom(ByteString data, - ExtensionRegistryLite extensionRegistry) - throws InvalidProtocolBufferException; + + @Override + Builder mergeFrom(ByteString data, ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException; + + @Override Builder mergeFrom(byte[] data) throws InvalidProtocolBufferException; - Builder mergeFrom(byte[] data, int off, int len) - throws InvalidProtocolBufferException; - Builder mergeFrom(byte[] data, - ExtensionRegistryLite extensionRegistry) - throws InvalidProtocolBufferException; - Builder mergeFrom(byte[] data, int off, int len, - ExtensionRegistryLite extensionRegistry) - throws InvalidProtocolBufferException; + + @Override + Builder mergeFrom(byte[] data, int off, int len) throws InvalidProtocolBufferException; + + @Override + Builder mergeFrom(byte[] data, ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException; + + @Override + Builder mergeFrom(byte[] data, int off, int len, ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException; + + @Override Builder mergeFrom(InputStream input) throws IOException; - Builder mergeFrom(InputStream input, - ExtensionRegistryLite extensionRegistry) - throws IOException; - boolean mergeDelimitedFrom(InputStream input) - throws IOException; - boolean mergeDelimitedFrom(InputStream input, - ExtensionRegistryLite extensionRegistry) - throws IOException; + + @Override + Builder mergeFrom(InputStream input, ExtensionRegistryLite extensionRegistry) + throws IOException; + + @Override + boolean mergeDelimitedFrom(InputStream input) throws IOException; + + @Override + boolean mergeDelimitedFrom(InputStream input, ExtensionRegistryLite extensionRegistry) + throws IOException; } } diff --git a/java/src/main/java/com/google/protobuf/MessageLite.java b/java/core/src/main/java/com/google/protobuf/MessageLite.java index 798b7943..88f531df 100644 --- a/java/src/main/java/com/google/protobuf/MessageLite.java +++ b/java/core/src/main/java/com/google/protobuf/MessageLite.java @@ -295,6 +295,27 @@ public interface MessageLite extends MessageLiteOrBuilder { Builder mergeFrom(InputStream input, ExtensionRegistryLite extensionRegistry) throws IOException; + + /** + * Merge {@code other} into the message being built. {@code other} must + * have the exact same type as {@code this} (i.e. + * {@code getClass().equals(getDefaultInstanceForType().getClass())}). + * + * Merging occurs as follows. For each field:<br> + * * For singular primitive fields, if the field is set in {@code other}, + * then {@code other}'s value overwrites the value in this message.<br> + * * For singular message fields, if the field is set in {@code other}, + * it is merged into the corresponding sub-message of this message + * using the same merging rules.<br> + * * For repeated fields, the elements in {@code other} are concatenated + * with the elements in this message. + * * For oneof groups, if the other message has one of the fields set, + * the group of this message is cleared and replaced by the field + * of the other message, so that the oneof constraint is preserved. + * + * This is equivalent to the {@code Message::MergeFrom} method in C++. + */ + Builder mergeFrom(MessageLite other); /** * Like {@link #mergeFrom(InputStream)}, but does not read until EOF. diff --git a/java/src/main/java/com/google/protobuf/MessageLiteOrBuilder.java b/java/core/src/main/java/com/google/protobuf/MessageLiteOrBuilder.java index 818386ce..818386ce 100644 --- a/java/src/main/java/com/google/protobuf/MessageLiteOrBuilder.java +++ b/java/core/src/main/java/com/google/protobuf/MessageLiteOrBuilder.java diff --git a/java/core/src/main/java/com/google/protobuf/MessageLiteToString.java b/java/core/src/main/java/com/google/protobuf/MessageLiteToString.java new file mode 100644 index 00000000..43847651 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/MessageLiteToString.java @@ -0,0 +1,239 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// 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.lang.reflect.Method; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +/** + * Helps generate {@link String} representations of {@link MessageLite} protos. + */ +// TODO(dweis): Fix map fields. +final class MessageLiteToString { + + private static final String LIST_SUFFIX = "List"; + private static final String BUILDER_LIST_SUFFIX = "OrBuilderList"; + private static final String BYTES_SUFFIX = "Bytes"; + + /** + * Returns a {@link String} representation of the {@link MessageLite} object. The first line of + * the {@code String} representation representation includes a comment string to uniquely identify + * the objcet instance. This acts as an indicator that this should not be relied on for + * comparisons. + * + * <p>For use by generated code only. + */ + static String toString(MessageLite messageLite, String commentString) { + StringBuilder buffer = new StringBuilder(); + buffer.append("# ").append(commentString); + reflectivePrintWithIndent(messageLite, buffer, 0); + return buffer.toString(); + } + + /** + * Reflectively prints the {@link MessageLite} to the buffer at given {@code indent} level. + * + * @param buffer the buffer to write to + * @param indent the number of spaces to indent the proto by + */ + private static void reflectivePrintWithIndent( + MessageLite messageLite, StringBuilder buffer, int indent) { + // Build a map of method name to method. We're looking for methods like getFoo(), hasFoo(), and + // getFooList() which might be useful for building an object's string representation. + Map<String, Method> nameToNoArgMethod = new HashMap<String, Method>(); + Map<String, Method> nameToMethod = new HashMap<String, Method>(); + Set<String> getters = new TreeSet<String>(); + for (Method method : messageLite.getClass().getDeclaredMethods()) { + nameToMethod.put(method.getName(), method); + if (method.getParameterTypes().length == 0) { + nameToNoArgMethod.put(method.getName(), method); + + if (method.getName().startsWith("get")) { + getters.add(method.getName()); + } + } + } + + for (String getter : getters) { + String suffix = getter.replaceFirst("get", ""); + if (suffix.endsWith(LIST_SUFFIX) && !suffix.endsWith(BUILDER_LIST_SUFFIX)) { + String camelCase = suffix.substring(0, 1).toLowerCase() + + suffix.substring(1, suffix.length() - LIST_SUFFIX.length()); + // Try to reflectively get the value and toString() the field as if it were repeated. This + // only works if the method names have not be proguarded out or renamed. + Method listMethod = nameToNoArgMethod.get("get" + suffix); + if (listMethod != null) { + printField( + buffer, + indent, + camelCaseToSnakeCase(camelCase), + GeneratedMessageLite.invokeOrDie(listMethod, messageLite)); + continue; + } + } + + Method setter = nameToMethod.get("set" + suffix); + if (setter == null) { + continue; + } + if (suffix.endsWith(BYTES_SUFFIX) + && nameToNoArgMethod.containsKey( + "get" + suffix.substring(0, suffix.length() - "Bytes".length()))) { + // Heuristic to skip bytes based accessors for string fields. + continue; + } + + String camelCase = suffix.substring(0, 1).toLowerCase() + suffix.substring(1); + + // Try to reflectively get the value and toString() the field as if it were optional. This + // only works if the method names have not be proguarded out or renamed. + Method getMethod = nameToNoArgMethod.get("get" + suffix); + Method hasMethod = nameToNoArgMethod.get("has" + suffix); + // TODO(dweis): Fix proto3 semantics. + if (getMethod != null) { + Object value = GeneratedMessageLite.invokeOrDie(getMethod, messageLite); + final boolean hasValue = hasMethod == null + ? !isDefaultValue(value) + : (Boolean) GeneratedMessageLite.invokeOrDie(hasMethod, messageLite); + // TODO(dweis): This doesn't stop printing oneof case twice: value and enum style. + if (hasValue) { + printField( + buffer, + indent, + camelCaseToSnakeCase(camelCase), + value); + } + continue; + } + } + + if (messageLite instanceof GeneratedMessageLite.ExtendableMessage) { + Iterator<Map.Entry<GeneratedMessageLite.ExtensionDescriptor, Object>> iter = + ((GeneratedMessageLite.ExtendableMessage<?, ?>) messageLite).extensions.iterator(); + while (iter.hasNext()) { + Map.Entry<GeneratedMessageLite.ExtensionDescriptor, Object> entry = iter.next(); + printField(buffer, indent, "[" + entry.getKey().getNumber() + "]", entry.getValue()); + } + } + + if (((GeneratedMessageLite<?, ?>) messageLite).unknownFields != null) { + ((GeneratedMessageLite<?, ?>) messageLite).unknownFields.printWithIndent(buffer, indent); + } + } + + private static boolean isDefaultValue(Object o) { + if (o instanceof Boolean) { + return !((Boolean) o); + } + if (o instanceof Integer) { + return ((Integer) o) == 0; + } + if (o instanceof Float) { + return ((Float) o) == 0f; + } + if (o instanceof Double) { + return ((Double) o) == 0d; + } + if (o instanceof String) { + return o.equals(""); + } + if (o instanceof ByteString) { + return o.equals(ByteString.EMPTY); + } + if (o instanceof MessageLite) { // Can happen in oneofs. + return o == ((MessageLite) o).getDefaultInstanceForType(); + } + if (o instanceof java.lang.Enum<?>) { // Catches oneof enums. + return ((java.lang.Enum<?>) o).ordinal() == 0; + } + + return false; + } + + /** + * Formats a text proto field. + * + * <p>For use by generated code only. + * + * @param buffer the buffer to write to + * @param indent the number of spaces the proto should be indented by + * @param name the field name (in lower underscore case) + * @param object the object value of the field + */ + static final void printField(StringBuilder buffer, int indent, String name, Object object) { + if (object instanceof List<?>) { + List<?> list = (List<?>) object; + for (Object entry : list) { + printField(buffer, indent, name, entry); + } + return; + } + + buffer.append('\n'); + for (int i = 0; i < indent; i++) { + buffer.append(' '); + } + buffer.append(name); + + if (object instanceof String) { + buffer.append(": \"").append(TextFormatEscaper.escapeText((String) object)).append('"'); + } else if (object instanceof ByteString) { + buffer.append(": \"").append(TextFormatEscaper.escapeBytes((ByteString) object)).append('"'); + } else if (object instanceof GeneratedMessageLite) { + buffer.append(" {"); + reflectivePrintWithIndent((GeneratedMessageLite<?, ?>) object, buffer, indent + 2); + buffer.append("\n"); + for (int i = 0; i < indent; i++) { + buffer.append(' '); + } + buffer.append("}"); + } else { + buffer.append(": ").append(object.toString()); + } + } + + private static final String camelCaseToSnakeCase(String camelCase) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < camelCase.length(); i++) { + char ch = camelCase.charAt(i); + if (Character.isUpperCase(ch)) { + builder.append("_"); + } + builder.append(Character.toLowerCase(ch)); + } + return builder.toString(); + } +} diff --git a/java/src/main/java/com/google/protobuf/MessageOrBuilder.java b/java/core/src/main/java/com/google/protobuf/MessageOrBuilder.java index f0fc4859..5e7d7821 100644 --- a/java/src/main/java/com/google/protobuf/MessageOrBuilder.java +++ b/java/core/src/main/java/com/google/protobuf/MessageOrBuilder.java @@ -42,7 +42,7 @@ import java.util.Map; public interface MessageOrBuilder extends MessageLiteOrBuilder { // (From MessageLite, re-declared here only for return type covariance.) - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override Message getDefaultInstanceForType(); /** diff --git a/java/src/main/java/com/google/protobuf/MessageReflection.java b/java/core/src/main/java/com/google/protobuf/MessageReflection.java index de4bfd3e..7b791d9e 100644 --- a/java/src/main/java/com/google/protobuf/MessageReflection.java +++ b/java/core/src/main/java/com/google/protobuf/MessageReflection.java @@ -364,12 +364,14 @@ class MessageReflection { * Finishes the merge and returns the underlying object. */ Object finish(); + } static class BuilderAdapter implements MergeTarget { private final Message.Builder builder; + @Override public Descriptors.Descriptor getDescriptorForType() { return builder.getDescriptorForType(); } @@ -378,6 +380,7 @@ class MessageReflection { this.builder = builder; } + @Override public Object getField(Descriptors.FieldDescriptor field) { return builder.getField(field); } @@ -387,25 +390,27 @@ class MessageReflection { return builder.hasField(field); } - public MergeTarget setField(Descriptors.FieldDescriptor field, - Object value) { + @Override + public MergeTarget setField(Descriptors.FieldDescriptor field, Object value) { builder.setField(field, value); return this; } + @Override public MergeTarget clearField(Descriptors.FieldDescriptor field) { builder.clearField(field); return this; } + @Override 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) { + @Override + public MergeTarget addRepeatedField(Descriptors.FieldDescriptor field, Object value) { builder.addRepeatedField(field, value); return this; } @@ -426,25 +431,30 @@ class MessageReflection { return builder.getOneofFieldDescriptor(oneof); } + @Override public ContainerType getContainerType() { return ContainerType.MESSAGE; } + @Override public ExtensionRegistry.ExtensionInfo findExtensionByName( ExtensionRegistry registry, String name) { return registry.findImmutableExtensionByName(name); } + @Override public ExtensionRegistry.ExtensionInfo findExtensionByNumber( - ExtensionRegistry registry, Descriptors.Descriptor containingType, - int fieldNumber) { + ExtensionRegistry registry, Descriptors.Descriptor containingType, int fieldNumber) { return registry.findImmutableExtensionByNumber(containingType, fieldNumber); } - public Object parseGroup(CodedInputStream input, + @Override + public Object parseGroup( + CodedInputStream input, ExtensionRegistryLite extensionRegistry, - Descriptors.FieldDescriptor field, Message defaultInstance) + Descriptors.FieldDescriptor field, + Message defaultInstance) throws IOException { Message.Builder subBuilder; // When default instance is not null. The field is an extension field. @@ -463,9 +473,12 @@ class MessageReflection { return subBuilder.buildPartial(); } - public Object parseMessage(CodedInputStream input, + @Override + public Object parseMessage( + CodedInputStream input, ExtensionRegistryLite extensionRegistry, - Descriptors.FieldDescriptor field, Message defaultInstance) + Descriptors.FieldDescriptor field, + Message defaultInstance) throws IOException { Message.Builder subBuilder; // When default instance is not null. The field is an extension field. @@ -484,9 +497,12 @@ class MessageReflection { return subBuilder.buildPartial(); } - public Object parseMessageFromBytes(ByteString bytes, + @Override + public Object parseMessageFromBytes( + ByteString bytes, ExtensionRegistryLite extensionRegistry, - Descriptors.FieldDescriptor field, Message defaultInstance) + Descriptors.FieldDescriptor field, + Message defaultInstance) throws IOException { Message.Builder subBuilder; // When default instance is not null. The field is an extension field. @@ -505,8 +521,9 @@ class MessageReflection { return subBuilder.buildPartial(); } - public MergeTarget newMergeTargetForField(Descriptors.FieldDescriptor field, - Message defaultInstance) { + @Override + public MergeTarget newMergeTargetForField( + Descriptors.FieldDescriptor field, Message defaultInstance) { if (defaultInstance != null) { return new BuilderAdapter( defaultInstance.newBuilderForType()); @@ -515,8 +532,8 @@ class MessageReflection { } } - public WireFormat.Utf8Validation - getUtf8Validation(Descriptors.FieldDescriptor descriptor) { + @Override + public WireFormat.Utf8Validation getUtf8Validation(Descriptors.FieldDescriptor descriptor) { if (descriptor.needsUtf8Check()) { return WireFormat.Utf8Validation.STRICT; } @@ -528,9 +545,11 @@ class MessageReflection { return WireFormat.Utf8Validation.LOOSE; } + @Override public Object finish() { return builder.buildPartial(); } + } @@ -542,38 +561,43 @@ class MessageReflection { this.extensions = extensions; } + @Override public Descriptors.Descriptor getDescriptorForType() { throw new UnsupportedOperationException( "getDescriptorForType() called on FieldSet object"); } + @Override public Object getField(Descriptors.FieldDescriptor field) { return extensions.getField(field); } + @Override public boolean hasField(Descriptors.FieldDescriptor field) { return extensions.hasField(field); } - public MergeTarget setField(Descriptors.FieldDescriptor field, - Object value) { + @Override + public MergeTarget setField(Descriptors.FieldDescriptor field, Object value) { extensions.setField(field, value); return this; } + @Override public MergeTarget clearField(Descriptors.FieldDescriptor field) { extensions.clearField(field); return this; } + @Override 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) { + @Override + public MergeTarget addRepeatedField(Descriptors.FieldDescriptor field, Object value) { extensions.addRepeatedField(field, value); return this; } @@ -594,25 +618,31 @@ class MessageReflection { return null; } + @Override public ContainerType getContainerType() { return ContainerType.EXTENSION_SET; } + @Override public ExtensionRegistry.ExtensionInfo findExtensionByName( ExtensionRegistry registry, String name) { return registry.findImmutableExtensionByName(name); } + @Override public ExtensionRegistry.ExtensionInfo findExtensionByNumber( - ExtensionRegistry registry, Descriptors.Descriptor containingType, - int fieldNumber) { + 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 { + @Override + public Object parseGroup( + CodedInputStream input, + ExtensionRegistryLite registry, + Descriptors.FieldDescriptor field, + Message defaultInstance) + throws IOException { Message.Builder subBuilder = defaultInstance.newBuilderForType(); if (!field.isRepeated()) { @@ -625,9 +655,13 @@ class MessageReflection { return subBuilder.buildPartial(); } - public Object parseMessage(CodedInputStream input, - ExtensionRegistryLite registry, Descriptors.FieldDescriptor field, - Message defaultInstance) throws IOException { + @Override + public Object parseMessage( + CodedInputStream input, + ExtensionRegistryLite registry, + Descriptors.FieldDescriptor field, + Message defaultInstance) + throws IOException { Message.Builder subBuilder = defaultInstance.newBuilderForType(); if (!field.isRepeated()) { @@ -640,9 +674,13 @@ class MessageReflection { return subBuilder.buildPartial(); } - public Object parseMessageFromBytes(ByteString bytes, - ExtensionRegistryLite registry, Descriptors.FieldDescriptor field, - Message defaultInstance) throws IOException { + @Override + 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); @@ -654,14 +692,15 @@ class MessageReflection { return subBuilder.buildPartial(); } + @Override public MergeTarget newMergeTargetForField( Descriptors.FieldDescriptor descriptor, Message defaultInstance) { throw new UnsupportedOperationException( "newMergeTargetForField() called on FieldSet object"); } - public WireFormat.Utf8Validation - getUtf8Validation(Descriptors.FieldDescriptor descriptor) { + @Override + public WireFormat.Utf8Validation getUtf8Validation(Descriptors.FieldDescriptor descriptor) { if (descriptor.needsUtf8Check()) { return WireFormat.Utf8Validation.STRICT; } @@ -669,10 +708,12 @@ class MessageReflection { return WireFormat.Utf8Validation.LOOSE; } + @Override public Object finish() { throw new UnsupportedOperationException( "finish() called on FieldSet object"); } + } /** diff --git a/java/src/main/java/com/google/protobuf/MutabilityOracle.java b/java/core/src/main/java/com/google/protobuf/MutabilityOracle.java index 82b723c9..82b723c9 100644 --- a/java/src/main/java/com/google/protobuf/MutabilityOracle.java +++ b/java/core/src/main/java/com/google/protobuf/MutabilityOracle.java diff --git a/java/core/src/main/java/com/google/protobuf/NioByteString.java b/java/core/src/main/java/com/google/protobuf/NioByteString.java new file mode 100644 index 00000000..6163c7b1 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/NioByteString.java @@ -0,0 +1,290 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// 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.io.IOException; +import java.io.InputStream; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.InvalidMarkException; +import java.nio.charset.Charset; +import java.util.Collections; +import java.util.List; + +/** + * A {@link ByteString} that wraps around a {@link ByteBuffer}. + */ +final class NioByteString extends ByteString.LeafByteString { + private final ByteBuffer buffer; + + NioByteString(ByteBuffer buffer) { + if (buffer == null) { + throw new NullPointerException("buffer"); + } + + this.buffer = buffer.slice().order(ByteOrder.nativeOrder()); + } + + // ================================================================= + // Serializable + + /** + * Magic method that lets us override serialization behavior. + */ + private Object writeReplace() { + return ByteString.copyFrom(buffer.slice()); + } + + /** + * Magic method that lets us override deserialization behavior. + */ + private void readObject(@SuppressWarnings("unused") ObjectInputStream in) throws IOException { + throw new InvalidObjectException("NioByteString instances are not to be serialized directly"); + } + + // ================================================================= + + @Override + public byte byteAt(int index) { + try { + return buffer.get(index); + } catch (ArrayIndexOutOfBoundsException e) { + throw e; + } catch (IndexOutOfBoundsException e) { + throw new ArrayIndexOutOfBoundsException(e.getMessage()); + } + } + + @Override + public int size() { + return buffer.remaining(); + } + + @Override + public ByteString substring(int beginIndex, int endIndex) { + try { + ByteBuffer slice = slice(beginIndex, endIndex); + return new NioByteString(slice); + } catch (ArrayIndexOutOfBoundsException e) { + throw e; + } catch (IndexOutOfBoundsException e) { + throw new ArrayIndexOutOfBoundsException(e.getMessage()); + } + } + + @Override + protected void copyToInternal( + byte[] target, int sourceOffset, int targetOffset, int numberToCopy) { + ByteBuffer slice = buffer.slice(); + slice.position(sourceOffset); + slice.get(target, targetOffset, numberToCopy); + } + + @Override + public void copyTo(ByteBuffer target) { + target.put(buffer.slice()); + } + + @Override + public void writeTo(OutputStream out) throws IOException { + out.write(toByteArray()); + } + + @Override + boolean equalsRange(ByteString other, int offset, int length) { + return substring(0, length).equals(other.substring(offset, offset + length)); + } + + @Override + void writeToInternal(OutputStream out, int sourceOffset, int numberToWrite) throws IOException { + if (buffer.hasArray()) { + // Optimized write for array-backed buffers. + // Note that we're taking the risk that a malicious OutputStream could modify the array. + int bufferOffset = buffer.arrayOffset() + buffer.position() + sourceOffset; + out.write(buffer.array(), bufferOffset, numberToWrite); + return; + } + + ByteBufferWriter.write(slice(sourceOffset, sourceOffset + numberToWrite), out); + } + + @Override + void writeTo(ByteOutput output) throws IOException { + output.writeLazy(buffer.slice()); + } + + @Override + public ByteBuffer asReadOnlyByteBuffer() { + return buffer.asReadOnlyBuffer(); + } + + @Override + public List<ByteBuffer> asReadOnlyByteBufferList() { + return Collections.singletonList(asReadOnlyByteBuffer()); + } + + @Override + protected String toStringInternal(Charset charset) { + final byte[] bytes; + final int offset; + final int length; + if (buffer.hasArray()) { + bytes = buffer.array(); + offset = buffer.arrayOffset() + buffer.position(); + length = buffer.remaining(); + } else { + // TODO(nathanmittler): Can we optimize this? + bytes = toByteArray(); + offset = 0; + length = bytes.length; + } + return new String(bytes, offset, length, charset); + } + + @Override + public boolean isValidUtf8() { + return Utf8.isValidUtf8(buffer); + } + + @Override + protected int partialIsValidUtf8(int state, int offset, int length) { + return Utf8.partialIsValidUtf8(state, buffer, offset, offset + length); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (!(other instanceof ByteString)) { + return false; + } + ByteString otherString = ((ByteString) other); + if (size() != otherString.size()) { + return false; + } + if (size() == 0) { + return true; + } + if (other instanceof NioByteString) { + return buffer.equals(((NioByteString) other).buffer); + } + if (other instanceof RopeByteString) { + return other.equals(this); + } + return buffer.equals(otherString.asReadOnlyByteBuffer()); + } + + @Override + protected int partialHash(int h, int offset, int length) { + for (int i = offset; i < offset + length; i++) { + h = h * 31 + buffer.get(i); + } + return h; + } + + @Override + public InputStream newInput() { + return new InputStream() { + private final ByteBuffer buf = buffer.slice(); + + @Override + public void mark(int readlimit) { + buf.mark(); + } + + @Override + public boolean markSupported() { + return true; + } + + @Override + public void reset() throws IOException { + try { + buf.reset(); + } catch (InvalidMarkException e) { + throw new IOException(e); + } + } + + @Override + public int available() throws IOException { + return buf.remaining(); + } + + @Override + public int read() throws IOException { + if (!buf.hasRemaining()) { + return -1; + } + return buf.get() & 0xFF; + } + + @Override + public int read(byte[] bytes, int off, int len) throws IOException { + if (!buf.hasRemaining()) { + return -1; + } + + len = Math.min(len, buf.remaining()); + buf.get(bytes, off, len); + return len; + } + }; + } + + @Override + public CodedInputStream newCodedInput() { + return CodedInputStream.newInstance(buffer); + } + + /** + * Creates a slice of a range of this buffer. + * + * @param beginIndex the beginning index of the slice (inclusive). + * @param endIndex the end index of the slice (exclusive). + * @return the requested slice. + */ + private ByteBuffer slice(int beginIndex, int endIndex) { + if (beginIndex < buffer.position() || endIndex > buffer.limit() || beginIndex > endIndex) { + throw new IllegalArgumentException( + String.format("Invalid indices [%d, %d]", beginIndex, endIndex)); + } + + ByteBuffer slice = buffer.slice(); + slice.position(beginIndex - buffer.position()); + slice.limit(endIndex - buffer.position()); + return slice; + } +} diff --git a/java/src/main/java/com/google/protobuf/Parser.java b/java/core/src/main/java/com/google/protobuf/Parser.java index 227c02b7..6db69247 100644 --- a/java/src/main/java/com/google/protobuf/Parser.java +++ b/java/core/src/main/java/com/google/protobuf/Parser.java @@ -37,9 +37,20 @@ import java.io.InputStream; * * The implementation should be stateless and thread-safe. * + * <p>All methods may throw {@link InvalidProtocolBufferException}. In the event of invalid data, + * like an encoding error, the cause of the thrown exception will be {@code null}. However, if an + * I/O problem occurs, an exception is thrown with an {@link IOException} cause. + * * @author liujisi@google.com (Pherl Liu) */ public interface Parser<MessageType> { + + // NB(jh): Other parts of the protobuf API that parse messages distinguish between an I/O problem + // (like failure reading bytes from a socket) and invalid data (encoding error) via the type of + // thrown exception. But it would be source-incompatible to make the methods in this interface do + // so since they were originally spec'ed to only throw InvalidProtocolBufferException. So callers + // must inspect the cause of the exception to distinguish these two cases. + /** * Parses a message of {@code MessageType} from the input. * diff --git a/java/src/main/java/com/google/protobuf/ProtobufArrayList.java b/java/core/src/main/java/com/google/protobuf/ProtobufArrayList.java index 759368c9..81255ec2 100644 --- a/java/src/main/java/com/google/protobuf/ProtobufArrayList.java +++ b/java/core/src/main/java/com/google/protobuf/ProtobufArrayList.java @@ -38,7 +38,7 @@ import java.util.List; /** * Implements {@link ProtobufList} for non-primitive and {@link String} types. */ -class ProtobufArrayList<E> extends AbstractProtobufList<E> { +final class ProtobufArrayList<E> extends AbstractProtobufList<E> { private static final ProtobufArrayList<Object> EMPTY_LIST = new ProtobufArrayList<Object>(); static { @@ -51,13 +51,23 @@ class ProtobufArrayList<E> extends AbstractProtobufList<E> { } private final List<E> list; - + ProtobufArrayList() { - list = new ArrayList<E>(); + this(new ArrayList<E>(DEFAULT_CAPACITY)); } - ProtobufArrayList(List<E> toCopy) { - list = new ArrayList<E>(toCopy); + private ProtobufArrayList(List<E> list) { + this.list = list; + } + + @Override + public ProtobufArrayList<E> mutableCopyWithCapacity(int capacity) { + if (capacity < size()) { + throw new IllegalArgumentException(); + } + List<E> newList = new ArrayList<E>(capacity); + newList.addAll(list); + return new ProtobufArrayList<E>(newList); } @Override diff --git a/java/src/main/java/com/google/protobuf/ProtocolMessageEnum.java b/java/core/src/main/java/com/google/protobuf/ProtocolMessageEnum.java index 0c8df989..a596d301 100644 --- a/java/src/main/java/com/google/protobuf/ProtocolMessageEnum.java +++ b/java/core/src/main/java/com/google/protobuf/ProtocolMessageEnum.java @@ -42,6 +42,7 @@ public interface ProtocolMessageEnum extends Internal.EnumLite { /** * Return the value's numeric value as defined in the .proto file. */ + @Override int getNumber(); /** diff --git a/java/src/main/java/com/google/protobuf/ProtocolStringList.java b/java/core/src/main/java/com/google/protobuf/ProtocolStringList.java index d553b41e..d553b41e 100644 --- a/java/src/main/java/com/google/protobuf/ProtocolStringList.java +++ b/java/core/src/main/java/com/google/protobuf/ProtocolStringList.java diff --git a/java/src/main/java/com/google/protobuf/RepeatedFieldBuilder.java b/java/core/src/main/java/com/google/protobuf/RepeatedFieldBuilder.java index f91cdbce..29f567dc 100644 --- a/java/src/main/java/com/google/protobuf/RepeatedFieldBuilder.java +++ b/java/core/src/main/java/com/google/protobuf/RepeatedFieldBuilder.java @@ -579,7 +579,7 @@ public class RepeatedFieldBuilder } } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public void markDirty() { onChanged(); } @@ -621,10 +621,12 @@ public class RepeatedFieldBuilder this.builder = builder; } + @Override public int size() { return this.builder.getCount(); } + @Override public MType get(int index) { return builder.getMessage(index); } @@ -654,10 +656,12 @@ public class RepeatedFieldBuilder this.builder = builder; } + @Override public int size() { return this.builder.getCount(); } + @Override public BType get(int index) { return builder.getBuilder(index); } @@ -687,10 +691,12 @@ public class RepeatedFieldBuilder this.builder = builder; } + @Override public int size() { return this.builder.getCount(); } + @Override public IType get(int index) { return builder.getMessageOrBuilder(index); } diff --git a/java/src/main/java/com/google/protobuf/RopeByteString.java b/java/core/src/main/java/com/google/protobuf/RopeByteString.java index 2c332624..3f3e9bd1 100644 --- a/java/src/main/java/com/google/protobuf/RopeByteString.java +++ b/java/core/src/main/java/com/google/protobuf/RopeByteString.java @@ -48,10 +48,11 @@ import java.util.Stack; /** * Class to represent {@code ByteStrings} formed by concatenation of other * ByteStrings, without copying the data in the pieces. The concatenation is - * represented as a tree whose leaf nodes are each a {@link LiteralByteString}. + * represented as a tree whose leaf nodes are each a + * {@link com.google.protobuf.ByteString.LeafByteString}. * * <p>Most of the operation here is inspired by the now-famous paper <a - * href="http://www.cs.ubc.ca/local/reading/proceedings/spe91-95/spe/vol25/issue12/spe986.pdf"> + * href="https://web.archive.org/web/20060202015456/http://www.cs.ubc.ca/local/reading/proceedings/spe91-95/spe/vol25/issue12/spe986.pdf"> * BAP95 </a> Ropes: an Alternative to Strings hans-j. boehm, russ atkinson and * michael plass * @@ -69,7 +70,7 @@ import java.util.Stack; * * @author carlanton@google.com (Carl Haverl) */ -class RopeByteString extends ByteString { +final class RopeByteString extends ByteString { /** * BAP95. Let Fn be the nth Fibonacci number. A {@link RopeByteString} of @@ -139,8 +140,9 @@ class RopeByteString extends ByteString { /** * Concatenate the given strings while performing various optimizations to * slow the growth rate of tree depth and tree node count. The result is - * either a {@link LiteralByteString} or a {@link RopeByteString} - * depending on which optimizations, if any, were applied. + * either a {@link com.google.protobuf.ByteString.LeafByteString} or a + * {@link RopeByteString} depending on which optimizations, if any, were + * applied. * * <p>Small pieces of length less than {@link * ByteString#CONCATENATE_BY_COPY_SIZE} may be copied by value here, as in @@ -151,21 +153,24 @@ class RopeByteString extends ByteString { * @return concatenation representing the same sequence as the given strings */ static ByteString concatenate(ByteString left, ByteString right) { - ByteString result; - RopeByteString leftRope = - (left instanceof RopeByteString) ? (RopeByteString) left : null; if (right.size() == 0) { - result = left; - } else if (left.size() == 0) { - result = right; - } else { - int newLength = left.size() + right.size(); - if (newLength < ByteString.CONCATENATE_BY_COPY_SIZE) { - // Optimization from BAP95: For short (leaves in paper, but just short - // here) total length, do a copy of data to a new leaf. - result = concatenateBytes(left, right); - } else if (leftRope != null - && leftRope.right.size() + right.size() < CONCATENATE_BY_COPY_SIZE) { + return left; + } + + if (left.size() == 0) { + return right; + } + + final int newLength = left.size() + right.size(); + if (newLength < ByteString.CONCATENATE_BY_COPY_SIZE) { + // Optimization from BAP95: For short (leaves in paper, but just short + // here) total length, do a copy of data to a new leaf. + return concatenateBytes(left, right); + } + + if (left instanceof RopeByteString) { + final RopeByteString leftRope = (RopeByteString) left; + if (leftRope.right.size() + right.size() < CONCATENATE_BY_COPY_SIZE) { // Optimization from BAP95: As an optimization of the case where the // ByteString is constructed by repeated concatenate, recognize the case // where a short string is concatenated to a left-hand node whose @@ -177,30 +182,29 @@ class RopeByteString extends ByteString { // new parent node so that the depth of the result is the same as the // given left tree. ByteString newRight = concatenateBytes(leftRope.right, right); - result = new RopeByteString(leftRope.left, newRight); - } else if (leftRope != null - && leftRope.left.getTreeDepth() > leftRope.right.getTreeDepth() + return new RopeByteString(leftRope.left, newRight); + } + + if (leftRope.left.getTreeDepth() > leftRope.right.getTreeDepth() && leftRope.getTreeDepth() > right.getTreeDepth()) { // Typically for concatenate-built strings the left-side is deeper than // the right. This is our final attempt to concatenate without - // increasing the tree depth. We'll redo the the node on the RHS. This + // increasing the tree depth. We'll redo the node on the RHS. This // is yet another optimization for building the string by repeatedly // concatenating on the right. ByteString newRight = new RopeByteString(leftRope.right, right); - result = new RopeByteString(leftRope.left, newRight); - } else { - // Fine, we'll add a node and increase the tree depth--unless we - // rebalance ;^) - int newDepth = Math.max(left.getTreeDepth(), right.getTreeDepth()) + 1; - if (newLength >= minLengthByDepth[newDepth]) { - // The tree is shallow enough, so don't rebalance - result = new RopeByteString(left, right); - } else { - result = new Balancer().balance(left, right); - } + return new RopeByteString(leftRope.left, newRight); } } - return result; + + // Fine, we'll add a node and increase the tree depth--unless we rebalance ;^) + int newDepth = Math.max(left.getTreeDepth(), right.getTreeDepth()) + 1; + if (newLength >= minLengthByDepth[newDepth]) { + // The tree is shallow enough, so don't rebalance + return new RopeByteString(left, right); + } + + return new Balancer().balance(left, right); } /** @@ -211,14 +215,14 @@ class RopeByteString extends ByteString { * @param right string on the right * @return string formed by copying data bytes */ - private static LiteralByteString concatenateBytes(ByteString left, + private static ByteString concatenateBytes(ByteString left, ByteString right) { int leftSize = left.size(); int rightSize = right.size(); byte[] bytes = new byte[leftSize + rightSize]; left.copyTo(bytes, 0, 0, leftSize); right.copyTo(bytes, 0, leftSize, rightSize); - return new LiteralByteString(bytes); // Constructor wraps bytes + return ByteString.wrap(bytes); // Constructor wraps bytes } /** @@ -248,22 +252,14 @@ class RopeByteString extends ByteString { */ @Override public byte byteAt(int index) { - if (index < 0) { - throw new ArrayIndexOutOfBoundsException("Index < 0: " + index); - } - if (index > totalLength) { - throw new ArrayIndexOutOfBoundsException( - "Index > length: " + index + ", " + totalLength); - } + checkIndex(index, totalLength); - byte result; // Find the relevant piece by recursive descent if (index < leftLength) { - result = left.byteAt(index); - } else { - result = right.byteAt(index - leftLength); + return left.byteAt(index); } - return result; + + return right.byteAt(index - leftLength); } @Override @@ -300,8 +296,7 @@ class RopeByteString extends ByteString { * * <p>Substrings of {@code length < 2} should result in at most a single * recursive call chain, terminating at a leaf node. Thus the result will be a - * {@link LiteralByteString}. {@link #RopeByteString(ByteString, - * ByteString)}. + * {@link com.google.protobuf.ByteString.LeafByteString}. * * @param beginIndex start at this index * @param endIndex the last character is the one before this index @@ -309,48 +304,36 @@ class RopeByteString extends ByteString { */ @Override public ByteString substring(int beginIndex, int endIndex) { - if (beginIndex < 0) { - throw new IndexOutOfBoundsException( - "Beginning index: " + beginIndex + " < 0"); + final int length = checkRange(beginIndex, endIndex, totalLength); + + if (length == 0) { + // Empty substring + return ByteString.EMPTY; } - if (endIndex > totalLength) { - throw new IndexOutOfBoundsException( - "End index: " + endIndex + " > " + totalLength); + + if (length == totalLength) { + // The whole string + return this; } - int substringLength = endIndex - beginIndex; - if (substringLength < 0) { - throw new IndexOutOfBoundsException( - "Beginning index larger than ending index: " + beginIndex + ", " - + endIndex); + + // Proper substring + if (endIndex <= leftLength) { + // Substring on the left + return left.substring(beginIndex, endIndex); } - ByteString result; - if (substringLength == 0) { - // Empty substring - result = ByteString.EMPTY; - } else if (substringLength == totalLength) { - // The whole string - result = this; - } else { - // Proper substring - if (endIndex <= leftLength) { - // Substring on the left - result = left.substring(beginIndex, endIndex); - } else if (beginIndex >= leftLength) { - // Substring on the right - result = right - .substring(beginIndex - leftLength, endIndex - leftLength); - } else { - // Split substring - ByteString leftSub = left.substring(beginIndex); - ByteString rightSub = right.substring(0, endIndex - leftLength); - // Intentionally not rebalancing, since in many cases these two - // substrings will already be less deep than the top-level - // RopeByteString we're taking a substring of. - result = new RopeByteString(leftSub, rightSub); - } + if (beginIndex >= leftLength) { + // Substring on the right + return right.substring(beginIndex - leftLength, endIndex - leftLength); } - return result; + + // Split substring + ByteString leftSub = left.substring(beginIndex); + ByteString rightSub = right.substring(0, endIndex - leftLength); + // Intentionally not rebalancing, since in many cases these two + // substrings will already be less deep than the top-level + // RopeByteString we're taking a substring of. + return new RopeByteString(leftSub, rightSub); } // ================================================================= @@ -386,12 +369,12 @@ class RopeByteString extends ByteString { @Override public List<ByteBuffer> asReadOnlyByteBufferList() { - // Walk through the list of LiteralByteString's that make up this + // Walk through the list of LeafByteString's that make up this // rope, and add each one as a read-only ByteBuffer. List<ByteBuffer> result = new ArrayList<ByteBuffer>(); PieceIterator pieces = new PieceIterator(this); while (pieces.hasNext()) { - LiteralByteString byteString = pieces.next(); + LeafByteString byteString = pieces.next(); result.add(byteString.asReadOnlyByteBuffer()); } return result; @@ -418,6 +401,12 @@ class RopeByteString extends ByteString { } @Override + void writeTo(ByteOutput output) throws IOException { + left.writeTo(output); + right.writeTo(output); + } + + @Override protected String toStringInternal(Charset charset) { return new String(toByteArray(), charset); } @@ -471,11 +460,10 @@ class RopeByteString extends ByteString { // hashCode if it's already computed. It's arguable we should compute the // hashCode here, and if we're going to be testing a bunch of byteStrings, // it might even make sense. - if (hash != 0) { - int cachedOtherHash = otherByteString.peekCachedHashCode(); - if (cachedOtherHash != 0 && hash != cachedOtherHash) { - return false; - } + int thisHash = peekCachedHashCode(); + int thatHash = otherByteString.peekCachedHashCode(); + if (thisHash != 0 && thatHash != 0 && thisHash != thatHash) { + return false; } return equalsFragments(otherByteString); @@ -492,12 +480,12 @@ class RopeByteString extends ByteString { */ private boolean equalsFragments(ByteString other) { int thisOffset = 0; - Iterator<LiteralByteString> thisIter = new PieceIterator(this); - LiteralByteString thisString = thisIter.next(); + Iterator<LeafByteString> thisIter = new PieceIterator(this); + LeafByteString thisString = thisIter.next(); int thatOffset = 0; - Iterator<LiteralByteString> thatIter = new PieceIterator(other); - LiteralByteString thatString = thatIter.next(); + Iterator<LeafByteString> thatIter = new PieceIterator(other); + LeafByteString thatString = thatIter.next(); int pos = 0; while (true) { @@ -536,33 +524,6 @@ class RopeByteString extends ByteString { } } - /** - * Cached hash value. Intentionally accessed via a data race, which is safe - * because of the Java Memory Model's "no out-of-thin-air values" guarantees - * for ints. - */ - private int hash = 0; - - @Override - public int hashCode() { - int h = hash; - - if (h == 0) { - h = totalLength; - h = partialHash(h, 0, totalLength); - if (h == 0) { - h = 1; - } - hash = h; - } - return h; - } - - @Override - protected int peekCachedHashCode() { - return hash; - } - @Override protected int partialHash(int h, int offset, int length) { int toIndex = offset + length; @@ -714,34 +675,34 @@ class RopeByteString extends ByteString { * <p>This iterator is used to implement * {@link RopeByteString#equalsFragments(ByteString)}. */ - private static class PieceIterator implements Iterator<LiteralByteString> { + private static class PieceIterator implements Iterator<LeafByteString> { private final Stack<RopeByteString> breadCrumbs = new Stack<RopeByteString>(); - private LiteralByteString next; + private LeafByteString next; private PieceIterator(ByteString root) { next = getLeafByLeft(root); } - private LiteralByteString getLeafByLeft(ByteString root) { + private LeafByteString getLeafByLeft(ByteString root) { ByteString pos = root; while (pos instanceof RopeByteString) { RopeByteString rbs = (RopeByteString) pos; breadCrumbs.push(rbs); pos = rbs.left; } - return (LiteralByteString) pos; + return (LeafByteString) pos; } - private LiteralByteString getNextNonEmptyLeaf() { + private LeafByteString getNextNonEmptyLeaf() { while (true) { // Almost always, we go through this loop exactly once. However, if // we discover an empty string in the rope, we toss it and try again. if (breadCrumbs.isEmpty()) { return null; } else { - LiteralByteString result = getLeafByLeft(breadCrumbs.pop().right); + LeafByteString result = getLeafByLeft(breadCrumbs.pop().right); if (!result.isEmpty()) { return result; } @@ -749,24 +710,28 @@ class RopeByteString extends ByteString { } } + @Override public boolean hasNext() { return next != null; } /** - * Returns the next item and advances one {@code LiteralByteString}. + * Returns the next item and advances one + * {@link com.google.protobuf.ByteString.LeafByteString}. * - * @return next non-empty LiteralByteString or {@code null} + * @return next non-empty LeafByteString or {@code null} */ - public LiteralByteString next() { + @Override + public LeafByteString next() { if (next == null) { throw new NoSuchElementException(); } - LiteralByteString result = next; + LeafByteString result = next; next = getNextNonEmptyLeaf(); return result; } + @Override public void remove() { throw new UnsupportedOperationException(); } @@ -778,55 +743,14 @@ class RopeByteString extends ByteString { private static final long serialVersionUID = 1L; Object writeReplace() { - return new LiteralByteString(toByteArray()); + return ByteString.wrap(toByteArray()); } - private void readObject(ObjectInputStream in) throws IOException { + private void readObject(@SuppressWarnings("unused") ObjectInputStream in) throws IOException { throw new InvalidObjectException( "RopeByteStream instances are not to be serialized directly"); } - // ================================================================= - // ByteIterator - - @Override - public ByteIterator iterator() { - return new RopeByteIterator(); - } - - private class RopeByteIterator implements ByteString.ByteIterator { - - private final PieceIterator pieces; - private ByteIterator bytes; - int bytesRemaining; - - private RopeByteIterator() { - pieces = new PieceIterator(RopeByteString.this); - bytes = pieces.next().iterator(); - bytesRemaining = size(); - } - - public boolean hasNext() { - return (bytesRemaining > 0); - } - - public Byte next() { - return nextByte(); // Does not instantiate a Byte - } - - public byte nextByte() { - if (!bytes.hasNext()) { - bytes = pieces.next().iterator(); - } - --bytesRemaining; - return bytes.nextByte(); - } - - public void remove() { - throw new UnsupportedOperationException(); - } - } - /** * This class is the {@link RopeByteString} equivalent for * {@link ByteArrayInputStream}. @@ -835,7 +759,7 @@ class RopeByteString extends ByteString { // Iterates through the pieces of the rope private PieceIterator pieceIterator; // The current piece - private LiteralByteString currentPiece; + private LeafByteString currentPiece; // The size of the current piece private int currentPieceSize; // The index of the next byte to read in the current piece @@ -872,7 +796,7 @@ class RopeByteString extends ByteString { /** * Internal implementation of read and skip. If b != null, then read the * next {@code length} bytes into the buffer {@code b} at - * offset {@code offset}. If b == null, then skip the next {@code length) + * offset {@code offset}. If b == null, then skip the next {@code length} * bytes. * <p> * This method assumes that all error checking has already happened. diff --git a/java/src/main/java/com/google/protobuf/RpcCallback.java b/java/core/src/main/java/com/google/protobuf/RpcCallback.java index 10752968..10752968 100644 --- a/java/src/main/java/com/google/protobuf/RpcCallback.java +++ b/java/core/src/main/java/com/google/protobuf/RpcCallback.java diff --git a/java/src/main/java/com/google/protobuf/RpcChannel.java b/java/core/src/main/java/com/google/protobuf/RpcChannel.java index f272f4ad..f272f4ad 100644 --- a/java/src/main/java/com/google/protobuf/RpcChannel.java +++ b/java/core/src/main/java/com/google/protobuf/RpcChannel.java diff --git a/java/src/main/java/com/google/protobuf/RpcController.java b/java/core/src/main/java/com/google/protobuf/RpcController.java index a92dd7be..a92dd7be 100644 --- a/java/src/main/java/com/google/protobuf/RpcController.java +++ b/java/core/src/main/java/com/google/protobuf/RpcController.java diff --git a/java/src/main/java/com/google/protobuf/RpcUtil.java b/java/core/src/main/java/com/google/protobuf/RpcUtil.java index 694b8d13..f7d555ae 100644 --- a/java/src/main/java/com/google/protobuf/RpcUtil.java +++ b/java/core/src/main/java/com/google/protobuf/RpcUtil.java @@ -71,6 +71,7 @@ public final class RpcUtil { final Class<Type> originalClass, final Type defaultInstance) { return new RpcCallback<Message>() { + @Override public void run(final Message parameter) { Type typedParameter; try { @@ -107,8 +108,9 @@ public final class RpcUtil { return new RpcCallback<ParameterType>() { private boolean alreadyCalled = false; + @Override public void run(final ParameterType parameter) { - synchronized(this) { + synchronized (this) { if (alreadyCalled) { throw new AlreadyCalledException(); } diff --git a/java/src/main/java/com/google/protobuf/Service.java b/java/core/src/main/java/com/google/protobuf/Service.java index ba7b033e..ba7b033e 100644 --- a/java/src/main/java/com/google/protobuf/Service.java +++ b/java/core/src/main/java/com/google/protobuf/Service.java diff --git a/java/src/main/java/com/google/protobuf/ServiceException.java b/java/core/src/main/java/com/google/protobuf/ServiceException.java index 00d57075..00d57075 100644 --- a/java/src/main/java/com/google/protobuf/ServiceException.java +++ b/java/core/src/main/java/com/google/protobuf/ServiceException.java diff --git a/java/src/main/java/com/google/protobuf/SingleFieldBuilder.java b/java/core/src/main/java/com/google/protobuf/SingleFieldBuilder.java index aba65e32..941b5def 100644 --- a/java/src/main/java/com/google/protobuf/SingleFieldBuilder.java +++ b/java/core/src/main/java/com/google/protobuf/SingleFieldBuilder.java @@ -234,7 +234,7 @@ public class SingleFieldBuilder } } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public void markDirty() { onChanged(); } diff --git a/java/src/main/java/com/google/protobuf/SmallSortedMap.java b/java/core/src/main/java/com/google/protobuf/SmallSortedMap.java index 0674d2e2..409fec10 100644 --- a/java/src/main/java/com/google/protobuf/SmallSortedMap.java +++ b/java/core/src/main/java/com/google/protobuf/SmallSortedMap.java @@ -35,12 +35,12 @@ import java.util.AbstractSet; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; -import java.util.TreeMap; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.SortedMap; +import java.util.TreeMap; /** * A custom map implementation from FieldDescriptor to Object optimized to @@ -411,22 +411,22 @@ class SmallSortedMap<K extends Comparable<K>, V> extends AbstractMap<K, V> { this.value = value; } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public K getKey() { return key; } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public V getValue() { return value; } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public int compareTo(Entry other) { return getKey().compareTo(other.getKey()); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public V setValue(V newValue) { checkMutable(); final V oldValue = this.value; @@ -535,13 +535,13 @@ class SmallSortedMap<K extends Comparable<K>, V> extends AbstractMap<K, V> { private boolean nextCalledBeforeRemove; private Iterator<Map.Entry<K, V>> lazyOverflowIterator; - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public boolean hasNext() { return (pos + 1) < entryList.size() || getOverflowIterator().hasNext(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public Map.Entry<K, V> next() { nextCalledBeforeRemove = true; // Always increment pos so that we know whether the last returned value @@ -552,7 +552,7 @@ class SmallSortedMap<K extends Comparable<K>, V> extends AbstractMap<K, V> { return getOverflowIterator().next(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public void remove() { if (!nextCalledBeforeRemove) { throw new IllegalStateException("remove() was called before next()"); @@ -588,31 +588,83 @@ class SmallSortedMap<K extends Comparable<K>, V> extends AbstractMap<K, V> { */ private static class EmptySet { - private static final Iterator<Object> ITERATOR = new Iterator<Object>() { - //@Override (Java 1.6 override semantics, but we must support 1.5) - public boolean hasNext() { - return false; - } - //@Override (Java 1.6 override semantics, but we must support 1.5) - public Object next() { - throw new NoSuchElementException(); - } - //@Override (Java 1.6 override semantics, but we must support 1.5) - public void remove() { - throw new UnsupportedOperationException(); - } - }; + private static final Iterator<Object> ITERATOR = + new Iterator<Object>() { + @Override + public boolean hasNext() { + return false; + } + @Override + public Object next() { + throw new NoSuchElementException(); + } + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; - private static final Iterable<Object> ITERABLE = new Iterable<Object>() { - //@Override (Java 1.6 override semantics, but we must support 1.5) - public Iterator<Object> iterator() { - return ITERATOR; - } - }; + private static final Iterable<Object> ITERABLE = + new Iterable<Object>() { + @Override + public Iterator<Object> iterator() { + return ITERATOR; + } + }; @SuppressWarnings("unchecked") static <T> Iterable<T> iterable() { return (Iterable<T>) ITERABLE; } } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof SmallSortedMap)) { + return super.equals(o); + } + + SmallSortedMap<?, ?> other = (SmallSortedMap<?, ?>) o; + final int size = size(); + if (size != other.size()) { + return false; + } + + // Best effort try to avoid allocating an entry set. + final int numArrayEntries = getNumArrayEntries(); + if (numArrayEntries != other.getNumArrayEntries()) { + return entrySet().equals(other.entrySet()); + } + + for (int i = 0; i < numArrayEntries; i++) { + if (!getArrayEntryAt(i).equals(other.getArrayEntryAt(i))) { + return false; + } + } + + if (numArrayEntries != size) { + return overflowEntries.equals(other.overflowEntries); + } + + + return true; + } + + @Override + public int hashCode() { + int h = 0; + final int listSize = getNumArrayEntries(); + for (int i = 0; i < listSize; i++) { + h += entryList.get(i).hashCode(); + } + // Avoid the iterator allocation if possible. + if (getNumOverflowEntries() > 0) { + h += overflowEntries.hashCode(); + } + return h; + } } diff --git a/java/src/main/java/com/google/protobuf/TextFormat.java b/java/core/src/main/java/com/google/protobuf/TextFormat.java index 44d036c1..c1c328fc 100644 --- a/java/src/main/java/com/google/protobuf/TextFormat.java +++ b/java/core/src/main/java/com/google/protobuf/TextFormat.java @@ -425,7 +425,7 @@ public final class TextFormat { case STRING: generator.print("\""); generator.print(escapeNonAscii - ? escapeText((String) value) + ? TextFormatEscaper.escapeText((String) value) : escapeDoubleQuotesAndBackslashes((String) value) .replace("\n", "\\n")); generator.print("\""); @@ -661,6 +661,14 @@ public final class TextFormat { nextToken(); } + int getLine() { + return line; + } + + int getColumn() { + return column; + } + /** Are we at the end of the input? */ public boolean atEnd() { return currentToken.length() == 0; @@ -957,11 +965,13 @@ public final class TextFormat { */ public boolean consumeBoolean() throws ParseException { if (currentToken.equals("true") + || currentToken.equals("True") || currentToken.equals("t") || currentToken.equals("1")) { nextToken(); return true; } else if (currentToken.equals("false") + || currentToken.equals("False") || currentToken.equals("f") || currentToken.equals("0")) { nextToken(); @@ -1074,6 +1084,18 @@ public final class TextFormat { private ParseException floatParseException(final NumberFormatException e) { return parseException("Couldn't parse number: " + e.getMessage()); } + + /** + * Returns a {@link UnknownFieldParseException} with the line and column + * numbers of the previous token in the description, and the unknown field + * name, suitable for throwing. + */ + public UnknownFieldParseException unknownFieldParseExceptionPreviousToken( + final String unknownField, final String description) { + // Note: People generally prefer one-based line and column numbers. + return new UnknownFieldParseException( + previousLine + 1, previousColumn + 1, unknownField, description); + } } /** Thrown when parsing an invalid text format message. */ @@ -1122,6 +1144,45 @@ public final class TextFormat { } } + /** + * Thrown when encountering an unknown field while parsing + * a text format message. + */ + public static class UnknownFieldParseException extends ParseException { + private final String unknownField; + + /** + * Create a new instance, with -1 as the line and column numbers, and an + * empty unknown field name. + */ + public UnknownFieldParseException(final String message) { + this(-1, -1, "", message); + } + + /** + * Create a new instance + * + * @param line the line number where the parse error occurred, + * using 1-offset. + * @param column the column number where the parser error occurred, + * using 1-offset. + * @param unknownField the name of the unknown field found while parsing. + */ + public UnknownFieldParseException(final int line, final int column, + final String unknownField, final String message) { + super(line, column, message); + this.unknownField = unknownField; + } + + /** + * Return the name of the unknown field encountered while parsing the + * protocol buffer string. + */ + public String getUnknownField() { + return unknownField; + } + } + private static final Parser PARSER = Parser.newBuilder().build(); /** @@ -1206,11 +1267,14 @@ public final class TextFormat { private final boolean allowUnknownFields; private final SingularOverwritePolicy singularOverwritePolicy; + private TextFormatParseInfoTree.Builder parseInfoTreeBuilder; - private Parser(boolean allowUnknownFields, - SingularOverwritePolicy singularOverwritePolicy) { + private Parser( + boolean allowUnknownFields, SingularOverwritePolicy singularOverwritePolicy, + TextFormatParseInfoTree.Builder parseInfoTreeBuilder) { this.allowUnknownFields = allowUnknownFields; this.singularOverwritePolicy = singularOverwritePolicy; + this.parseInfoTreeBuilder = parseInfoTreeBuilder; } /** @@ -1227,6 +1291,7 @@ public final class TextFormat { private boolean allowUnknownFields = false; private SingularOverwritePolicy singularOverwritePolicy = SingularOverwritePolicy.ALLOW_SINGULAR_OVERWRITES; + private TextFormatParseInfoTree.Builder parseInfoTreeBuilder = null; /** @@ -1237,8 +1302,15 @@ public final class TextFormat { return this; } + public Builder setParseInfoTreeBuilder( + TextFormatParseInfoTree.Builder parseInfoTreeBuilder) { + this.parseInfoTreeBuilder = parseInfoTreeBuilder; + return this; + } + public Parser build() { - return new Parser(allowUnknownFields, singularOverwritePolicy); + return new Parser( + allowUnknownFields, singularOverwritePolicy, parseInfoTreeBuilder); } } @@ -1329,7 +1401,21 @@ public final class TextFormat { final ExtensionRegistry extensionRegistry, final MessageReflection.MergeTarget target) throws ParseException { + mergeField(tokenizer, extensionRegistry, target, parseInfoTreeBuilder); + } + + /** + * 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, + TextFormatParseInfoTree.Builder parseTreeBuilder) + throws ParseException { FieldDescriptor field = null; + int startLine = tokenizer.getLine(); + int startColumn = tokenizer.getColumn(); final Descriptor type = target.getDescriptorForType(); ExtensionRegistry.ExtensionInfo extension = null; @@ -1388,7 +1474,8 @@ public final class TextFormat { if (field == null) { if (!allowUnknownFields) { - throw tokenizer.parseExceptionPreviousToken( + throw tokenizer.unknownFieldParseExceptionPreviousToken( + name, "Message type \"" + type.getFullName() + "\" has no field named \"" + name + "\"."); } else { @@ -1420,14 +1507,51 @@ public final class TextFormat { // Handle potential ':'. if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { tokenizer.tryConsume(":"); // optional + if (parseTreeBuilder != null) { + TextFormatParseInfoTree.Builder childParseTreeBuilder = + parseTreeBuilder.getBuilderForSubMessageField(field); + consumeFieldValues(tokenizer, extensionRegistry, target, field, extension, + childParseTreeBuilder); + } else { + consumeFieldValues(tokenizer, extensionRegistry, target, field, extension, + parseTreeBuilder); + } } else { tokenizer.consume(":"); // required + consumeFieldValues( + tokenizer, extensionRegistry, target, field, extension, parseTreeBuilder); + } + + if (parseTreeBuilder != null) { + parseTreeBuilder.setLocation( + field, TextFormatParseLocation.create(startLine, startColumn)); + } + + // For historical reasons, fields may optionally be separated by commas or + // semicolons. + if (!tokenizer.tryConsume(";")) { + tokenizer.tryConsume(","); } + } + + /** + * Parse a one or more field values from {@code tokenizer} and merge it into + * {@code builder}. + */ + private void consumeFieldValues( + final Tokenizer tokenizer, + final ExtensionRegistry extensionRegistry, + final MessageReflection.MergeTarget target, + final FieldDescriptor field, + final ExtensionRegistry.ExtensionInfo extension, + final TextFormatParseInfoTree.Builder parseTreeBuilder) + throws ParseException { // 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); + consumeFieldValue(tokenizer, extensionRegistry, target, field, extension, + parseTreeBuilder); if (tokenizer.tryConsume("]")) { // End of list. break; @@ -1435,13 +1559,8 @@ public final class TextFormat { tokenizer.consume(","); } } else { - consumeFieldValue(tokenizer, extensionRegistry, target, field, extension); - } - - // For historical reasons, fields may optionally be separated by commas or - // semicolons. - if (!tokenizer.tryConsume(";")) { - tokenizer.tryConsume(","); + consumeFieldValue( + tokenizer, extensionRegistry, target, field, extension, parseTreeBuilder); } } @@ -1454,7 +1573,8 @@ public final class TextFormat { final ExtensionRegistry extensionRegistry, final MessageReflection.MergeTarget target, final FieldDescriptor field, - final ExtensionRegistry.ExtensionInfo extension) + final ExtensionRegistry.ExtensionInfo extension, + final TextFormatParseInfoTree.Builder parseTreeBuilder) throws ParseException { Object value = null; @@ -1476,7 +1596,7 @@ public final class TextFormat { throw tokenizer.parseException( "Expected \"" + endToken + "\"."); } - mergeField(tokenizer, extensionRegistry, subField); + mergeField(tokenizer, extensionRegistry, subField, parseTreeBuilder); } value = subField.finish(); @@ -1652,11 +1772,6 @@ 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 @@ -1665,74 +1780,15 @@ public final class TextFormat { * which no defined short-hand escape sequence is defined will be escaped * using 3-digit octal sequences. */ - public static String escapeBytes(final ByteSequence input) { - final StringBuilder builder = new StringBuilder(input.size()); - for (int i = 0; i < input.size(); i++) { - final byte b = input.byteAt(i); - switch (b) { - // Java does not recognize \a or \v, apparently. - case 0x07: builder.append("\\a"); break; - case '\b': builder.append("\\b"); break; - case '\f': builder.append("\\f"); break; - case '\n': builder.append("\\n"); break; - case '\r': builder.append("\\r"); break; - case '\t': builder.append("\\t"); break; - case 0x0b: builder.append("\\v"); break; - case '\\': builder.append("\\\\"); break; - case '\'': builder.append("\\\'"); break; - case '"' : builder.append("\\\""); break; - default: - // Only ASCII characters between 0x20 (space) and 0x7e (tilde) are - // printable. Other byte values must be escaped. - if (b >= 0x20 && b <= 0x7e) { - builder.append((char) b); - } else { - builder.append('\\'); - builder.append((char) ('0' + ((b >>> 6) & 3))); - builder.append((char) ('0' + ((b >>> 3) & 7))); - builder.append((char) ('0' + (b & 7))); - } - break; - } - } - return builder.toString(); - } - - /** - * 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. - */ - public static String escapeBytes(final ByteString input) { - return escapeBytes(new ByteSequence() { - @Override - public int size() { - return input.size(); - } - @Override - public byte byteAt(int offset) { - return input.byteAt(offset); - } - }); + public static String escapeBytes(ByteString input) { + return TextFormatEscaper.escapeBytes(input); } /** * Like {@link #escapeBytes(ByteString)}, but used for byte array. */ - public static String escapeBytes(final byte[] input) { - return escapeBytes(new ByteSequence() { - @Override - public int size() { - return input.length; - } - @Override - public byte byteAt(int offset) { - return input[offset]; - } - }); + public static String escapeBytes(byte[] input) { + return TextFormatEscaper.escapeBytes(input); } /** @@ -1816,7 +1872,9 @@ public final class TextFormat { } } - return ByteString.copyFrom(result, 0, pos); + return result.length == pos + ? ByteString.wrap(result) // This reference has not been out of our control. + : ByteString.copyFrom(result, 0, pos); } /** @@ -1844,7 +1902,7 @@ public final class TextFormat { * 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("\"", "\\\""); + return TextFormatEscaper.escapeDoubleQuotesAndBackslashes(input); } /** diff --git a/java/core/src/main/java/com/google/protobuf/TextFormatEscaper.java b/java/core/src/main/java/com/google/protobuf/TextFormatEscaper.java new file mode 100644 index 00000000..da9ceadd --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/TextFormatEscaper.java @@ -0,0 +1,137 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// 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; + +/** + * Provide text format escaping support for proto2 instances. + */ +final class TextFormatEscaper { + private TextFormatEscaper() {} + + 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 + * 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 ByteSequence input) { + final StringBuilder builder = new StringBuilder(input.size()); + for (int i = 0; i < input.size(); i++) { + final byte b = input.byteAt(i); + switch (b) { + // Java does not recognize \a or \v, apparently. + case 0x07: builder.append("\\a"); break; + case '\b': builder.append("\\b"); break; + case '\f': builder.append("\\f"); break; + case '\n': builder.append("\\n"); break; + case '\r': builder.append("\\r"); break; + case '\t': builder.append("\\t"); break; + case 0x0b: builder.append("\\v"); break; + case '\\': builder.append("\\\\"); break; + case '\'': builder.append("\\\'"); break; + case '"' : builder.append("\\\""); break; + default: + // Only ASCII characters between 0x20 (space) and 0x7e (tilde) are + // printable. Other byte values must be escaped. + if (b >= 0x20 && b <= 0x7e) { + builder.append((char) b); + } else { + builder.append('\\'); + builder.append((char) ('0' + ((b >>> 6) & 3))); + builder.append((char) ('0' + ((b >>> 3) & 7))); + builder.append((char) ('0' + (b & 7))); + } + break; + } + } + return builder.toString(); + } + + /** + * 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() { + @Override + public int size() { + return input.size(); + } + @Override + 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() { + @Override + public int size() { + return input.length; + } + @Override + public byte byteAt(int offset) { + return input[offset]; + } + }); + } + + /** + * Like {@link #escapeBytes(ByteString)}, but escapes a text string. + * Non-ASCII characters are first encoded as UTF-8, then each byte is escaped + * individually as a 3-digit octal escape. Yes, it's weird. + */ + static String escapeText(final String input) { + return escapeBytes(ByteString.copyFromUtf8(input)); + } + + /** + * Escape double quotes and backslashes in a String for unicode output of a message. + */ + static String escapeDoubleQuotesAndBackslashes(final String input) { + return input.replace("\\", "\\\\").replace("\"", "\\\""); + } +} diff --git a/java/core/src/main/java/com/google/protobuf/TextFormatParseInfoTree.java b/java/core/src/main/java/com/google/protobuf/TextFormatParseInfoTree.java new file mode 100644 index 00000000..2ecf912e --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/TextFormatParseInfoTree.java @@ -0,0 +1,225 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// 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.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + + +/** + * Data structure which is populated with the locations of each field value parsed from the text. + * + * <p>The locations of primary fields values are retrieved by {@code getLocation} or + * {@code getLocations}. The locations of sub message values are within nested + * {@code TextFormatParseInfoTree}s and are retrieve by {@getNestedTree} or {code @getNestedTrees}. + * + * <p>The {@code TextFormatParseInfoTree} is created by a Builder. + */ +public class TextFormatParseInfoTree { + + // Defines a mapping between each field's descriptor to the list of locations where + // its value(s) were was encountered. + private Map<FieldDescriptor, List<TextFormatParseLocation>> locationsFromField; + + // Defines a mapping between a field's descriptor to a list of TextFormatParseInfoTrees for + // sub message location information. + Map<FieldDescriptor, List<TextFormatParseInfoTree>> subtreesFromField; + + /** + * Construct a {@code TextFormatParseInfoTree}. + * + * @param locationsFromField a map of fields to location in the source code + * @param subtreeBuildersFromField a map of fields to parse tree location information builders + */ + private TextFormatParseInfoTree( + Map<FieldDescriptor, List<TextFormatParseLocation>> locationsFromField, + Map<FieldDescriptor, List<TextFormatParseInfoTree.Builder>> subtreeBuildersFromField) { + + // The maps are unmodifiable. The values in the maps are unmodifiable. + Map<FieldDescriptor, List<TextFormatParseLocation>> locs = + new HashMap<FieldDescriptor, List<TextFormatParseLocation>>(); + for (Entry<FieldDescriptor, List<TextFormatParseLocation>> kv : locationsFromField.entrySet()) { + locs.put(kv.getKey(), Collections.unmodifiableList(kv.getValue())); + } + this.locationsFromField = Collections.unmodifiableMap(locs); + + Map<FieldDescriptor, List<TextFormatParseInfoTree>> subs = + new HashMap<FieldDescriptor, List<TextFormatParseInfoTree>>(); + for (Entry<FieldDescriptor, List<Builder>> kv : subtreeBuildersFromField.entrySet()) { + List<TextFormatParseInfoTree> submessagesOfField = new ArrayList<TextFormatParseInfoTree>(); + for (Builder subBuilder : kv.getValue()) { + submessagesOfField.add(subBuilder.build()); + } + subs.put(kv.getKey(), Collections.unmodifiableList(submessagesOfField)); + } + this.subtreesFromField = Collections.unmodifiableMap(subs); + } + + /** + * Retrieve all the locations of a field. + * + * @param fieldDescriptor the the @{link FieldDescriptor} of the desired field + * @return a list of the locations of values of the field. If there are not values + * or the field doesn't exist, an empty list is returned. + */ + public List<TextFormatParseLocation> getLocations(final FieldDescriptor fieldDescriptor) { + List<TextFormatParseLocation> result = locationsFromField.get(fieldDescriptor); + return (result == null) ? Collections.<TextFormatParseLocation>emptyList() : result; + } + + /** + * Get the location in the source of a field's value. + * + * <p>Returns the {@link TextFormatParseLocation} for index-th value of the field in the parsed + * text. + * + * @param fieldDescriptor the @{link FieldDescriptor} of the desired field + * @param index the index of the value. + * @return the {@link TextFormatParseLocation} of the value + * @throws IllegalArgumentException index is out of range + */ + public TextFormatParseLocation getLocation(final FieldDescriptor fieldDescriptor, int index) { + return getFromList(getLocations(fieldDescriptor), index, fieldDescriptor); + } + + /** + * Retrieve a list of all the location information trees for a sub message field. + * + * @param fieldDescriptor the @{link FieldDescriptor} of the desired field + * @return A list of {@link TextFormatParseInfoTree} + */ + public List<TextFormatParseInfoTree> getNestedTrees(final FieldDescriptor fieldDescriptor) { + List<TextFormatParseInfoTree> result = subtreesFromField.get(fieldDescriptor); + return result == null ? Collections.<TextFormatParseInfoTree>emptyList() : result; + } + + /** + * Returns the parse info tree for the given field, which must be a message type. + * + * @param fieldDescriptor the @{link FieldDescriptor} of the desired sub message + * @param index the index of message value. + * @return the {@code ParseInfoTree} of the message value. {@code null} is returned if the field + * doesn't exist or the index is out of range. + * @throws IllegalArgumentException if index is out of range + */ + public TextFormatParseInfoTree getNestedTree(final FieldDescriptor fieldDescriptor, int index) { + return getFromList(getNestedTrees(fieldDescriptor), index, fieldDescriptor); + } + + /** + * Create a builder for a {@code ParseInfoTree}. + * + * @return the builder + */ + public static Builder builder() { + return new Builder(); + } + + private static <T> T getFromList(List<T> list, int index, FieldDescriptor fieldDescriptor) { + if (index >= list.size() || index < 0) { + throw new IllegalArgumentException(String.format("Illegal index field: %s, index %d", + fieldDescriptor == null ? "<null>" : fieldDescriptor.getName(), index)); + } + return list.get(index); + } + + /** + * Builder for a {@link TextFormatParseInfoTree}. + */ + public static class Builder { + + private Map<FieldDescriptor, List<TextFormatParseLocation>> locationsFromField; + + // Defines a mapping between a field's descriptor to a list of ParseInfoTrees builders for + // sub message location information. + private Map<FieldDescriptor, List<Builder>> subtreeBuildersFromField; + + /** + * Create a root level {@ParseInfoTree} builder. + */ + private Builder() { + locationsFromField = new HashMap<FieldDescriptor, List<TextFormatParseLocation>>(); + subtreeBuildersFromField = new HashMap<FieldDescriptor, List<Builder>>(); + } + + /** + * Record the starting location of a single value for a field. + * + * @param fieldDescriptor the field + * @param location source code location information + */ + public Builder setLocation( + final FieldDescriptor fieldDescriptor, TextFormatParseLocation location) { + List<TextFormatParseLocation> fieldLocations = locationsFromField.get(fieldDescriptor); + if (fieldLocations == null) { + fieldLocations = new ArrayList<TextFormatParseLocation>(); + locationsFromField.put(fieldDescriptor, fieldLocations); + } + fieldLocations.add(location); + return this; + } + + /** + * Set for a sub message. + * + * <p>A new builder is created for a sub message. The builder that is returned is a new builder. + * The return is <emph>not</emph> the invoked {@code builder.getBuilderForSubMessageField}. + * + * @param fieldDescriptor the field whose value is the submessage + * @return a new Builder for the sub message + */ + public Builder getBuilderForSubMessageField(final FieldDescriptor fieldDescriptor) { + List<Builder> submessageBuilders = subtreeBuildersFromField.get(fieldDescriptor); + if (submessageBuilders == null) { + submessageBuilders = new ArrayList<Builder>(); + subtreeBuildersFromField.put(fieldDescriptor, submessageBuilders); + } + Builder subtreeBuilder = new Builder(); + submessageBuilders.add(subtreeBuilder); + return subtreeBuilder; + } + + /** + * Build the {@code TextFormatParseInfoTree}. + * + * @return the {@code TextFormatParseInfoTree} + */ + public TextFormatParseInfoTree build() { + return new TextFormatParseInfoTree(locationsFromField, subtreeBuildersFromField); + } + } +} diff --git a/java/core/src/main/java/com/google/protobuf/TextFormatParseLocation.java b/java/core/src/main/java/com/google/protobuf/TextFormatParseLocation.java new file mode 100644 index 00000000..cce286e1 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/TextFormatParseLocation.java @@ -0,0 +1,104 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// 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.Arrays; + +/** + * A location in the source code. + * + * <p>A location is the starting line number and starting column number. + */ +public final class TextFormatParseLocation { + + /** + * The empty location. + */ + public static final TextFormatParseLocation EMPTY = new TextFormatParseLocation(-1, -1); + + /** + * Create a location. + * + * @param line the starting line number + * @param column the starting column number + * @return a {@code ParseLocation} + */ + static TextFormatParseLocation create(int line, int column) { + if (line == -1 && column == -1) { + return EMPTY; + } + if (line < 0 || column < 0) { + throw new IllegalArgumentException( + String.format("line and column values must be >= 0: line %d, column: %d", line, column)); + } + return new TextFormatParseLocation(line, column); + } + + private final int line; + private final int column; + + private TextFormatParseLocation(int line, int column) { + this.line = line; + this.column = column; + } + + public int getLine() { + return line; + } + + public int getColumn() { + return column; + } + + @Override + public String toString() { + return String.format("ParseLocation{line=%d, column=%d}", line, column); + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof TextFormatParseLocation)) { + return false; + } + TextFormatParseLocation that = (TextFormatParseLocation) o; + return (this.line == that.getLine()) + && (this.column == that.getColumn()); + } + + @Override + public int hashCode() { + int[] values = {line, column}; + return Arrays.hashCode(values); + } +} diff --git a/java/src/main/java/com/google/protobuf/UninitializedMessageException.java b/java/core/src/main/java/com/google/protobuf/UninitializedMessageException.java index 5714c063..5714c063 100644 --- a/java/src/main/java/com/google/protobuf/UninitializedMessageException.java +++ b/java/core/src/main/java/com/google/protobuf/UninitializedMessageException.java diff --git a/java/src/main/java/com/google/protobuf/UnknownFieldSet.java b/java/core/src/main/java/com/google/protobuf/UnknownFieldSet.java index 7cd2250e..c906420d 100644 --- a/java/src/main/java/com/google/protobuf/UnknownFieldSet.java +++ b/java/core/src/main/java/com/google/protobuf/UnknownFieldSet.java @@ -76,6 +76,7 @@ public final class UnknownFieldSet implements MessageLite { public static UnknownFieldSet getDefaultInstance() { return defaultInstance; } + @Override public UnknownFieldSet getDefaultInstanceForType() { return defaultInstance; } @@ -126,6 +127,7 @@ public final class UnknownFieldSet implements MessageLite { } /** Serializes the set and writes it to {@code output}. */ + @Override public void writeTo(final CodedOutputStream output) throws IOException { for (final Map.Entry<Integer, Field> entry : fields.entrySet()) { entry.getValue().writeTo(entry.getKey(), output); @@ -146,6 +148,7 @@ public final class UnknownFieldSet implements MessageLite { * Serializes the message to a {@code ByteString} and returns it. This is * just a trivial wrapper around {@link #writeTo(CodedOutputStream)}. */ + @Override public ByteString toByteString() { try { final ByteString.CodedBuilder out = @@ -163,6 +166,7 @@ public final class UnknownFieldSet implements MessageLite { * Serializes the message to a {@code byte} array and returns it. This is * just a trivial wrapper around {@link #writeTo(CodedOutputStream)}. */ + @Override public byte[] toByteArray() { try { final byte[] result = new byte[getSerializedSize()]; @@ -181,12 +185,14 @@ public final class UnknownFieldSet implements MessageLite { * Serializes the message and writes it to {@code output}. This is just a * trivial wrapper around {@link #writeTo(CodedOutputStream)}. */ + @Override public void writeTo(final OutputStream output) throws IOException { final CodedOutputStream codedOutput = CodedOutputStream.newInstance(output); writeTo(codedOutput); codedOutput.flush(); } + @Override public void writeDelimitedTo(OutputStream output) throws IOException { final CodedOutputStream codedOutput = CodedOutputStream.newInstance(output); codedOutput.writeRawVarint32(getSerializedSize()); @@ -195,6 +201,7 @@ public final class UnknownFieldSet implements MessageLite { } /** Get the number of bytes required to encode this set. */ + @Override public int getSerializedSize() { int result = 0; for (final Map.Entry<Integer, Field> entry : fields.entrySet()) { @@ -228,6 +235,7 @@ public final class UnknownFieldSet implements MessageLite { return result; } + @Override public boolean isInitialized() { // UnknownFieldSets do not have required fields, so they are always // initialized. @@ -258,10 +266,12 @@ public final class UnknownFieldSet implements MessageLite { return newBuilder().mergeFrom(input).build(); } + @Override public Builder newBuilderForType() { return newBuilder(); } + @Override public Builder toBuilder() { return newBuilder().mergeFrom(this); } @@ -329,6 +339,7 @@ public final class UnknownFieldSet implements MessageLite { * in undefined behavior and can cause a {@code NullPointerException} to be * thrown. */ + @Override public UnknownFieldSet build() { getFieldBuilder(0); // Force lastField to be built. final UnknownFieldSet result; @@ -341,6 +352,7 @@ public final class UnknownFieldSet implements MessageLite { return result; } + @Override public UnknownFieldSet buildPartial() { // No required fields, so this is the same as build(). return build(); @@ -353,6 +365,7 @@ public final class UnknownFieldSet implements MessageLite { new UnknownFieldSet(fields)); } + @Override public UnknownFieldSet getDefaultInstanceForType() { return UnknownFieldSet.getDefaultInstance(); } @@ -364,6 +377,7 @@ public final class UnknownFieldSet implements MessageLite { } /** Reset the builder to an empty set. */ + @Override public Builder clear() { reinitialize(); return this; @@ -487,6 +501,7 @@ public final class UnknownFieldSet implements MessageLite { * Parse an entire message from {@code input} and merge its fields into * this set. */ + @Override public Builder mergeFrom(final CodedInputStream input) throws IOException { while (true) { final int tag = input.readTag(); @@ -536,8 +551,8 @@ public final class UnknownFieldSet implements MessageLite { * set being built. This is just a small wrapper around * {@link #mergeFrom(CodedInputStream)}. */ - public Builder mergeFrom(final ByteString data) - throws InvalidProtocolBufferException { + @Override + public Builder mergeFrom(final ByteString data) throws InvalidProtocolBufferException { try { final CodedInputStream input = data.newCodedInput(); mergeFrom(input); @@ -557,8 +572,8 @@ public final class UnknownFieldSet implements MessageLite { * set being built. This is just a small wrapper around * {@link #mergeFrom(CodedInputStream)}. */ - public Builder mergeFrom(final byte[] data) - throws InvalidProtocolBufferException { + @Override + public Builder mergeFrom(final byte[] data) throws InvalidProtocolBufferException { try { final CodedInputStream input = CodedInputStream.newInstance(data); mergeFrom(input); @@ -578,6 +593,7 @@ public final class UnknownFieldSet implements MessageLite { * set being built. This is just a small wrapper around * {@link #mergeFrom(CodedInputStream)}. */ + @Override public Builder mergeFrom(final InputStream input) throws IOException { final CodedInputStream codedInput = CodedInputStream.newInstance(input); mergeFrom(codedInput); @@ -585,8 +601,8 @@ public final class UnknownFieldSet implements MessageLite { return this; } - public boolean mergeDelimitedFrom(InputStream input) - throws IOException { + @Override + public boolean mergeDelimitedFrom(InputStream input) throws IOException { final int firstByte = input.read(); if (firstByte == -1) { return false; @@ -597,30 +613,29 @@ public final class UnknownFieldSet implements MessageLite { return true; } - public boolean mergeDelimitedFrom( - InputStream input, - ExtensionRegistryLite extensionRegistry) throws IOException { + @Override + public boolean mergeDelimitedFrom(InputStream input, ExtensionRegistryLite extensionRegistry) + throws IOException { // UnknownFieldSet has no extensions. return mergeDelimitedFrom(input); } - public Builder mergeFrom( - CodedInputStream input, - ExtensionRegistryLite extensionRegistry) throws IOException { + @Override + public Builder mergeFrom(CodedInputStream input, ExtensionRegistryLite extensionRegistry) + throws IOException { // UnknownFieldSet has no extensions. return mergeFrom(input); } - public Builder mergeFrom( - ByteString data, - ExtensionRegistryLite extensionRegistry) + @Override + public Builder mergeFrom(ByteString data, ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { // UnknownFieldSet has no extensions. return mergeFrom(data); } - public Builder mergeFrom(byte[] data, int off, int len) - throws InvalidProtocolBufferException { + @Override + public Builder mergeFrom(byte[] data, int off, int len) throws InvalidProtocolBufferException { try { final CodedInputStream input = CodedInputStream.newInstance(data, off, len); @@ -636,29 +651,37 @@ public final class UnknownFieldSet implements MessageLite { } } - public Builder mergeFrom( - byte[] data, - ExtensionRegistryLite extensionRegistry) + @Override + public Builder mergeFrom(byte[] data, ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { // UnknownFieldSet has no extensions. return mergeFrom(data); } - public Builder mergeFrom( - byte[] data, int off, int len, - ExtensionRegistryLite extensionRegistry) + @Override + public Builder mergeFrom(byte[] data, int off, int len, ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { // UnknownFieldSet has no extensions. return mergeFrom(data, off, len); } - public Builder mergeFrom( - InputStream input, - ExtensionRegistryLite extensionRegistry) throws IOException { + @Override + public Builder mergeFrom(InputStream input, ExtensionRegistryLite extensionRegistry) + throws IOException { // UnknownFieldSet has no extensions. return mergeFrom(input); } + @Override + public Builder mergeFrom(MessageLite m) { + if (m instanceof UnknownFieldSet) { + return mergeFrom((UnknownFieldSet) m); + } + throw new IllegalArgumentException( + "mergeFrom(MessageLite) can only merge messages of the same type."); + } + + @Override public boolean isInitialized() { // UnknownFieldSets do not have required fields, so they are always // initialized. @@ -987,6 +1010,7 @@ public final class UnknownFieldSet implements MessageLite { * Parser to implement MessageLite interface. */ public static final class Parser extends AbstractParser<UnknownFieldSet> { + @Override public UnknownFieldSet parsePartialFrom( CodedInputStream input, ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { @@ -1004,6 +1028,7 @@ public final class UnknownFieldSet implements MessageLite { } private static final Parser PARSER = new Parser(); + @Override public final Parser getParserForType() { return PARSER; } diff --git a/java/src/main/java/com/google/protobuf/UnknownFieldSetLite.java b/java/core/src/main/java/com/google/protobuf/UnknownFieldSetLite.java index 435ad4d4..9500f905 100644 --- a/java/src/main/java/com/google/protobuf/UnknownFieldSetLite.java +++ b/java/core/src/main/java/com/google/protobuf/UnknownFieldSetLite.java @@ -61,15 +61,6 @@ public final class UnknownFieldSetLite { public static UnknownFieldSetLite getDefaultInstance() { return DEFAULT_INSTANCE; } - - /** - * Returns an empty {@code UnknownFieldSetLite.Builder}. - * - * <p>For use by generated code only. - */ - public static Builder newBuilder() { - return new Builder(); - } /** * Returns a new mutable instance. @@ -262,6 +253,21 @@ public final class UnknownFieldSetLite { return hashCode; } + /** + * Prints a String representation of the unknown field set. + * + * <p>For use by generated code only. + * + * @param buffer the buffer to write to + * @param indent the number of spaces the fields should be indented by + */ + final void printWithIndent(StringBuilder buffer, int indent) { + for (int i = 0; i < count; i++) { + int fieldNumber = WireFormat.getTagFieldNumber(tags[i]); + MessageLiteToString.printField(buffer, indent, String.valueOf(fieldNumber), objects[i]); + } + } + private void storeField(int tag, Object value) { ensureCapacity(); @@ -369,90 +375,4 @@ public final class UnknownFieldSetLite { } return this; } - - /** - * Builder for {@link UnknownFieldSetLite}s. - * - * <p>Use {@link UnknownFieldSet#newBuilder()} to construct a {@code Builder}. - * - * <p>For use by generated code only. - */ - // TODO(dweis): Update the mutable API to no longer need this builder and delete. - public static final class Builder { - - private UnknownFieldSetLite set; - - private Builder() { - this.set = null; - } - - /** - * Ensures internal state is initialized for use. - */ - private void ensureNotBuilt() { - if (set == null) { - set = new UnknownFieldSetLite(); - } - - set.checkMutable(); - } - - /** - * Parse a single field from {@code input} and merge it into this set. - * - * <p>For use by generated code only. - * - * @param tag The field's tag number, which was already parsed. - * @return {@code false} if the tag is an end group tag. - */ - boolean mergeFieldFrom(final int tag, final CodedInputStream input) throws IOException { - ensureNotBuilt(); - return set.mergeFieldFrom(tag, input); - } - - /** - * Convenience method for merging a new field containing a single varint - * value. This is used in particular when an unknown enum value is - * encountered. - * - * <p>For use by generated code only. - */ - Builder mergeVarintField(int fieldNumber, int value) { - ensureNotBuilt(); - set.mergeVarintField(fieldNumber, value); - return this; - } - - /** - * Convenience method for merging a length-delimited field. - * - * <p>For use by generated code only. - */ - public Builder mergeLengthDelimitedField(final int fieldNumber, final ByteString value) { - ensureNotBuilt(); - set.mergeLengthDelimitedField(fieldNumber, value); - return this; - } - - /** - * Build the {@link UnknownFieldSetLite} and return it. - * - * <p>Once {@code build()} has been called, the {@code Builder} will no - * longer be usable. Calling any method after {@code build()} will result - * in undefined behavior and can cause an - * {@code UnsupportedOperationException} to be thrown. - * - * <p>For use by generated code only. - */ - public UnknownFieldSetLite build() { - if (set == null) { - return DEFAULT_INSTANCE; - } - - set.checkMutable(); - set.makeImmutable(); - - return set; - } - } } diff --git a/java/src/main/java/com/google/protobuf/UnmodifiableLazyStringList.java b/java/core/src/main/java/com/google/protobuf/UnmodifiableLazyStringList.java index 5257c5a2..30e87911 100644 --- a/java/src/main/java/com/google/protobuf/UnmodifiableLazyStringList.java +++ b/java/core/src/main/java/com/google/protobuf/UnmodifiableLazyStringList.java @@ -68,42 +68,42 @@ public class UnmodifiableLazyStringList extends AbstractList<String> return list.size(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public ByteString getByteString(int index) { return list.getByteString(index); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public void add(ByteString element) { throw new UnsupportedOperationException(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public void set(int index, ByteString element) { throw new UnsupportedOperationException(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public boolean addAllByteString(Collection<? extends ByteString> element) { throw new UnsupportedOperationException(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public byte[] getByteArray(int index) { return list.getByteArray(index); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public void add(byte[] element) { throw new UnsupportedOperationException(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public void set(int index, byte[] element) { throw new UnsupportedOperationException(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public boolean addAllByteArray(Collection<byte[]> element) { throw new UnsupportedOperationException(); } @@ -113,47 +113,47 @@ public class UnmodifiableLazyStringList extends AbstractList<String> return new ListIterator<String>() { ListIterator<String> iter = list.listIterator(index); - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public boolean hasNext() { return iter.hasNext(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public String next() { return iter.next(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public boolean hasPrevious() { return iter.hasPrevious(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public String previous() { return iter.previous(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public int nextIndex() { return iter.nextIndex(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public int previousIndex() { return iter.previousIndex(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public void remove() { throw new UnsupportedOperationException(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public void set(String o) { throw new UnsupportedOperationException(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public void add(String o) { throw new UnsupportedOperationException(); } @@ -165,45 +165,45 @@ public class UnmodifiableLazyStringList extends AbstractList<String> return new Iterator<String>() { Iterator<String> iter = list.iterator(); - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public boolean hasNext() { return iter.hasNext(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public String next() { return iter.next(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public void remove() { throw new UnsupportedOperationException(); } }; } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public List<?> getUnderlyingElements() { // The returned value is already unmodifiable. return list.getUnderlyingElements(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public void mergeFrom(LazyStringList other) { throw new UnsupportedOperationException(); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public List<byte[]> asByteArrayList() { return Collections.unmodifiableList(list.asByteArrayList()); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public List<ByteString> asByteStringList() { return Collections.unmodifiableList(list.asByteStringList()); } - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public LazyStringList getUnmodifiableView() { return this; } diff --git a/java/core/src/main/java/com/google/protobuf/UnsafeByteOperations.java b/java/core/src/main/java/com/google/protobuf/UnsafeByteOperations.java new file mode 100644 index 00000000..0fbf4d40 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/UnsafeByteOperations.java @@ -0,0 +1,84 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// 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.io.IOException; +import java.nio.ByteBuffer; + +/** + * Provides a number of unsafe byte operations to be used by advanced applications with high + * performance requirements. These methods are referred to as "unsafe" due to the fact that they + * potentially expose the backing buffer of a {@link ByteString} to the application. + * + * <p><strong>DISCLAIMER:</strong> The methods in this class should only be called if it is + * guaranteed that the buffer backing the {@link ByteString} will never change! Mutation of a + * {@link ByteString} can lead to unexpected and undesirable consequences in your application, + * and will likely be difficult to debug. Proceed with caution! + */ +@ExperimentalApi +public final class UnsafeByteOperations { + private UnsafeByteOperations() {} + + /** + * An unsafe operation that returns a {@link ByteString} that is backed by the provided buffer. + * + * @param buffer the Java NIO buffer to be wrapped + * @return a {@link ByteString} backed by the provided buffer + */ + public static ByteString unsafeWrap(ByteBuffer buffer) { + if (buffer.hasArray()) { + final int offset = buffer.arrayOffset(); + return ByteString.wrap(buffer.array(), offset + buffer.position(), buffer.remaining()); + } else { + return new NioByteString(buffer); + } + } + + /** + * Writes the given {@link ByteString} to the provided {@link ByteOutput}. Calling this method may + * result in multiple operations on the target {@link ByteOutput} + * (i.e. for roped {@link ByteString}s). + * + * <p>This method exposes the internal backing buffer(s) of the {@link ByteString} to the {@link + * ByteOutput} in order to avoid additional copying overhead. It would be possible for a malicious + * {@link ByteOutput} to corrupt the {@link ByteString}. Use with caution! + * + * <p> NOTE: The {@link ByteOutput} <strong>MUST NOT</strong> modify the provided buffers. Doing + * so may result in corrupted data, which would be difficult to debug. + * + * @param bytes the {@link ByteString} to be written + * @param output the output to receive the bytes + * @throws IOException if an I/O error occurs + */ + public static void unsafeWriteTo(ByteString bytes, ByteOutput output) throws IOException { + bytes.writeTo(output); + } +} diff --git a/java/core/src/main/java/com/google/protobuf/Utf8.java b/java/core/src/main/java/com/google/protobuf/Utf8.java new file mode 100644 index 00000000..308c69e9 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/Utf8.java @@ -0,0 +1,1683 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// 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 static java.lang.Character.MAX_SURROGATE; +import static java.lang.Character.MIN_SURROGATE; +import static java.lang.Character.isSurrogatePair; +import static java.lang.Character.toCodePoint; + +import java.lang.reflect.Field; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.security.AccessController; +import java.security.PrivilegedExceptionAction; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A set of low-level, high-performance static utility methods related + * to the UTF-8 character encoding. This class has no dependencies + * outside of the core JDK libraries. + * + * <p>There are several variants of UTF-8. The one implemented by + * this class is the restricted definition of UTF-8 introduced in + * Unicode 3.1, which mandates the rejection of "overlong" byte + * sequences as well as rejection of 3-byte surrogate codepoint byte + * sequences. Note that the UTF-8 decoder included in Oracle's JDK + * has been modified to also reject "overlong" byte sequences, but (as + * of 2011) still accepts 3-byte surrogate codepoint byte sequences. + * + * <p>The byte sequences considered valid by this class are exactly + * those that can be roundtrip converted to Strings and back to bytes + * using the UTF-8 charset, without loss: <pre> {@code + * Arrays.equals(bytes, new String(bytes, Internal.UTF_8).getBytes(Internal.UTF_8)) + * }</pre> + * + * <p>See the Unicode Standard,</br> + * Table 3-6. <em>UTF-8 Bit Distribution</em>,</br> + * Table 3-7. <em>Well Formed UTF-8 Byte Sequences</em>. + * + * <p>This class supports decoding of partial byte sequences, so that the + * bytes in a complete UTF-8 byte sequences can be stored in multiple + * segments. Methods typically return {@link #MALFORMED} if the partial + * byte sequence is definitely not well-formed, {@link #COMPLETE} if it is + * well-formed in the absence of additional input, or if the byte sequence + * apparently terminated in the middle of a character, an opaque integer + * "state" value containing enough information to decode the character when + * passed to a subsequent invocation of a partial decoding method. + * + * @author martinrb@google.com (Martin Buchholz) + */ +// TODO(nathanmittler): Copy changes in this class back to Guava +final class Utf8 { + private static final Logger logger = Logger.getLogger(Utf8.class.getName()); + + /** + * UTF-8 is a runtime hot spot so we attempt to provide heavily optimized implementations + * depending on what is available on the platform. The processor is the platform-optimized + * delegate for which all methods are delegated directly to. + */ + private static final Processor processor = + UnsafeProcessor.isAvailable() ? new UnsafeProcessor() : new SafeProcessor(); + + /** + * A mask used when performing unsafe reads to determine if a long value contains any non-ASCII + * characters (i.e. any byte >= 0x80). + */ + private static final long ASCII_MASK_LONG = 0x8080808080808080L; + + /** + * Maximum number of bytes per Java UTF-16 char in UTF-8. + * @see java.nio.charset.CharsetEncoder#maxBytesPerChar() + */ + static final int MAX_BYTES_PER_CHAR = 3; + + /** + * State value indicating that the byte sequence is well-formed and + * complete (no further bytes are needed to complete a character). + */ + public static final int COMPLETE = 0; + + /** + * State value indicating that the byte sequence is definitely not + * well-formed. + */ + public static final int MALFORMED = -1; + + /** + * Used by {@code Unsafe} UTF-8 string validation logic to determine the minimum string length + * above which to employ an optimized algorithm for counting ASCII characters. The reason for this + * threshold is that for small strings, the optimization may not be beneficial or may even + * negatively impact performance since it requires additional logic to avoid unaligned reads + * (when calling {@code Unsafe.getLong}). This threshold guarantees that even if the initial + * offset is unaligned, we're guaranteed to make at least one call to {@code Unsafe.getLong()} + * which provides a performance improvement that entirely subsumes the cost of the additional + * logic. + */ + private static final int UNSAFE_COUNT_ASCII_THRESHOLD = 16; + + // Other state values include the partial bytes of the incomplete + // character to be decoded in the simplest way: we pack the bytes + // into the state int in little-endian order. For example: + // + // int state = byte1 ^ (byte2 << 8) ^ (byte3 << 16); + // + // Such a state is unpacked thus (note the ~ operation for byte2 to + // undo byte1's sign-extension bits): + // + // int byte1 = (byte) state; + // int byte2 = (byte) ~(state >> 8); + // int byte3 = (byte) (state >> 16); + // + // We cannot store a zero byte in the state because it would be + // indistinguishable from the absence of a byte. But we don't need + // to, because partial bytes must always be negative. When building + // a state, we ensure that byte1 is negative and subsequent bytes + // are valid trailing bytes. + + /** + * Returns {@code true} if the given byte array is a well-formed + * UTF-8 byte sequence. + * + * <p>This is a convenience method, equivalent to a call to {@code + * isValidUtf8(bytes, 0, bytes.length)}. + */ + public static boolean isValidUtf8(byte[] bytes) { + return processor.isValidUtf8(bytes, 0, bytes.length); + } + + /** + * Returns {@code true} if the given byte array slice is a + * well-formed UTF-8 byte sequence. The range of bytes to be + * checked extends from index {@code index}, inclusive, to {@code + * limit}, exclusive. + * + * <p>This is a convenience method, equivalent to {@code + * partialIsValidUtf8(bytes, index, limit) == Utf8.COMPLETE}. + */ + public static boolean isValidUtf8(byte[] bytes, int index, int limit) { + return processor.isValidUtf8(bytes, index, limit); + } + + /** + * Tells whether the given byte array slice is a well-formed, + * malformed, or incomplete UTF-8 byte sequence. The range of bytes + * to be checked extends from index {@code index}, inclusive, to + * {@code limit}, exclusive. + * + * @param state either {@link Utf8#COMPLETE} (if this is the initial decoding + * operation) or the value returned from a call to a partial decoding method + * for the previous bytes + * + * @return {@link #MALFORMED} if the partial byte sequence is + * definitely not well-formed, {@link #COMPLETE} if it is well-formed + * (no additional input needed), or if the byte sequence is + * "incomplete", i.e. apparently terminated in the middle of a character, + * an opaque integer "state" value containing enough information to + * decode the character when passed to a subsequent invocation of a + * partial decoding method. + */ + public static int partialIsValidUtf8(int state, byte[] bytes, int index, int limit) { + return processor.partialIsValidUtf8(state, bytes, index, limit); + } + + private static int incompleteStateFor(int byte1) { + return (byte1 > (byte) 0xF4) ? + MALFORMED : byte1; + } + + private static int incompleteStateFor(int byte1, int byte2) { + return (byte1 > (byte) 0xF4 || + byte2 > (byte) 0xBF) ? + MALFORMED : byte1 ^ (byte2 << 8); + } + + private static int incompleteStateFor(int byte1, int byte2, int byte3) { + return (byte1 > (byte) 0xF4 || + byte2 > (byte) 0xBF || + byte3 > (byte) 0xBF) ? + MALFORMED : byte1 ^ (byte2 << 8) ^ (byte3 << 16); + } + + private static int incompleteStateFor(byte[] bytes, int index, int limit) { + int byte1 = bytes[index - 1]; + switch (limit - index) { + case 0: return incompleteStateFor(byte1); + case 1: return incompleteStateFor(byte1, bytes[index]); + case 2: return incompleteStateFor(byte1, bytes[index], bytes[index + 1]); + default: throw new AssertionError(); + } + } + + private static int incompleteStateFor( + final ByteBuffer buffer, final int byte1, final int index, final int remaining) { + switch (remaining) { + case 0: + return incompleteStateFor(byte1); + case 1: + return incompleteStateFor(byte1, buffer.get(index)); + case 2: + return incompleteStateFor(byte1, buffer.get(index), buffer.get(index + 1)); + default: + throw new AssertionError(); + } + } + + // These UTF-8 handling methods are copied from Guava's Utf8 class with a modification to throw + // a protocol buffer local exception. This exception is then caught in CodedOutputStream so it can + // fallback to more lenient behavior. + + static class UnpairedSurrogateException extends IllegalArgumentException { + private UnpairedSurrogateException(int index, int length) { + super("Unpaired surrogate at index " + index + " of " + length); + } + } + + /** + * Returns the number of bytes in the UTF-8-encoded form of {@code sequence}. For a string, + * this method is equivalent to {@code string.getBytes(UTF_8).length}, but is more efficient in + * both time and space. + * + * @throws IllegalArgumentException if {@code sequence} contains ill-formed UTF-16 (unpaired + * surrogates) + */ + static int encodedLength(CharSequence sequence) { + // Warning to maintainers: this implementation is highly optimized. + int utf16Length = sequence.length(); + int utf8Length = utf16Length; + int i = 0; + + // This loop optimizes for pure ASCII. + while (i < utf16Length && sequence.charAt(i) < 0x80) { + i++; + } + + // This loop optimizes for chars less than 0x800. + for (; i < utf16Length; i++) { + char c = sequence.charAt(i); + if (c < 0x800) { + utf8Length += ((0x7f - c) >>> 31); // branch free! + } else { + utf8Length += encodedLengthGeneral(sequence, i); + break; + } + } + + if (utf8Length < utf16Length) { + // Necessary and sufficient condition for overflow because of maximum 3x expansion + throw new IllegalArgumentException("UTF-8 length does not fit in int: " + + (utf8Length + (1L << 32))); + } + return utf8Length; + } + + private static int encodedLengthGeneral(CharSequence sequence, int start) { + int utf16Length = sequence.length(); + int utf8Length = 0; + for (int i = start; i < utf16Length; i++) { + char c = sequence.charAt(i); + if (c < 0x800) { + utf8Length += (0x7f - c) >>> 31; // branch free! + } else { + utf8Length += 2; + // jdk7+: if (Character.isSurrogate(c)) { + if (Character.MIN_SURROGATE <= c && c <= Character.MAX_SURROGATE) { + // Check that we have a well-formed surrogate pair. + int cp = Character.codePointAt(sequence, i); + if (cp < Character.MIN_SUPPLEMENTARY_CODE_POINT) { + throw new UnpairedSurrogateException(i, utf16Length); + } + i++; + } + } + } + return utf8Length; + } + + static int encode(CharSequence in, byte[] out, int offset, int length) { + return processor.encodeUtf8(in, out, offset, length); + } + // End Guava UTF-8 methods. + + /** + * Determines if the given {@link ByteBuffer} is a valid UTF-8 string. + * + * <p>Selects an optimal algorithm based on the type of {@link ByteBuffer} (i.e. heap or direct) + * and the capabilities of the platform. + * + * @param buffer the buffer to check. + * @see Utf8#isValidUtf8(byte[], int, int) + */ + static boolean isValidUtf8(ByteBuffer buffer) { + return processor.isValidUtf8(buffer, buffer.position(), buffer.remaining()); + } + + /** + * Determines if the given {@link ByteBuffer} is a partially valid UTF-8 string. + * + * <p>Selects an optimal algorithm based on the type of {@link ByteBuffer} (i.e. heap or direct) + * and the capabilities of the platform. + * + * @param buffer the buffer to check. + * @see Utf8#partialIsValidUtf8(int, byte[], int, int) + */ + static int partialIsValidUtf8(int state, ByteBuffer buffer, int index, int limit) { + return processor.partialIsValidUtf8(state, buffer, index, limit); + } + + /** + * Encodes the given characters to the target {@link ByteBuffer} using UTF-8 encoding. + * + * <p>Selects an optimal algorithm based on the type of {@link ByteBuffer} (i.e. heap or direct) + * and the capabilities of the platform. + * + * @param in the source string to be encoded + * @param out the target buffer to receive the encoded string. + * @see Utf8#encode(CharSequence, byte[], int, int) + */ + static void encodeUtf8(CharSequence in, ByteBuffer out) { + processor.encodeUtf8(in, out); + } + + /** + * Counts (approximately) the number of consecutive ASCII characters in the given buffer. + * The byte order of the {@link ByteBuffer} does not matter, so performance can be improved if + * native byte order is used (i.e. no byte-swapping in {@link ByteBuffer#getLong(int)}). + * + * @param buffer the buffer to be scanned for ASCII chars + * @param index the starting index of the scan + * @param limit the limit within buffer for the scan + * @return the number of ASCII characters found. The stopping position will be at or + * before the first non-ASCII byte. + */ + private static int estimateConsecutiveAscii(ByteBuffer buffer, int index, int limit) { + int i = index; + final int lim = limit - 7; + // This simple loop stops when we encounter a byte >= 0x80 (i.e. non-ASCII). + // To speed things up further, we're reading longs instead of bytes so we use a mask to + // determine if any byte in the current long is non-ASCII. + for (; i < lim && (buffer.getLong(i) & ASCII_MASK_LONG) == 0; i += 8) {} + return i - index; + } + + /** + * A processor of UTF-8 strings, providing methods for checking validity and encoding. + */ + // TODO(nathanmittler): Add support for Memory/MemoryBlock on Android. + abstract static class Processor { + /** + * Returns {@code true} if the given byte array slice is a + * well-formed UTF-8 byte sequence. The range of bytes to be + * checked extends from index {@code index}, inclusive, to {@code + * limit}, exclusive. + * + * <p>This is a convenience method, equivalent to {@code + * partialIsValidUtf8(bytes, index, limit) == Utf8.COMPLETE}. + */ + final boolean isValidUtf8(byte[] bytes, int index, int limit) { + return partialIsValidUtf8(COMPLETE, bytes, index, limit) == COMPLETE; + } + + /** + * Tells whether the given byte array slice is a well-formed, + * malformed, or incomplete UTF-8 byte sequence. The range of bytes + * to be checked extends from index {@code index}, inclusive, to + * {@code limit}, exclusive. + * + * @param state either {@link Utf8#COMPLETE} (if this is the initial decoding + * operation) or the value returned from a call to a partial decoding method + * for the previous bytes + * + * @return {@link #MALFORMED} if the partial byte sequence is + * definitely not well-formed, {@link #COMPLETE} if it is well-formed + * (no additional input needed), or if the byte sequence is + * "incomplete", i.e. apparently terminated in the middle of a character, + * an opaque integer "state" value containing enough information to + * decode the character when passed to a subsequent invocation of a + * partial decoding method. + */ + abstract int partialIsValidUtf8(int state, byte[] bytes, int index, int limit); + + /** + * Returns {@code true} if the given portion of the {@link ByteBuffer} is a + * well-formed UTF-8 byte sequence. The range of bytes to be + * checked extends from index {@code index}, inclusive, to {@code + * limit}, exclusive. + * + * <p>This is a convenience method, equivalent to {@code + * partialIsValidUtf8(bytes, index, limit) == Utf8.COMPLETE}. + */ + final boolean isValidUtf8(ByteBuffer buffer, int index, int limit) { + return partialIsValidUtf8(COMPLETE, buffer, index, limit) == COMPLETE; + } + + /** + * Indicates whether or not the given buffer contains a valid UTF-8 string. + * + * @param buffer the buffer to check. + * @return {@code true} if the given buffer contains a valid UTF-8 string. + */ + final int partialIsValidUtf8( + final int state, final ByteBuffer buffer, int index, final int limit) { + if (buffer.hasArray()) { + final int offset = buffer.arrayOffset(); + return partialIsValidUtf8(state, buffer.array(), offset + index, offset + limit); + } else if (buffer.isDirect()){ + return partialIsValidUtf8Direct(state, buffer, index, limit); + } + return partialIsValidUtf8Default(state, buffer, index, limit); + } + + /** + * Performs validation for direct {@link ByteBuffer} instances. + */ + abstract int partialIsValidUtf8Direct( + final int state, final ByteBuffer buffer, int index, final int limit); + + /** + * Performs validation for {@link ByteBuffer} instances using the {@link ByteBuffer} API rather + * than potentially faster approaches. This first completes validation for the current + * character (provided by {@code state}) and then finishes validation for the sequence. + */ + final int partialIsValidUtf8Default( + final int state, final ByteBuffer buffer, int index, final int limit) { + if (state != COMPLETE) { + // The previous decoding operation was incomplete (or malformed). + // We look for a well-formed sequence consisting of bytes from + // the previous decoding operation (stored in state) together + // with bytes from the array slice. + // + // We expect such "straddler characters" to be rare. + + if (index >= limit) { // No bytes? No progress. + return state; + } + + byte byte1 = (byte) state; + // byte1 is never ASCII. + if (byte1 < (byte) 0xE0) { + // two-byte form + + // Simultaneously checks for illegal trailing-byte in + // leading position and overlong 2-byte form. + if (byte1 < (byte) 0xC2 + // byte2 trailing-byte test + || buffer.get(index++) > (byte) 0xBF) { + return MALFORMED; + } + } else if (byte1 < (byte) 0xF0) { + // three-byte form + + // Get byte2 from saved state or array + byte byte2 = (byte) ~(state >> 8); + if (byte2 == 0) { + byte2 = buffer.get(index++); + if (index >= limit) { + return incompleteStateFor(byte1, byte2); + } + } + if (byte2 > (byte) 0xBF + // overlong? 5 most significant bits must not all be zero + || (byte1 == (byte) 0xE0 && byte2 < (byte) 0xA0) + // illegal surrogate codepoint? + || (byte1 == (byte) 0xED && byte2 >= (byte) 0xA0) + // byte3 trailing-byte test + || buffer.get(index++) > (byte) 0xBF) { + return MALFORMED; + } + } else { + // four-byte form + + // Get byte2 and byte3 from saved state or array + byte byte2 = (byte) ~(state >> 8); + byte byte3 = 0; + if (byte2 == 0) { + byte2 = buffer.get(index++); + if (index >= limit) { + return incompleteStateFor(byte1, byte2); + } + } else { + byte3 = (byte) (state >> 16); + } + if (byte3 == 0) { + byte3 = buffer.get(index++); + if (index >= limit) { + return incompleteStateFor(byte1, byte2, byte3); + } + } + + // If we were called with state == MALFORMED, then byte1 is 0xFF, + // which never occurs in well-formed UTF-8, and so we will return + // MALFORMED again below. + + if (byte2 > (byte) 0xBF + // Check that 1 <= plane <= 16. Tricky optimized form of: + // if (byte1 > (byte) 0xF4 || + // byte1 == (byte) 0xF0 && byte2 < (byte) 0x90 || + // byte1 == (byte) 0xF4 && byte2 > (byte) 0x8F) + || (((byte1 << 28) + (byte2 - (byte) 0x90)) >> 30) != 0 + // byte3 trailing-byte test + || byte3 > (byte) 0xBF + // byte4 trailing-byte test + || buffer.get(index++) > (byte) 0xBF) { + return MALFORMED; + } + } + } + + // Finish validation for the sequence. + return partialIsValidUtf8(buffer, index, limit); + } + + /** + * Performs validation for {@link ByteBuffer} instances using the {@link ByteBuffer} API rather + * than potentially faster approaches. + */ + private static int partialIsValidUtf8(final ByteBuffer buffer, int index, final int limit) { + index += estimateConsecutiveAscii(buffer, index, limit); + + for (;;) { + // Optimize for interior runs of ASCII bytes. + // TODO(nathanmittler): Consider checking 8 bytes at a time after some threshold? + // Maybe after seeing a few in a row that are ASCII, go back to fast mode? + int byte1; + do { + if (index >= limit) { + return COMPLETE; + } + } while ((byte1 = buffer.get(index++)) >= 0); + + // If we're here byte1 is not ASCII. Only need to handle 2-4 byte forms. + if (byte1 < (byte) 0xE0) { + // Two-byte form (110xxxxx 10xxxxxx) + if (index >= limit) { + // Incomplete sequence + return byte1; + } + + // Simultaneously checks for illegal trailing-byte in + // leading position and overlong 2-byte form. + if (byte1 < (byte) 0xC2 || buffer.get(index) > (byte) 0xBF) { + return MALFORMED; + } + index++; + } else if (byte1 < (byte) 0xF0) { + // Three-byte form (1110xxxx 10xxxxxx 10xxxxxx) + if (index >= limit - 1) { + // Incomplete sequence + return incompleteStateFor(buffer, byte1, index, limit - index); + } + + final byte byte2 = buffer.get(index++); + if (byte2 > (byte) 0xBF + // overlong? 5 most significant bits must not all be zero + || (byte1 == (byte) 0xE0 && byte2 < (byte) 0xA0) + // check for illegal surrogate codepoints + || (byte1 == (byte) 0xED && byte2 >= (byte) 0xA0) + // byte3 trailing-byte test + || buffer.get(index) > (byte) 0xBF) { + return MALFORMED; + } + index++; + } else { + // Four-byte form (1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx) + if (index >= limit - 2) { + // Incomplete sequence + return incompleteStateFor(buffer, byte1, index, limit - index); + } + + // TODO(nathanmittler): Consider using getInt() to improve performance. + final int byte2 = buffer.get(index++); + if (byte2 > (byte) 0xBF + // Check that 1 <= plane <= 16. Tricky optimized form of: + // if (byte1 > (byte) 0xF4 || + // byte1 == (byte) 0xF0 && byte2 < (byte) 0x90 || + // byte1 == (byte) 0xF4 && byte2 > (byte) 0x8F) + || (((byte1 << 28) + (byte2 - (byte) 0x90)) >> 30) != 0 + // byte3 trailing-byte test + || buffer.get(index++) > (byte) 0xBF + // byte4 trailing-byte test + || buffer.get(index++) > (byte) 0xBF) { + return MALFORMED; + } + } + } + } + + /** + * Encodes an input character sequence ({@code in}) to UTF-8 in the target array ({@code out}). + * For a string, this method is similar to + * <pre>{@code + * byte[] a = string.getBytes(UTF_8); + * System.arraycopy(a, 0, bytes, offset, a.length); + * return offset + a.length; + * }</pre> + * + * but is more efficient in both time and space. One key difference is that this method + * requires paired surrogates, and therefore does not support chunking. + * While {@code String.getBytes(UTF_8)} replaces unpaired surrogates with the default + * replacement character, this method throws {@link UnpairedSurrogateException}. + * + * <p>To ensure sufficient space in the output buffer, either call {@link #encodedLength} to + * compute the exact amount needed, or leave room for + * {@code Utf8.MAX_BYTES_PER_CHAR * sequence.length()}, which is the largest possible number + * of bytes that any input can be encoded to. + * + * @param in the input character sequence to be encoded + * @param out the target array + * @param offset the starting offset in {@code bytes} to start writing at + * @param length the length of the {@code bytes}, starting from {@code offset} + * @throws UnpairedSurrogateException if {@code sequence} contains ill-formed UTF-16 (unpaired + * surrogates) + * @throws ArrayIndexOutOfBoundsException if {@code sequence} encoded in UTF-8 is longer than + * {@code bytes.length - offset} + * @return the new offset, equivalent to {@code offset + Utf8.encodedLength(sequence)} + */ + abstract int encodeUtf8(CharSequence in, byte[] out, int offset, int length); + + /** + * Encodes an input character sequence ({@code in}) to UTF-8 in the target buffer ({@code out}). + * Upon returning from this method, the {@code out} position will point to the position after + * the last encoded byte. This method requires paired surrogates, and therefore does not + * support chunking. + * + * <p>To ensure sufficient space in the output buffer, either call {@link #encodedLength} to + * compute the exact amount needed, or leave room for + * {@code Utf8.MAX_BYTES_PER_CHAR * in.length()}, which is the largest possible number + * of bytes that any input can be encoded to. + * + * @param in the source character sequence to be encoded + * @param out the target buffer + * @throws UnpairedSurrogateException if {@code in} contains ill-formed UTF-16 (unpaired + * surrogates) + * @throws ArrayIndexOutOfBoundsException if {@code in} encoded in UTF-8 is longer than + * {@code out.remaining()} + */ + final void encodeUtf8(CharSequence in, ByteBuffer out) { + if (out.hasArray()) { + final int offset = out.arrayOffset(); + int endIndex = + Utf8.encode(in, out.array(), offset + out.position(), out.remaining()); + out.position(endIndex - offset); + } else if (out.isDirect()) { + encodeUtf8Direct(in, out); + } else { + encodeUtf8Default(in, out); + } + } + + /** + * Encodes the input character sequence to a direct {@link ByteBuffer} instance. + */ + abstract void encodeUtf8Direct(CharSequence in, ByteBuffer out); + + /** + * Encodes the input character sequence to a {@link ByteBuffer} instance using the {@link + * ByteBuffer} API, rather than potentially faster approaches. + */ + final void encodeUtf8Default(CharSequence in, ByteBuffer out) { + final int inLength = in.length(); + int outIx = out.position(); + int inIx = 0; + + // Since ByteBuffer.putXXX() already checks boundaries for us, no need to explicitly check + // access. Assume the buffer is big enough and let it handle the out of bounds exception + // if it occurs. + try { + // Designed to take advantage of + // https://wikis.oracle.com/display/HotSpotInternals/RangeCheckElimination + for (char c; inIx < inLength && (c = in.charAt(inIx)) < 0x80; ++inIx) { + out.put(outIx + inIx, (byte) c); + } + if (inIx == inLength) { + // Successfully encoded the entire string. + out.position(outIx + inIx); + return; + } + + outIx += inIx; + for (char c; inIx < inLength; ++inIx, ++outIx) { + c = in.charAt(inIx); + if (c < 0x80) { + // One byte (0xxx xxxx) + out.put(outIx, (byte) c); + } else if (c < 0x800) { + // Two bytes (110x xxxx 10xx xxxx) + + // Benchmarks show put performs better than putShort here (for HotSpot). + out.put(outIx++, (byte) (0xC0 | (c >>> 6))); + out.put(outIx, (byte) (0x80 | (0x3F & c))); + } else if (c < MIN_SURROGATE || MAX_SURROGATE < c) { + // Three bytes (1110 xxxx 10xx xxxx 10xx xxxx) + // Maximum single-char code point is 0xFFFF, 16 bits. + + // Benchmarks show put performs better than putShort here (for HotSpot). + out.put(outIx++, (byte) (0xE0 | (c >>> 12))); + out.put(outIx++, (byte) (0x80 | (0x3F & (c >>> 6)))); + out.put(outIx, (byte) (0x80 | (0x3F & c))); + } else { + // Four bytes (1111 xxxx 10xx xxxx 10xx xxxx 10xx xxxx) + + // Minimum code point represented by a surrogate pair is 0x10000, 17 bits, four UTF-8 + // bytes + final char low; + if (inIx + 1 == inLength || !isSurrogatePair(c, (low = in.charAt(++inIx)))) { + throw new UnpairedSurrogateException(inIx, inLength); + } + // TODO(nathanmittler): Consider using putInt() to improve performance. + int codePoint = toCodePoint(c, low); + out.put(outIx++, (byte) ((0xF << 4) | (codePoint >>> 18))); + out.put(outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 12)))); + out.put(outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 6)))); + out.put(outIx, (byte) (0x80 | (0x3F & codePoint))); + } + } + + // Successfully encoded the entire string. + out.position(outIx); + } catch (IndexOutOfBoundsException e) { + // TODO(nathanmittler): Consider making the API throw IndexOutOfBoundsException instead. + + // If we failed in the outer ASCII loop, outIx will not have been updated. In this case, + // use inIx to determine the bad write index. + int badWriteIndex = out.position() + Math.max(inIx, outIx - out.position() + 1); + throw new ArrayIndexOutOfBoundsException( + "Failed writing " + in.charAt(inIx) + " at index " + badWriteIndex); + } + } + } + + /** + * {@link Processor} implementation that does not use any {@code sun.misc.Unsafe} methods. + */ + static final class SafeProcessor extends Processor { + @Override + int partialIsValidUtf8(int state, byte[] bytes, int index, int limit) { + if (state != COMPLETE) { + // The previous decoding operation was incomplete (or malformed). + // We look for a well-formed sequence consisting of bytes from + // the previous decoding operation (stored in state) together + // with bytes from the array slice. + // + // We expect such "straddler characters" to be rare. + + if (index >= limit) { // No bytes? No progress. + return state; + } + int byte1 = (byte) state; + // byte1 is never ASCII. + if (byte1 < (byte) 0xE0) { + // two-byte form + + // Simultaneously checks for illegal trailing-byte in + // leading position and overlong 2-byte form. + if (byte1 < (byte) 0xC2 + // byte2 trailing-byte test + || bytes[index++] > (byte) 0xBF) { + return MALFORMED; + } + } else if (byte1 < (byte) 0xF0) { + // three-byte form + + // Get byte2 from saved state or array + int byte2 = (byte) ~(state >> 8); + if (byte2 == 0) { + byte2 = bytes[index++]; + if (index >= limit) { + return incompleteStateFor(byte1, byte2); + } + } + if (byte2 > (byte) 0xBF + // overlong? 5 most significant bits must not all be zero + || (byte1 == (byte) 0xE0 && byte2 < (byte) 0xA0) + // illegal surrogate codepoint? + || (byte1 == (byte) 0xED && byte2 >= (byte) 0xA0) + // byte3 trailing-byte test + || bytes[index++] > (byte) 0xBF) { + return MALFORMED; + } + } else { + // four-byte form + + // Get byte2 and byte3 from saved state or array + int byte2 = (byte) ~(state >> 8); + int byte3 = 0; + if (byte2 == 0) { + byte2 = bytes[index++]; + if (index >= limit) { + return incompleteStateFor(byte1, byte2); + } + } else { + byte3 = (byte) (state >> 16); + } + if (byte3 == 0) { + byte3 = bytes[index++]; + if (index >= limit) { + return incompleteStateFor(byte1, byte2, byte3); + } + } + + // If we were called with state == MALFORMED, then byte1 is 0xFF, + // which never occurs in well-formed UTF-8, and so we will return + // MALFORMED again below. + + if (byte2 > (byte) 0xBF + // Check that 1 <= plane <= 16. Tricky optimized form of: + // if (byte1 > (byte) 0xF4 || + // byte1 == (byte) 0xF0 && byte2 < (byte) 0x90 || + // byte1 == (byte) 0xF4 && byte2 > (byte) 0x8F) + || (((byte1 << 28) + (byte2 - (byte) 0x90)) >> 30) != 0 + // byte3 trailing-byte test + || byte3 > (byte) 0xBF + // byte4 trailing-byte test + || bytes[index++] > (byte) 0xBF) { + return MALFORMED; + } + } + } + + return partialIsValidUtf8(bytes, index, limit); + } + + @Override + int partialIsValidUtf8Direct(int state, ByteBuffer buffer, int index, int limit) { + // For safe processing, we have to use the ByteBuffer API. + return partialIsValidUtf8Default(state, buffer, index, limit); + } + + @Override + int encodeUtf8(CharSequence in, byte[] out, int offset, int length) { + int utf16Length = in.length(); + int j = offset; + int i = 0; + int limit = offset + length; + // Designed to take advantage of + // https://wikis.oracle.com/display/HotSpotInternals/RangeCheckElimination + for (char c; i < utf16Length && i + j < limit && (c = in.charAt(i)) < 0x80; i++) { + out[j + i] = (byte) c; + } + if (i == utf16Length) { + return j + utf16Length; + } + j += i; + for (char c; i < utf16Length; i++) { + c = in.charAt(i); + if (c < 0x80 && j < limit) { + out[j++] = (byte) c; + } else if (c < 0x800 && j <= limit - 2) { // 11 bits, two UTF-8 bytes + out[j++] = (byte) ((0xF << 6) | (c >>> 6)); + out[j++] = (byte) (0x80 | (0x3F & c)); + } else if ((c < Character.MIN_SURROGATE || Character.MAX_SURROGATE < c) && j <= limit - 3) { + // Maximum single-char code point is 0xFFFF, 16 bits, three UTF-8 bytes + out[j++] = (byte) ((0xF << 5) | (c >>> 12)); + out[j++] = (byte) (0x80 | (0x3F & (c >>> 6))); + out[j++] = (byte) (0x80 | (0x3F & c)); + } else if (j <= limit - 4) { + // Minimum code point represented by a surrogate pair is 0x10000, 17 bits, + // four UTF-8 bytes + final char low; + if (i + 1 == in.length() + || !Character.isSurrogatePair(c, (low = in.charAt(++i)))) { + throw new UnpairedSurrogateException((i - 1), utf16Length); + } + int codePoint = Character.toCodePoint(c, low); + out[j++] = (byte) ((0xF << 4) | (codePoint >>> 18)); + out[j++] = (byte) (0x80 | (0x3F & (codePoint >>> 12))); + out[j++] = (byte) (0x80 | (0x3F & (codePoint >>> 6))); + out[j++] = (byte) (0x80 | (0x3F & codePoint)); + } else { + // If we are surrogates and we're not a surrogate pair, always throw an + // UnpairedSurrogateException instead of an ArrayOutOfBoundsException. + if ((Character.MIN_SURROGATE <= c && c <= Character.MAX_SURROGATE) + && (i + 1 == in.length() + || !Character.isSurrogatePair(c, in.charAt(i + 1)))) { + throw new UnpairedSurrogateException(i, utf16Length); + } + throw new ArrayIndexOutOfBoundsException("Failed writing " + c + " at index " + j); + } + } + return j; + } + + @Override + void encodeUtf8Direct(CharSequence in, ByteBuffer out) { + // For safe processing, we have to use the ByteBuffer API. + encodeUtf8Default(in, out); + } + + private static int partialIsValidUtf8(byte[] bytes, int index, int limit) { + // Optimize for 100% ASCII (Hotspot loves small simple top-level loops like this). + // This simple loop stops when we encounter a byte >= 0x80 (i.e. non-ASCII). + while (index < limit && bytes[index] >= 0) { + index++; + } + + return (index >= limit) ? COMPLETE : partialIsValidUtf8NonAscii(bytes, index, limit); + } + + private static int partialIsValidUtf8NonAscii(byte[] bytes, int index, int limit) { + for (;;) { + int byte1, byte2; + + // Optimize for interior runs of ASCII bytes. + do { + if (index >= limit) { + return COMPLETE; + } + } while ((byte1 = bytes[index++]) >= 0); + + if (byte1 < (byte) 0xE0) { + // two-byte form + + if (index >= limit) { + // Incomplete sequence + return byte1; + } + + // Simultaneously checks for illegal trailing-byte in + // leading position and overlong 2-byte form. + if (byte1 < (byte) 0xC2 + || bytes[index++] > (byte) 0xBF) { + return MALFORMED; + } + } else if (byte1 < (byte) 0xF0) { + // three-byte form + + if (index >= limit - 1) { // incomplete sequence + return incompleteStateFor(bytes, index, limit); + } + if ((byte2 = bytes[index++]) > (byte) 0xBF + // overlong? 5 most significant bits must not all be zero + || (byte1 == (byte) 0xE0 && byte2 < (byte) 0xA0) + // check for illegal surrogate codepoints + || (byte1 == (byte) 0xED && byte2 >= (byte) 0xA0) + // byte3 trailing-byte test + || bytes[index++] > (byte) 0xBF) { + return MALFORMED; + } + } else { + // four-byte form + + if (index >= limit - 2) { // incomplete sequence + return incompleteStateFor(bytes, index, limit); + } + if ((byte2 = bytes[index++]) > (byte) 0xBF + // Check that 1 <= plane <= 16. Tricky optimized form of: + // if (byte1 > (byte) 0xF4 || + // byte1 == (byte) 0xF0 && byte2 < (byte) 0x90 || + // byte1 == (byte) 0xF4 && byte2 > (byte) 0x8F) + || (((byte1 << 28) + (byte2 - (byte) 0x90)) >> 30) != 0 + // byte3 trailing-byte test + || bytes[index++] > (byte) 0xBF + // byte4 trailing-byte test + || bytes[index++] > (byte) 0xBF) { + return MALFORMED; + } + } + } + } + } + + /** + * {@link Processor} that uses {@code sun.misc.Unsafe} where possible to improve performance. + */ + static final class UnsafeProcessor extends Processor { + private static final sun.misc.Unsafe UNSAFE = getUnsafe(); + private static final long BUFFER_ADDRESS_OFFSET = + fieldOffset(field(Buffer.class, "address")); + private static final int ARRAY_BASE_OFFSET = byteArrayBaseOffset(); + + /** + * We only use Unsafe operations if we have access to direct {@link ByteBuffer}'s address + * and the array base offset is a multiple of 8 (needed by Unsafe.getLong()). + */ + private static final boolean AVAILABLE = + BUFFER_ADDRESS_OFFSET != -1 && ARRAY_BASE_OFFSET % 8 == 0; + + /** + * Indicates whether or not all required unsafe operations are supported on this platform. + */ + static boolean isAvailable() { + return AVAILABLE; + } + + @Override + int partialIsValidUtf8(int state, byte[] bytes, final int index, final int limit) { + if ((index | limit | bytes.length - limit) < 0) { + throw new ArrayIndexOutOfBoundsException( + String.format("Array length=%d, index=%d, limit=%d", bytes.length, index, limit)); + } + long offset = ARRAY_BASE_OFFSET + index; + final long offsetLimit = ARRAY_BASE_OFFSET + limit; + if (state != COMPLETE) { + // The previous decoding operation was incomplete (or malformed). + // We look for a well-formed sequence consisting of bytes from + // the previous decoding operation (stored in state) together + // with bytes from the array slice. + // + // We expect such "straddler characters" to be rare. + + if (offset >= offsetLimit) { // No bytes? No progress. + return state; + } + int byte1 = (byte) state; + // byte1 is never ASCII. + if (byte1 < (byte) 0xE0) { + // two-byte form + + // Simultaneously checks for illegal trailing-byte in + // leading position and overlong 2-byte form. + if (byte1 < (byte) 0xC2 + // byte2 trailing-byte test + || UNSAFE.getByte(bytes, offset++) > (byte) 0xBF) { + return MALFORMED; + } + } else if (byte1 < (byte) 0xF0) { + // three-byte form + + // Get byte2 from saved state or array + int byte2 = (byte) ~(state >> 8); + if (byte2 == 0) { + byte2 = UNSAFE.getByte(bytes, offset++); + if (offset >= offsetLimit) { + return incompleteStateFor(byte1, byte2); + } + } + if (byte2 > (byte) 0xBF + // overlong? 5 most significant bits must not all be zero + || (byte1 == (byte) 0xE0 && byte2 < (byte) 0xA0) + // illegal surrogate codepoint? + || (byte1 == (byte) 0xED && byte2 >= (byte) 0xA0) + // byte3 trailing-byte test + || UNSAFE.getByte(bytes, offset++) > (byte) 0xBF) { + return MALFORMED; + } + } else { + // four-byte form + + // Get byte2 and byte3 from saved state or array + int byte2 = (byte) ~(state >> 8); + int byte3 = 0; + if (byte2 == 0) { + byte2 = UNSAFE.getByte(bytes, offset++); + if (offset >= offsetLimit) { + return incompleteStateFor(byte1, byte2); + } + } else { + byte3 = (byte) (state >> 16); + } + if (byte3 == 0) { + byte3 = UNSAFE.getByte(bytes, offset++); + if (offset >= offsetLimit) { + return incompleteStateFor(byte1, byte2, byte3); + } + } + + // If we were called with state == MALFORMED, then byte1 is 0xFF, + // which never occurs in well-formed UTF-8, and so we will return + // MALFORMED again below. + + if (byte2 > (byte) 0xBF + // Check that 1 <= plane <= 16. Tricky optimized form of: + // if (byte1 > (byte) 0xF4 || + // byte1 == (byte) 0xF0 && byte2 < (byte) 0x90 || + // byte1 == (byte) 0xF4 && byte2 > (byte) 0x8F) + || (((byte1 << 28) + (byte2 - (byte) 0x90)) >> 30) != 0 + // byte3 trailing-byte test + || byte3 > (byte) 0xBF + // byte4 trailing-byte test + || UNSAFE.getByte(bytes, offset++) > (byte) 0xBF) { + return MALFORMED; + } + } + } + + return partialIsValidUtf8(bytes, offset, (int) (offsetLimit - offset)); + } + + @Override + int partialIsValidUtf8Direct( + final int state, ByteBuffer buffer, final int index, final int limit) { + if ((index | limit | buffer.limit() - limit) < 0) { + throw new ArrayIndexOutOfBoundsException( + String.format("buffer limit=%d, index=%d, limit=%d", buffer.limit(), index, limit)); + } + long address = addressOffset(buffer) + index; + final long addressLimit = address + (limit - index); + if (state != COMPLETE) { + // The previous decoding operation was incomplete (or malformed). + // We look for a well-formed sequence consisting of bytes from + // the previous decoding operation (stored in state) together + // with bytes from the array slice. + // + // We expect such "straddler characters" to be rare. + + if (address >= addressLimit) { // No bytes? No progress. + return state; + } + + final int byte1 = (byte) state; + // byte1 is never ASCII. + if (byte1 < (byte) 0xE0) { + // two-byte form + + // Simultaneously checks for illegal trailing-byte in + // leading position and overlong 2-byte form. + if (byte1 < (byte) 0xC2 + // byte2 trailing-byte test + || UNSAFE.getByte(address++) > (byte) 0xBF) { + return MALFORMED; + } + } else if (byte1 < (byte) 0xF0) { + // three-byte form + + // Get byte2 from saved state or array + int byte2 = (byte) ~(state >> 8); + if (byte2 == 0) { + byte2 = UNSAFE.getByte(address++); + if (address >= addressLimit) { + return incompleteStateFor(byte1, byte2); + } + } + if (byte2 > (byte) 0xBF + // overlong? 5 most significant bits must not all be zero + || (byte1 == (byte) 0xE0 && byte2 < (byte) 0xA0) + // illegal surrogate codepoint? + || (byte1 == (byte) 0xED && byte2 >= (byte) 0xA0) + // byte3 trailing-byte test + || UNSAFE.getByte(address++) > (byte) 0xBF) { + return MALFORMED; + } + } else { + // four-byte form + + // Get byte2 and byte3 from saved state or array + int byte2 = (byte) ~(state >> 8); + int byte3 = 0; + if (byte2 == 0) { + byte2 = UNSAFE.getByte(address++); + if (address >= addressLimit) { + return incompleteStateFor(byte1, byte2); + } + } else { + byte3 = (byte) (state >> 16); + } + if (byte3 == 0) { + byte3 = UNSAFE.getByte(address++); + if (address >= addressLimit) { + return incompleteStateFor(byte1, byte2, byte3); + } + } + + // If we were called with state == MALFORMED, then byte1 is 0xFF, + // which never occurs in well-formed UTF-8, and so we will return + // MALFORMED again below. + + if (byte2 > (byte) 0xBF + // Check that 1 <= plane <= 16. Tricky optimized form of: + // if (byte1 > (byte) 0xF4 || + // byte1 == (byte) 0xF0 && byte2 < (byte) 0x90 || + // byte1 == (byte) 0xF4 && byte2 > (byte) 0x8F) + || (((byte1 << 28) + (byte2 - (byte) 0x90)) >> 30) != 0 + // byte3 trailing-byte test + || byte3 > (byte) 0xBF + // byte4 trailing-byte test + || UNSAFE.getByte(address++) > (byte) 0xBF) { + return MALFORMED; + } + } + } + + return partialIsValidUtf8(address, (int) (addressLimit - address)); + } + + @Override + int encodeUtf8(final CharSequence in, final byte[] out, final int offset, final int length) { + long outIx = ARRAY_BASE_OFFSET + offset; + final long outLimit = outIx + length; + final int inLimit = in.length(); + if (inLimit > length || out.length - length < offset) { + // Not even enough room for an ASCII-encoded string. + throw new ArrayIndexOutOfBoundsException( + "Failed writing " + in.charAt(inLimit - 1) + " at index " + (offset + length)); + } + + // Designed to take advantage of + // https://wikis.oracle.com/display/HotSpotInternals/RangeCheckElimination + int inIx = 0; + for (char c; inIx < inLimit && (c = in.charAt(inIx)) < 0x80; ++inIx) { + UNSAFE.putByte(out, outIx++, (byte) c); + } + if (inIx == inLimit) { + // We're done, it was ASCII encoded. + return (int) (outIx - ARRAY_BASE_OFFSET); + } + + for (char c; inIx < inLimit; ++inIx) { + c = in.charAt(inIx); + if (c < 0x80 && outIx < outLimit) { + UNSAFE.putByte(out, outIx++, (byte) c); + } else if (c < 0x800 && outIx <= outLimit - 2L) { // 11 bits, two UTF-8 bytes + UNSAFE.putByte(out, outIx++, (byte) ((0xF << 6) | (c >>> 6))); + UNSAFE.putByte(out, outIx++, (byte) (0x80 | (0x3F & c))); + } else if ((c < MIN_SURROGATE || MAX_SURROGATE < c) && outIx <= outLimit - 3L) { + // Maximum single-char code point is 0xFFFF, 16 bits, three UTF-8 bytes + UNSAFE.putByte(out, outIx++, (byte) ((0xF << 5) | (c >>> 12))); + UNSAFE.putByte(out, outIx++, (byte) (0x80 | (0x3F & (c >>> 6)))); + UNSAFE.putByte(out, outIx++, (byte) (0x80 | (0x3F & c))); + } else if (outIx <= outLimit - 4L) { + // Minimum code point represented by a surrogate pair is 0x10000, 17 bits, four UTF-8 + // bytes + final char low; + if (inIx + 1 == inLimit || !isSurrogatePair(c, (low = in.charAt(++inIx)))) { + throw new UnpairedSurrogateException((inIx - 1), inLimit); + } + int codePoint = toCodePoint(c, low); + UNSAFE.putByte(out, outIx++, (byte) ((0xF << 4) | (codePoint >>> 18))); + UNSAFE.putByte(out, outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 12)))); + UNSAFE.putByte(out, outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 6)))); + UNSAFE.putByte(out, outIx++, (byte) (0x80 | (0x3F & codePoint))); + } else { + if ((MIN_SURROGATE <= c && c <= MAX_SURROGATE) + && (inIx + 1 == inLimit || !isSurrogatePair(c, in.charAt(inIx + 1)))) { + // We are surrogates and we're not a surrogate pair. + throw new UnpairedSurrogateException(inIx, inLimit); + } + // Not enough space in the output buffer. + throw new ArrayIndexOutOfBoundsException("Failed writing " + c + " at index " + outIx); + } + } + + // All bytes have been encoded. + return (int) (outIx - ARRAY_BASE_OFFSET); + } + + @Override + void encodeUtf8Direct(CharSequence in, ByteBuffer out) { + final long address = addressOffset(out); + long outIx = address + out.position(); + final long outLimit = address + out.limit(); + final int inLimit = in.length(); + if (inLimit > outLimit - outIx) { + // Not even enough room for an ASCII-encoded string. + throw new ArrayIndexOutOfBoundsException( + "Failed writing " + in.charAt(inLimit - 1) + " at index " + out.limit()); + } + + // Designed to take advantage of + // https://wikis.oracle.com/display/HotSpotInternals/RangeCheckElimination + int inIx = 0; + for (char c; inIx < inLimit && (c = in.charAt(inIx)) < 0x80; ++inIx) { + UNSAFE.putByte(outIx++, (byte) c); + } + if (inIx == inLimit) { + // We're done, it was ASCII encoded. + out.position((int) (outIx - address)); + return; + } + + for (char c; inIx < inLimit; ++inIx) { + c = in.charAt(inIx); + if (c < 0x80 && outIx < outLimit) { + UNSAFE.putByte(outIx++, (byte) c); + } else if (c < 0x800 && outIx <= outLimit - 2L) { // 11 bits, two UTF-8 bytes + UNSAFE.putByte(outIx++, (byte) ((0xF << 6) | (c >>> 6))); + UNSAFE.putByte(outIx++, (byte) (0x80 | (0x3F & c))); + } else if ((c < MIN_SURROGATE || MAX_SURROGATE < c) && outIx <= outLimit - 3L) { + // Maximum single-char code point is 0xFFFF, 16 bits, three UTF-8 bytes + UNSAFE.putByte(outIx++, (byte) ((0xF << 5) | (c >>> 12))); + UNSAFE.putByte(outIx++, (byte) (0x80 | (0x3F & (c >>> 6)))); + UNSAFE.putByte(outIx++, (byte) (0x80 | (0x3F & c))); + } else if (outIx <= outLimit - 4L) { + // Minimum code point represented by a surrogate pair is 0x10000, 17 bits, four UTF-8 + // bytes + final char low; + if (inIx + 1 == inLimit || !isSurrogatePair(c, (low = in.charAt(++inIx)))) { + throw new UnpairedSurrogateException((inIx - 1), inLimit); + } + int codePoint = toCodePoint(c, low); + UNSAFE.putByte(outIx++, (byte) ((0xF << 4) | (codePoint >>> 18))); + UNSAFE.putByte(outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 12)))); + UNSAFE.putByte(outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 6)))); + UNSAFE.putByte(outIx++, (byte) (0x80 | (0x3F & codePoint))); + } else { + if ((MIN_SURROGATE <= c && c <= MAX_SURROGATE) + && (inIx + 1 == inLimit || !isSurrogatePair(c, in.charAt(inIx + 1)))) { + // We are surrogates and we're not a surrogate pair. + throw new UnpairedSurrogateException(inIx, inLimit); + } + // Not enough space in the output buffer. + throw new ArrayIndexOutOfBoundsException("Failed writing " + c + " at index " + outIx); + } + } + + // All bytes have been encoded. + out.position((int) (outIx - address)); + } + + /** + * Counts (approximately) the number of consecutive ASCII characters starting from the given + * position, using the most efficient method available to the platform. + * + * @param bytes the array containing the character sequence + * @param offset the offset position of the index (same as index + arrayBaseOffset) + * @param maxChars the maximum number of characters to count + * @return the number of ASCII characters found. The stopping position will be at or + * before the first non-ASCII byte. + */ + private static int unsafeEstimateConsecutiveAscii( + byte[] bytes, long offset, final int maxChars) { + int remaining = maxChars; + if (remaining < UNSAFE_COUNT_ASCII_THRESHOLD) { + // Don't bother with small strings. + return 0; + } + + // Read bytes until 8-byte aligned so that we can read longs in the loop below. + // Byte arrays are already either 8 or 16-byte aligned, so we just need to make sure that + // the index (relative to the start of the array) is also 8-byte aligned. We do this by + // ANDing the index with 7 to determine the number of bytes that need to be read before + // we're 8-byte aligned. + final int unaligned = (int) offset & 7; + for (int j = unaligned; j > 0; j--) { + if (UNSAFE.getByte(bytes, offset++) < 0) { + return unaligned - j; + } + } + + // This simple loop stops when we encounter a byte >= 0x80 (i.e. non-ASCII). + // To speed things up further, we're reading longs instead of bytes so we use a mask to + // determine if any byte in the current long is non-ASCII. + remaining -= unaligned; + for (; remaining >= 8 && (UNSAFE.getLong(bytes, offset) & ASCII_MASK_LONG) == 0; + offset += 8, remaining -= 8) {} + return maxChars - remaining; + } + + /** + * Same as {@link Utf8#estimateConsecutiveAscii(ByteBuffer, int, int)} except that it uses the + * most efficient method available to the platform. + */ + private static int unsafeEstimateConsecutiveAscii(long address, final int maxChars) { + int remaining = maxChars; + if (remaining < UNSAFE_COUNT_ASCII_THRESHOLD) { + // Don't bother with small strings. + return 0; + } + + // Read bytes until 8-byte aligned so that we can read longs in the loop below. + // We do this by ANDing the address with 7 to determine the number of bytes that need to + // be read before we're 8-byte aligned. + final int unaligned = (int) address & 7; + for (int j = unaligned; j > 0; j--) { + if (UNSAFE.getByte(address++) < 0) { + return unaligned - j; + } + } + + // This simple loop stops when we encounter a byte >= 0x80 (i.e. non-ASCII). + // To speed things up further, we're reading longs instead of bytes so we use a mask to + // determine if any byte in the current long is non-ASCII. + remaining -= unaligned; + for (; remaining >= 8 && (UNSAFE.getLong(address) & ASCII_MASK_LONG) == 0; + address += 8, remaining -= 8) {} + return maxChars - remaining; + } + + private static int partialIsValidUtf8(final byte[] bytes, long offset, int remaining) { + // Skip past ASCII characters as quickly as possible. + final int skipped = unsafeEstimateConsecutiveAscii(bytes, offset, remaining); + remaining -= skipped; + offset += skipped; + + for (;;) { + // Optimize for interior runs of ASCII bytes. + // TODO(nathanmittler): Consider checking 8 bytes at a time after some threshold? + // Maybe after seeing a few in a row that are ASCII, go back to fast mode? + int byte1 = 0; + for (; remaining > 0 && (byte1 = UNSAFE.getByte(bytes, offset++)) >= 0; --remaining) { + } + if (remaining == 0) { + return COMPLETE; + } + remaining--; + + // If we're here byte1 is not ASCII. Only need to handle 2-4 byte forms. + if (byte1 < (byte) 0xE0) { + // Two-byte form (110xxxxx 10xxxxxx) + if (remaining == 0) { + // Incomplete sequence + return byte1; + } + remaining--; + + // Simultaneously checks for illegal trailing-byte in + // leading position and overlong 2-byte form. + if (byte1 < (byte) 0xC2 + || UNSAFE.getByte(bytes, offset++) > (byte) 0xBF) { + return MALFORMED; + } + } else if (byte1 < (byte) 0xF0) { + // Three-byte form (1110xxxx 10xxxxxx 10xxxxxx) + if (remaining < 2) { + // Incomplete sequence + return unsafeIncompleteStateFor(bytes, byte1, offset, remaining); + } + remaining -= 2; + + final int byte2; + if ((byte2 = UNSAFE.getByte(bytes, offset++)) > (byte) 0xBF + // overlong? 5 most significant bits must not all be zero + || (byte1 == (byte) 0xE0 && byte2 < (byte) 0xA0) + // check for illegal surrogate codepoints + || (byte1 == (byte) 0xED && byte2 >= (byte) 0xA0) + // byte3 trailing-byte test + || UNSAFE.getByte(bytes, offset++) > (byte) 0xBF) { + return MALFORMED; + } + } else { + // Four-byte form (1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx) + if (remaining < 3) { + // Incomplete sequence + return unsafeIncompleteStateFor(bytes, byte1, offset, remaining); + } + remaining -= 3; + + final int byte2; + if ((byte2 = UNSAFE.getByte(bytes, offset++)) > (byte) 0xBF + // Check that 1 <= plane <= 16. Tricky optimized form of: + // if (byte1 > (byte) 0xF4 || + // byte1 == (byte) 0xF0 && byte2 < (byte) 0x90 || + // byte1 == (byte) 0xF4 && byte2 > (byte) 0x8F) + || (((byte1 << 28) + (byte2 - (byte) 0x90)) >> 30) != 0 + // byte3 trailing-byte test + || UNSAFE.getByte(bytes, offset++) > (byte) 0xBF + // byte4 trailing-byte test + || UNSAFE.getByte(bytes, offset++) > (byte) 0xBF) { + return MALFORMED; + } + } + } + } + + private static int partialIsValidUtf8(long address, int remaining) { + // Skip past ASCII characters as quickly as possible. + final int skipped = unsafeEstimateConsecutiveAscii(address, remaining); + address += skipped; + remaining -= skipped; + + for (;;) { + // Optimize for interior runs of ASCII bytes. + // TODO(nathanmittler): Consider checking 8 bytes at a time after some threshold? + // Maybe after seeing a few in a row that are ASCII, go back to fast mode? + int byte1 = 0; + for (; remaining > 0 && (byte1 = UNSAFE.getByte(address++)) >= 0; --remaining) { + } + if (remaining == 0) { + return COMPLETE; + } + remaining--; + + if (byte1 < (byte) 0xE0) { + // Two-byte form + + if (remaining == 0) { + // Incomplete sequence + return byte1; + } + remaining--; + + // Simultaneously checks for illegal trailing-byte in + // leading position and overlong 2-byte form. + if (byte1 < (byte) 0xC2 || UNSAFE.getByte(address++) > (byte) 0xBF) { + return MALFORMED; + } + } else if (byte1 < (byte) 0xF0) { + // Three-byte form + + if (remaining < 2) { + // Incomplete sequence + return unsafeIncompleteStateFor(address, byte1, remaining); + } + remaining -= 2; + + final byte byte2 = UNSAFE.getByte(address++); + if (byte2 > (byte) 0xBF + // overlong? 5 most significant bits must not all be zero + || (byte1 == (byte) 0xE0 && byte2 < (byte) 0xA0) + // check for illegal surrogate codepoints + || (byte1 == (byte) 0xED && byte2 >= (byte) 0xA0) + // byte3 trailing-byte test + || UNSAFE.getByte(address++) > (byte) 0xBF) { + return MALFORMED; + } + } else { + // Four-byte form + + if (remaining < 3) { + // Incomplete sequence + return unsafeIncompleteStateFor(address, byte1, remaining); + } + remaining -= 3; + + final byte byte2 = UNSAFE.getByte(address++); + if (byte2 > (byte) 0xBF + // Check that 1 <= plane <= 16. Tricky optimized form of: + // if (byte1 > (byte) 0xF4 || + // byte1 == (byte) 0xF0 && byte2 < (byte) 0x90 || + // byte1 == (byte) 0xF4 && byte2 > (byte) 0x8F) + || (((byte1 << 28) + (byte2 - (byte) 0x90)) >> 30) != 0 + // byte3 trailing-byte test + || UNSAFE.getByte(address++) > (byte) 0xBF + // byte4 trailing-byte test + || UNSAFE.getByte(address++) > (byte) 0xBF) { + return MALFORMED; + } + } + } + } + + private static int unsafeIncompleteStateFor(byte[] bytes, int byte1, long offset, + int remaining) { + switch (remaining) { + case 0: { + return incompleteStateFor(byte1); + } + case 1: { + return incompleteStateFor(byte1, UNSAFE.getByte(bytes, offset)); + } + case 2: { + return incompleteStateFor(byte1, UNSAFE.getByte(bytes, offset), + UNSAFE.getByte(bytes, offset + 1)); + } + default: { + throw new AssertionError(); + } + } + } + + private static int unsafeIncompleteStateFor(long address, final int byte1, int remaining) { + switch (remaining) { + case 0: { + return incompleteStateFor(byte1); + } + case 1: { + return incompleteStateFor(byte1, UNSAFE.getByte(address)); + } + case 2: { + return incompleteStateFor(byte1, UNSAFE.getByte(address), UNSAFE.getByte(address + 1)); + } + default: { + throw new AssertionError(); + } + } + } + + /** + * Gets the field with the given name within the class, or {@code null} if not found. If + * found, the field is made accessible. + */ + private static Field field(Class<?> clazz, String fieldName) { + Field field; + try { + field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + } catch (Throwable t) { + // Failed to access the fields. + field = null; + } + logger.log(Level.FINEST, "{0}.{1}: {2}", + new Object[] {clazz.getName(), fieldName, (field != null ? "available" : "unavailable")}); + return field; + } + + /** + * Returns the offset of the provided field, or {@code -1} if {@code sun.misc.Unsafe} is not + * available. + */ + private static long fieldOffset(Field field) { + return field == null || UNSAFE == null ? -1 : UNSAFE.objectFieldOffset(field); + } + + /** + * Get the base offset for byte arrays, or {@code -1} if {@code sun.misc.Unsafe} is not + * available. + */ + private static <T> int byteArrayBaseOffset() { + return UNSAFE == null ? -1 : UNSAFE.arrayBaseOffset(byte[].class); + } + + /** + * Gets the offset of the {@code address} field of the given direct {@link ByteBuffer}. + */ + private static long addressOffset(ByteBuffer buffer) { + return UNSAFE.getLong(buffer, BUFFER_ADDRESS_OFFSET); + } + + /** + * Gets the {@code sun.misc.Unsafe} instance, or {@code null} if not available on this + * platform. + */ + private static sun.misc.Unsafe getUnsafe() { + sun.misc.Unsafe unsafe = null; + try { + unsafe = AccessController.doPrivileged(new PrivilegedExceptionAction<sun.misc.Unsafe>() { + @Override + public sun.misc.Unsafe run() throws Exception { + Class<sun.misc.Unsafe> k = sun.misc.Unsafe.class; + + // Check that this platform supports all of the required unsafe methods. + checkRequiredMethods(k); + + for (Field f : k.getDeclaredFields()) { + f.setAccessible(true); + Object x = f.get(null); + if (k.isInstance(x)) { + return k.cast(x); + } + } + // The sun.misc.Unsafe field does not exist. + return null; + } + }); + } catch (Throwable e) { + // Catching Throwable here due to the fact that Google AppEngine raises NoClassDefFoundError + // for Unsafe. + } + + logger.log(Level.FINEST, "sun.misc.Unsafe: {}", + unsafe != null ? "available" : "unavailable"); + return unsafe; + } + + /** + * Verifies that all required methods of {@code sun.misc.Unsafe} are available on this platform. + */ + private static void checkRequiredMethods(Class<sun.misc.Unsafe> clazz) + throws NoSuchMethodException, SecurityException { + // Needed for Unsafe byte[] access + clazz.getMethod("arrayBaseOffset", Class.class); + clazz.getMethod("getByte", Object.class, long.class); + clazz.getMethod("putByte", Object.class, long.class, byte.class); + clazz.getMethod("getLong", Object.class, long.class); + + // Needed for Unsafe Direct ByteBuffer access + clazz.getMethod("objectFieldOffset", Field.class); + clazz.getMethod("getByte", long.class); + clazz.getMethod("getLong", Object.class, long.class); + clazz.getMethod("putByte", long.class, byte.class); + clazz.getMethod("getLong", long.class); + } + } + + private Utf8() {} +} diff --git a/java/src/main/java/com/google/protobuf/WireFormat.java b/java/core/src/main/java/com/google/protobuf/WireFormat.java index 8dbe1ae3..6a58b081 100644 --- a/java/src/main/java/com/google/protobuf/WireFormat.java +++ b/java/core/src/main/java/com/google/protobuf/WireFormat.java @@ -116,16 +116,24 @@ public final class WireFormat { FIXED32 (JavaType.INT , WIRETYPE_FIXED32 ), BOOL (JavaType.BOOLEAN , WIRETYPE_VARINT ), STRING (JavaType.STRING , WIRETYPE_LENGTH_DELIMITED) { - public boolean isPackable() { return false; } + @Override + public boolean isPackable() { + return false; } }, GROUP (JavaType.MESSAGE , WIRETYPE_START_GROUP ) { - public boolean isPackable() { return false; } + @Override + public boolean isPackable() { + return false; } }, MESSAGE (JavaType.MESSAGE , WIRETYPE_LENGTH_DELIMITED) { - public boolean isPackable() { return false; } + @Override + public boolean isPackable() { + return false; } }, BYTES (JavaType.BYTE_STRING, WIRETYPE_LENGTH_DELIMITED) { - public boolean isPackable() { return false; } + @Override + public boolean isPackable() { + return false; } }, UINT32 (JavaType.INT , WIRETYPE_VARINT ), ENUM (JavaType.ENUM , WIRETYPE_VARINT ), @@ -170,18 +178,21 @@ public final class WireFormat { enum Utf8Validation { /** Eagerly parses to String; silently accepts invalid UTF8 bytes. */ LOOSE { + @Override Object readString(CodedInputStream input) throws IOException { return input.readString(); } }, /** Eagerly parses to String; throws an IOException on invalid bytes. */ STRICT { + @Override Object readString(CodedInputStream input) throws IOException { return input.readStringRequireUtf8(); } }, /** Keep data as ByteString; validation/conversion to String is lazy. */ LAZY { + @Override Object readString(CodedInputStream input) throws IOException { return input.readBytes(); } diff --git a/java/src/test/java/com/google/protobuf/AbstractMessageTest.java b/java/core/src/test/java/com/google/protobuf/AbstractMessageTest.java index d964ef59..7dc9fc15 100644 --- a/java/src/test/java/com/google/protobuf/AbstractMessageTest.java +++ b/java/core/src/test/java/com/google/protobuf/AbstractMessageTest.java @@ -40,7 +40,6 @@ import protobuf_unittest.UnittestProto.TestPackedTypes; import protobuf_unittest.UnittestProto.TestRequired; import protobuf_unittest.UnittestProto.TestRequiredForeign; import protobuf_unittest.UnittestProto.TestUnpackedTypes; - import junit.framework.TestCase; import java.util.Map; @@ -65,35 +64,44 @@ public class AbstractMessageTest extends TestCase { this.wrappedMessage = wrappedMessage; } + @Override public Descriptors.Descriptor getDescriptorForType() { return wrappedMessage.getDescriptorForType(); } + @Override public AbstractMessageWrapper getDefaultInstanceForType() { return new AbstractMessageWrapper( wrappedMessage.getDefaultInstanceForType()); } + @Override public Map<Descriptors.FieldDescriptor, Object> getAllFields() { return wrappedMessage.getAllFields(); } + @Override public boolean hasField(Descriptors.FieldDescriptor field) { return wrappedMessage.hasField(field); } + @Override public Object getField(Descriptors.FieldDescriptor field) { return wrappedMessage.getField(field); } + @Override public int getRepeatedFieldCount(Descriptors.FieldDescriptor field) { return wrappedMessage.getRepeatedFieldCount(field); } - public Object getRepeatedField( - Descriptors.FieldDescriptor field, int index) { + @Override + public Object getRepeatedField(Descriptors.FieldDescriptor field, int index) { return wrappedMessage.getRepeatedField(field, index); } + @Override public UnknownFieldSet getUnknownFields() { return wrappedMessage.getUnknownFields(); } + @Override public Builder newBuilderForType() { return new Builder(wrappedMessage.newBuilderForType()); } + @Override public Builder toBuilder() { return new Builder(wrappedMessage.toBuilder()); } @@ -105,65 +113,80 @@ public class AbstractMessageTest extends TestCase { this.wrappedBuilder = wrappedBuilder; } + @Override public AbstractMessageWrapper build() { return new AbstractMessageWrapper(wrappedBuilder.build()); } + @Override public AbstractMessageWrapper buildPartial() { return new AbstractMessageWrapper(wrappedBuilder.buildPartial()); } + @Override public Builder clone() { return new Builder(wrappedBuilder.clone()); } + @Override public boolean isInitialized() { return clone().buildPartial().isInitialized(); } + @Override public Descriptors.Descriptor getDescriptorForType() { return wrappedBuilder.getDescriptorForType(); } + @Override public AbstractMessageWrapper getDefaultInstanceForType() { return new AbstractMessageWrapper( wrappedBuilder.getDefaultInstanceForType()); } + @Override public Map<Descriptors.FieldDescriptor, Object> getAllFields() { return wrappedBuilder.getAllFields(); } + @Override public Builder newBuilderForField(Descriptors.FieldDescriptor field) { return new Builder(wrappedBuilder.newBuilderForField(field)); } + @Override public boolean hasField(Descriptors.FieldDescriptor field) { return wrappedBuilder.hasField(field); } + @Override public Object getField(Descriptors.FieldDescriptor field) { return wrappedBuilder.getField(field); } + @Override public Builder setField(Descriptors.FieldDescriptor field, Object value) { wrappedBuilder.setField(field, value); return this; } + @Override public Builder clearField(Descriptors.FieldDescriptor field) { wrappedBuilder.clearField(field); return this; } + @Override public int getRepeatedFieldCount(Descriptors.FieldDescriptor field) { return wrappedBuilder.getRepeatedFieldCount(field); } - public Object getRepeatedField( - Descriptors.FieldDescriptor field, int index) { + @Override + public Object getRepeatedField(Descriptors.FieldDescriptor field, int index) { return wrappedBuilder.getRepeatedField(field, index); } - public Builder setRepeatedField(Descriptors.FieldDescriptor field, - int index, Object value) { + @Override + public Builder setRepeatedField(Descriptors.FieldDescriptor field, int index, Object value) { wrappedBuilder.setRepeatedField(field, index, value); return this; } - public Builder addRepeatedField( - Descriptors.FieldDescriptor field, Object value) { + @Override + public Builder addRepeatedField(Descriptors.FieldDescriptor field, Object value) { wrappedBuilder.addRepeatedField(field, value); return this; } + @Override public UnknownFieldSet getUnknownFields() { return wrappedBuilder.getUnknownFields(); } + @Override public Builder setUnknownFields(UnknownFieldSet unknownFields) { wrappedBuilder.setUnknownFields(unknownFields); return this; @@ -173,6 +196,7 @@ public class AbstractMessageTest extends TestCase { return wrappedBuilder.getFieldBuilder(field); } } + @Override public Parser<? extends Message> getParserForType() { return wrappedMessage.getParserForType(); } diff --git a/java/src/test/java/com/google/protobuf/AnyTest.java b/java/core/src/test/java/com/google/protobuf/AnyTest.java index e169f69d..cf91ed91 100644 --- a/java/src/test/java/com/google/protobuf/AnyTest.java +++ b/java/core/src/test/java/com/google/protobuf/AnyTest.java @@ -75,6 +75,51 @@ public class AnyTest extends TestCase { } } + public void testCustomTypeUrls() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TestUtil.setAllFields(builder); + TestAllTypes message = builder.build(); + + TestAny container = TestAny.newBuilder() + .setValue(Any.pack(message, "xxx.com")).build(); + + assertEquals( + "xxx.com/" + TestAllTypes.getDescriptor().getFullName(), + container.getValue().getTypeUrl()); + + assertTrue(container.getValue().is(TestAllTypes.class)); + assertFalse(container.getValue().is(TestAny.class)); + + TestAllTypes result = container.getValue().unpack(TestAllTypes.class); + TestUtil.assertAllFieldsSet(result); + + container = TestAny.newBuilder() + .setValue(Any.pack(message, "yyy.com/")).build(); + + assertEquals( + "yyy.com/" + TestAllTypes.getDescriptor().getFullName(), + container.getValue().getTypeUrl()); + + assertTrue(container.getValue().is(TestAllTypes.class)); + assertFalse(container.getValue().is(TestAny.class)); + + result = container.getValue().unpack(TestAllTypes.class); + TestUtil.assertAllFieldsSet(result); + + container = TestAny.newBuilder() + .setValue(Any.pack(message, "")).build(); + + assertEquals( + "/" + TestAllTypes.getDescriptor().getFullName(), + container.getValue().getTypeUrl()); + + assertTrue(container.getValue().is(TestAllTypes.class)); + assertFalse(container.getValue().is(TestAny.class)); + + result = container.getValue().unpack(TestAllTypes.class); + TestUtil.assertAllFieldsSet(result); + } + public void testCachedUnpackResult() throws Exception { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); TestUtil.setAllFields(builder); diff --git a/java/src/test/java/com/google/protobuf/BooleanArrayListTest.java b/java/core/src/test/java/com/google/protobuf/BooleanArrayListTest.java index df89c263..24b96c60 100644 --- a/java/src/test/java/com/google/protobuf/BooleanArrayListTest.java +++ b/java/core/src/test/java/com/google/protobuf/BooleanArrayListTest.java @@ -73,20 +73,6 @@ public class BooleanArrayListTest extends TestCase { assertImmutable(list); } - public void testCopyConstructor() { - BooleanArrayList copy = new BooleanArrayList(TERTIARY_LIST); - assertEquals(TERTIARY_LIST, copy); - - copy = new BooleanArrayList(BooleanArrayList.emptyList()); - assertEquals(BooleanArrayList.emptyList(), copy); - - copy = new BooleanArrayList(asList(false, false, true)); - assertEquals(asList(false, false, true), copy); - - copy = new BooleanArrayList(Collections.<Boolean>emptyList()); - assertEquals(BooleanArrayList.emptyList(), copy); - } - public void testModificationWithIteration() { list.addAll(asList(true, false, false, true)); Iterator<Boolean> iterator = list.iterator(); @@ -310,10 +296,6 @@ public class BooleanArrayListTest extends TestCase { } private void assertImmutable(BooleanArrayList list) { - if (list.contains(1)) { - throw new RuntimeException("Cannot test the immutability of lists that contain 1."); - } - try { list.add(false); fail(); @@ -413,7 +395,7 @@ public class BooleanArrayListTest extends TestCase { } try { - list.removeAll(Collections.singleton(1)); + list.removeAll(Collections.singleton(Boolean.TRUE)); fail(); } catch (UnsupportedOperationException e) { // expected @@ -434,7 +416,7 @@ public class BooleanArrayListTest extends TestCase { } try { - list.retainAll(Collections.singleton(1)); + list.retainAll(Collections.singleton(Boolean.TRUE)); fail(); } catch (UnsupportedOperationException e) { // expected diff --git a/java/src/test/java/com/google/protobuf/BoundedByteStringTest.java b/java/core/src/test/java/com/google/protobuf/BoundedByteStringTest.java index 447e6ef3..db10ee74 100644 --- a/java/src/test/java/com/google/protobuf/BoundedByteStringTest.java +++ b/java/core/src/test/java/com/google/protobuf/BoundedByteStringTest.java @@ -37,7 +37,6 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.UnsupportedEncodingException; - /** * This class tests {@link BoundedByteString}, which extends {@link LiteralByteString}, * by inheriting the tests from {@link LiteralByteStringTest}. The only method which @@ -62,7 +61,7 @@ public class BoundedByteStringTest extends LiteralByteStringTest { @Override public void testToString() throws UnsupportedEncodingException { String testString = "I love unicode \u1234\u5678 characters"; - LiteralByteString unicode = new LiteralByteString(testString.getBytes(Internal.UTF_8)); + ByteString unicode = ByteString.wrap(testString.getBytes(Internal.UTF_8)); ByteString chopped = unicode.substring(2, unicode.size() - 6); assertEquals(classUnderTest + ".substring() must have the expected type", classUnderTest, getActualClassName(chopped)); @@ -73,9 +72,9 @@ public class BoundedByteStringTest extends LiteralByteStringTest { } @Override - public void testCharsetToString() throws UnsupportedEncodingException { + public void testCharsetToString() { String testString = "I love unicode \u1234\u5678 characters"; - LiteralByteString unicode = new LiteralByteString(testString.getBytes(Internal.UTF_8)); + ByteString unicode = ByteString.wrap(testString.getBytes(Internal.UTF_8)); ByteString chopped = unicode.substring(2, unicode.size() - 6); assertEquals(classUnderTest + ".substring() must have the expected type", classUnderTest, getActualClassName(chopped)); diff --git a/java/core/src/test/java/com/google/protobuf/ByteBufferWriterTest.java b/java/core/src/test/java/com/google/protobuf/ByteBufferWriterTest.java new file mode 100644 index 00000000..cbe742e5 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/ByteBufferWriterTest.java @@ -0,0 +1,81 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package com.google.protobuf; + +import junit.framework.TestCase; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Random; + +/** + * Tests for {@link ByteBufferWriter}. + */ +public class ByteBufferWriterTest extends TestCase { + + public void testHeapBuffer() throws IOException { + // Test a small and large buffer. + testWrite(ByteBuffer.allocate(100)); + testWrite(ByteBuffer.allocate(1024 * 100)); + } + + public void testDirectBuffer() throws IOException { + // Test a small and large buffer. + testWrite(ByteBuffer.allocateDirect(100)); + testWrite(ByteBuffer.allocateDirect(1024 * 100)); + } + + private void testWrite(ByteBuffer buffer) throws IOException { + fillRandom(buffer); + ByteArrayOutputStream os = new ByteArrayOutputStream(buffer.remaining()); + ByteBufferWriter.write(buffer, os); + assertEquals(0, buffer.position()); + assertTrue(Arrays.equals(toArray(buffer), os.toByteArray())); + } + + private void fillRandom(ByteBuffer buf) { + byte[] bytes = new byte[buf.remaining()]; + new Random().nextBytes(bytes); + buf.put(bytes); + buf.flip(); + return; + } + + private byte[] toArray(ByteBuffer buf) { + int originalPosition = buf.position(); + byte[] bytes = new byte[buf.remaining()]; + buf.get(bytes); + buf.position(originalPosition); + return bytes; + } +} diff --git a/java/src/test/java/com/google/protobuf/ByteStringTest.java b/java/core/src/test/java/com/google/protobuf/ByteStringTest.java index 8af5dcde..ad9f266e 100644 --- a/java/src/test/java/com/google/protobuf/ByteStringTest.java +++ b/java/core/src/test/java/com/google/protobuf/ByteStringTest.java @@ -39,7 +39,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.io.UnsupportedEncodingException; +import java.lang.reflect.Field; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.ArrayList; @@ -129,7 +129,7 @@ public class ByteStringTest extends TestCase { isArrayRange(byteString.toByteArray(), bytes, 500, bytes.length - 500)); } - public void testCopyFrom_StringEncoding() throws UnsupportedEncodingException { + public void testCopyFrom_StringEncoding() { String testString = "I love unicode \u1234\u5678 characters"; ByteString byteString = ByteString.copyFrom(testString, UTF_16); byte[] testBytes = testString.getBytes(UTF_16); @@ -137,7 +137,7 @@ public class ByteStringTest extends TestCase { isArrayRange(byteString.toByteArray(), testBytes, 0, testBytes.length)); } - public void testCopyFrom_Utf8() throws UnsupportedEncodingException { + public void testCopyFrom_Utf8() { String testString = "I love unicode \u1234\u5678 characters"; ByteString byteString = ByteString.copyFromUtf8(testString); byte[] testBytes = testString.getBytes(Internal.UTF_8); @@ -154,6 +154,7 @@ public class ByteStringTest extends TestCase { isArrayRange(byteString.toByteArray(), testBytes, 0, testBytes.length)); // Call copyFrom on an iteration that's not a collection ByteString byteStringAlt = ByteString.copyFrom(new Iterable<ByteString>() { + @Override public Iterator<ByteString> iterator() { return pieces.iterator(); } @@ -399,7 +400,7 @@ public class ByteStringTest extends TestCase { } } - public void testToStringUtf8() throws UnsupportedEncodingException { + public void testToStringUtf8() { String testString = "I love unicode \u1234\u5678 characters"; byte[] testBytes = testString.getBytes(Internal.UTF_8); ByteString byteString = ByteString.copyFrom(testBytes); @@ -419,7 +420,7 @@ public class ByteStringTest extends TestCase { // Test newOutput() using a variety of buffer sizes and a variety of (fixed) // write sizes - public void testNewOutput_ArrayWrite() throws IOException { + public void testNewOutput_ArrayWrite() { byte[] bytes = getTestBytes(); int length = bytes.length; int[] bufferSizes = {128, 256, length / 2, length - 1, length, length + 1, @@ -442,7 +443,7 @@ public class ByteStringTest extends TestCase { // Test newOutput() using a variety of buffer sizes, but writing all the // characters using write(byte); - public void testNewOutput_WriteChar() throws IOException { + public void testNewOutput_WriteChar() { byte[] bytes = getTestBytes(); int length = bytes.length; int[] bufferSizes = {0, 1, 128, 256, length / 2, @@ -461,7 +462,7 @@ public class ByteStringTest extends TestCase { // Test newOutput() in which we write the bytes using a variety of methods // and sizes, and in which we repeatedly call toByteString() in the middle. - public void testNewOutput_Mixed() throws IOException { + public void testNewOutput_Mixed() { Random rng = new Random(1); byte[] bytes = getTestBytes(); int length = bytes.length; @@ -494,7 +495,7 @@ public class ByteStringTest extends TestCase { } } - public void testNewOutputEmpty() throws IOException { + public void testNewOutputEmpty() { // Make sure newOutput() correctly builds empty byte strings ByteString byteString = ByteString.newOutput().toByteString(); assertEquals(ByteString.EMPTY, byteString); @@ -655,11 +656,11 @@ public class ByteStringTest extends TestCase { // trees of empty leaves, to make a string that will fail this test. for (int i = 1; i < duo.size(); ++i) { assertTrue("Substrings of size() < 2 must not be RopeByteStrings", - duo.substring(i - 1, i) instanceof LiteralByteString); + duo.substring(i - 1, i) instanceof ByteString.LeafByteString); } for (int i = 1; i < quintet.size(); ++i) { assertTrue("Substrings of size() < 2 must not be RopeByteStrings", - quintet.substring(i - 1, i) instanceof LiteralByteString); + quintet.substring(i - 1, i) instanceof ByteString.LeafByteString); } } @@ -724,7 +725,7 @@ public class ByteStringTest extends TestCase { } data1[1] = (byte) 11; // Test LiteralByteString.writeTo(OutputStream,int,int) - LiteralByteString left = new LiteralByteString(data1); + ByteString left = ByteString.wrap(data1); byte[] result = substringUsingWriteTo(left, 1, 1); assertEquals(1, result.length); assertEquals((byte) 11, result[0]); @@ -733,7 +734,7 @@ public class ByteStringTest extends TestCase { for (int i = 0; i < data1.length; i++) { data2[i] = (byte) 2; } - LiteralByteString right = new LiteralByteString(data2); + ByteString right = ByteString.wrap(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 @@ -757,4 +758,17 @@ public class ByteStringTest extends TestCase { assertEquals((byte) 2, result[dataSize - dataSize / 2]); assertEquals((byte) 2, result[dataSize - 1]); } + + /** + * Tests ByteString uses Arrays based byte copier when running under Hotstop VM. + */ + public void testByteArrayCopier() throws Exception { + Field field = ByteString.class.getDeclaredField("byteArrayCopier"); + field.setAccessible(true); + Object byteArrayCopier = field.get(null); + assertNotNull(byteArrayCopier); + assertTrue( + byteArrayCopier.toString(), + byteArrayCopier.getClass().getSimpleName().endsWith("ArraysByteArrayCopier")); + } } diff --git a/java/src/test/java/com/google/protobuf/CheckUtf8Test.java b/java/core/src/test/java/com/google/protobuf/CheckUtf8Test.java index 3d6381c9..cc65d19a 100644 --- a/java/src/test/java/com/google/protobuf/CheckUtf8Test.java +++ b/java/core/src/test/java/com/google/protobuf/CheckUtf8Test.java @@ -34,6 +34,7 @@ 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; /** diff --git a/java/src/test/java/com/google/protobuf/CodedInputStreamTest.java b/java/core/src/test/java/com/google/protobuf/CodedInputStreamTest.java index 18d8142c..ca940ced 100644 --- a/java/src/test/java/com/google/protobuf/CodedInputStreamTest.java +++ b/java/core/src/test/java/com/google/protobuf/CodedInputStreamTest.java @@ -81,10 +81,12 @@ public class CodedInputStreamTest extends TestCase { this.blockSize = blockSize; } + @Override public int read(byte[] b) throws IOException { return super.read(b, 0, Math.min(b.length, blockSize)); } + @Override public int read(byte[] b, int off, int len) throws IOException { return super.read(b, off, Math.min(len, blockSize)); } @@ -547,6 +549,56 @@ public class CodedInputStreamTest extends TestCase { } } + public void testReadString() throws Exception { + String lorem = "Lorem ipsum dolor sit amet "; + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < 4096; i += lorem.length()) { + builder.append(lorem); + } + lorem = builder.toString().substring(0, 4096); + byte[] bytes = lorem.getBytes("UTF-8"); + ByteString.Output rawOutput = ByteString.newOutput(); + CodedOutputStream output = CodedOutputStream.newInstance(rawOutput, bytes.length); + + int tag = WireFormat.makeTag(1, WireFormat.WIRETYPE_LENGTH_DELIMITED); + output.writeRawVarint32(tag); + output.writeRawVarint32(bytes.length); + output.writeRawBytes(bytes); + output.flush(); + + CodedInputStream input = + CodedInputStream.newInstance( + new ByteArrayInputStream(rawOutput.toByteString().toByteArray())); + assertEquals(tag, input.readTag()); + String text = input.readString(); + assertEquals(lorem, text); + } + + public void testReadStringRequireUtf8() throws Exception { + String lorem = "Lorem ipsum dolor sit amet "; + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < 4096; i += lorem.length()) { + builder.append(lorem); + } + lorem = builder.toString().substring(0, 4096); + byte[] bytes = lorem.getBytes("UTF-8"); + ByteString.Output rawOutput = ByteString.newOutput(); + CodedOutputStream output = CodedOutputStream.newInstance(rawOutput, bytes.length); + + int tag = WireFormat.makeTag(1, WireFormat.WIRETYPE_LENGTH_DELIMITED); + output.writeRawVarint32(tag); + output.writeRawVarint32(bytes.length); + output.writeRawBytes(bytes); + output.flush(); + + CodedInputStream input = + CodedInputStream.newInstance( + new ByteArrayInputStream(rawOutput.toByteString().toByteArray())); + assertEquals(tag, input.readTag()); + String text = input.readStringRequireUtf8(); + assertEquals(lorem, text); + } + /** * Tests that if we readString invalid UTF-8 bytes, no exception * is thrown. Instead, the invalid bytes are replaced with the Unicode diff --git a/java/core/src/test/java/com/google/protobuf/CodedOutputStreamTest.java b/java/core/src/test/java/com/google/protobuf/CodedOutputStreamTest.java new file mode 100644 index 00000000..33aa4357 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/CodedOutputStreamTest.java @@ -0,0 +1,776 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// 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.CodedOutputStream.OutOfSpaceException; +import protobuf_unittest.UnittestProto.SparseEnumMessage; +import protobuf_unittest.UnittestProto.TestAllTypes; +import protobuf_unittest.UnittestProto.TestPackedTypes; +import protobuf_unittest.UnittestProto.TestSparseEnum; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Unit test for {@link CodedOutputStream}. + * + * @author kenton@google.com Kenton Varda + */ +public class CodedOutputStreamTest extends TestCase { + private interface Coder { + CodedOutputStream stream(); + + byte[] toByteArray(); + + OutputType getOutputType(); + } + + private static final class OutputStreamCoder implements Coder { + private final CodedOutputStream stream; + private final ByteArrayOutputStream output; + + OutputStreamCoder(int size) { + output = new ByteArrayOutputStream(); + stream = CodedOutputStream.newInstance(output, size); + } + + @Override + public CodedOutputStream stream() { + return stream; + } + + @Override + public byte[] toByteArray() { + return output.toByteArray(); + } + + @Override + public OutputType getOutputType() { + return OutputType.STREAM; + } + } + + private static final class ArrayCoder implements Coder { + private final CodedOutputStream stream; + private final byte[] bytes; + + ArrayCoder(int size) { + bytes = new byte[size]; + stream = CodedOutputStream.newInstance(bytes); + } + + @Override + public CodedOutputStream stream() { + return stream; + } + + @Override + public byte[] toByteArray() { + return Arrays.copyOf(bytes, stream.getTotalBytesWritten()); + } + + @Override + public OutputType getOutputType() { + return OutputType.ARRAY; + } + } + + private static final class NioHeapCoder implements Coder { + private final CodedOutputStream stream; + private final ByteBuffer buffer; + private final int initialPosition; + + NioHeapCoder(int size) { + this(size, 0); + } + + NioHeapCoder(int size, int initialPosition) { + this.initialPosition = initialPosition; + buffer = ByteBuffer.allocate(size); + buffer.position(initialPosition); + stream = CodedOutputStream.newInstance(buffer); + } + + @Override + public CodedOutputStream stream() { + return stream; + } + + @Override + public byte[] toByteArray() { + ByteBuffer dup = buffer.duplicate(); + dup.position(initialPosition); + dup.limit(buffer.position()); + + byte[] bytes = new byte[dup.remaining()]; + dup.get(bytes); + return bytes; + } + + @Override + public OutputType getOutputType() { + return OutputType.NIO_HEAP; + } + } + + private static final class NioDirectCoder implements Coder { + private final int initialPosition; + private final CodedOutputStream stream; + private final ByteBuffer buffer; + + NioDirectCoder(int size) { + this(size, 0); + } + + NioDirectCoder(int size, int initialPosition) { + this.initialPosition = initialPosition; + buffer = ByteBuffer.allocateDirect(size); + buffer.position(initialPosition); + stream = CodedOutputStream.newInstance(buffer); + } + + @Override + public CodedOutputStream stream() { + return stream; + } + + @Override + public byte[] toByteArray() { + ByteBuffer dup = buffer.duplicate(); + dup.position(initialPosition); + dup.limit(buffer.position()); + + byte[] bytes = new byte[dup.remaining()]; + dup.get(bytes); + return bytes; + } + + @Override + public OutputType getOutputType() { + return OutputType.NIO_DIRECT; + } + } + + private enum OutputType { + ARRAY() { + @Override + Coder newCoder(int size) { + return new ArrayCoder(size); + } + }, + NIO_HEAP() { + @Override + Coder newCoder(int size) { + return new NioHeapCoder(size); + } + }, + NIO_DIRECT() { + @Override + Coder newCoder(int size) { + return new NioDirectCoder(size); + } + }, + STREAM() { + @Override + Coder newCoder(int size) { + return new OutputStreamCoder(size); + } + }; + + abstract Coder newCoder(int size); + } + + /** Checks that invariants are maintained for varint round trip input and output. */ + public void testVarintRoundTrips() throws Exception { + for (OutputType outputType : OutputType.values()) { + assertVarintRoundTrip(outputType, 0L); + for (int bits = 0; bits < 64; bits++) { + long value = 1L << bits; + assertVarintRoundTrip(outputType, value); + assertVarintRoundTrip(outputType, value + 1); + assertVarintRoundTrip(outputType, value - 1); + assertVarintRoundTrip(outputType, -value); + } + } + } + + /** Tests writeRawVarint32() and writeRawVarint64(). */ + public void testWriteVarint() throws Exception { + assertWriteVarint(bytes(0x00), 0); + assertWriteVarint(bytes(0x01), 1); + assertWriteVarint(bytes(0x7f), 127); + // 14882 + assertWriteVarint(bytes(0xa2, 0x74), (0x22 << 0) | (0x74 << 7)); + // 2961488830 + assertWriteVarint( + bytes(0xbe, 0xf7, 0x92, 0x84, 0x0b), + (0x3e << 0) | (0x77 << 7) | (0x12 << 14) | (0x04 << 21) | (0x0bL << 28)); + + // 64-bit + // 7256456126 + assertWriteVarint( + bytes(0xbe, 0xf7, 0x92, 0x84, 0x1b), + (0x3e << 0) | (0x77 << 7) | (0x12 << 14) | (0x04 << 21) | (0x1bL << 28)); + // 41256202580718336 + assertWriteVarint( + bytes(0x80, 0xe6, 0xeb, 0x9c, 0xc3, 0xc9, 0xa4, 0x49), + (0x00 << 0) | (0x66 << 7) | (0x6b << 14) | (0x1c << 21) | (0x43L << 28) | (0x49L << 35) + | (0x24L << 42) | (0x49L << 49)); + // 11964378330978735131 + assertWriteVarint( + bytes(0x9b, 0xa8, 0xf9, 0xc2, 0xbb, 0xd6, 0x80, 0x85, 0xa6, 0x01), + (0x1b << 0) | (0x28 << 7) | (0x79 << 14) | (0x42 << 21) | (0x3bL << 28) | (0x56L << 35) + | (0x00L << 42) | (0x05L << 49) | (0x26L << 56) | (0x01L << 63)); + } + + /** Tests writeRawLittleEndian32() and writeRawLittleEndian64(). */ + public void testWriteLittleEndian() throws Exception { + assertWriteLittleEndian32(bytes(0x78, 0x56, 0x34, 0x12), 0x12345678); + assertWriteLittleEndian32(bytes(0xf0, 0xde, 0xbc, 0x9a), 0x9abcdef0); + + assertWriteLittleEndian64( + bytes(0xf0, 0xde, 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12), 0x123456789abcdef0L); + assertWriteLittleEndian64( + bytes(0x78, 0x56, 0x34, 0x12, 0xf0, 0xde, 0xbc, 0x9a), 0x9abcdef012345678L); + } + + /** Test encodeZigZag32() and encodeZigZag64(). */ + public void testEncodeZigZag() throws Exception { + assertEquals(0, CodedOutputStream.encodeZigZag32(0)); + assertEquals(1, CodedOutputStream.encodeZigZag32(-1)); + assertEquals(2, CodedOutputStream.encodeZigZag32(1)); + assertEquals(3, CodedOutputStream.encodeZigZag32(-2)); + assertEquals(0x7FFFFFFE, CodedOutputStream.encodeZigZag32(0x3FFFFFFF)); + assertEquals(0x7FFFFFFF, CodedOutputStream.encodeZigZag32(0xC0000000)); + assertEquals(0xFFFFFFFE, CodedOutputStream.encodeZigZag32(0x7FFFFFFF)); + assertEquals(0xFFFFFFFF, CodedOutputStream.encodeZigZag32(0x80000000)); + + assertEquals(0, CodedOutputStream.encodeZigZag64(0)); + assertEquals(1, CodedOutputStream.encodeZigZag64(-1)); + assertEquals(2, CodedOutputStream.encodeZigZag64(1)); + assertEquals(3, CodedOutputStream.encodeZigZag64(-2)); + assertEquals(0x000000007FFFFFFEL, CodedOutputStream.encodeZigZag64(0x000000003FFFFFFFL)); + assertEquals(0x000000007FFFFFFFL, CodedOutputStream.encodeZigZag64(0xFFFFFFFFC0000000L)); + assertEquals(0x00000000FFFFFFFEL, CodedOutputStream.encodeZigZag64(0x000000007FFFFFFFL)); + assertEquals(0x00000000FFFFFFFFL, CodedOutputStream.encodeZigZag64(0xFFFFFFFF80000000L)); + assertEquals(0xFFFFFFFFFFFFFFFEL, CodedOutputStream.encodeZigZag64(0x7FFFFFFFFFFFFFFFL)); + assertEquals(0xFFFFFFFFFFFFFFFFL, CodedOutputStream.encodeZigZag64(0x8000000000000000L)); + + // Some easier-to-verify round-trip tests. The inputs (other than 0, 1, -1) + // were chosen semi-randomly via keyboard bashing. + assertEquals(0, CodedOutputStream.encodeZigZag32(CodedInputStream.decodeZigZag32(0))); + assertEquals(1, CodedOutputStream.encodeZigZag32(CodedInputStream.decodeZigZag32(1))); + assertEquals(-1, CodedOutputStream.encodeZigZag32(CodedInputStream.decodeZigZag32(-1))); + assertEquals(14927, CodedOutputStream.encodeZigZag32(CodedInputStream.decodeZigZag32(14927))); + assertEquals(-3612, CodedOutputStream.encodeZigZag32(CodedInputStream.decodeZigZag32(-3612))); + + assertEquals(0, CodedOutputStream.encodeZigZag64(CodedInputStream.decodeZigZag64(0))); + assertEquals(1, CodedOutputStream.encodeZigZag64(CodedInputStream.decodeZigZag64(1))); + assertEquals(-1, CodedOutputStream.encodeZigZag64(CodedInputStream.decodeZigZag64(-1))); + assertEquals(14927, CodedOutputStream.encodeZigZag64(CodedInputStream.decodeZigZag64(14927))); + assertEquals(-3612, CodedOutputStream.encodeZigZag64(CodedInputStream.decodeZigZag64(-3612))); + + assertEquals( + 856912304801416L, + CodedOutputStream.encodeZigZag64(CodedInputStream.decodeZigZag64(856912304801416L))); + assertEquals( + -75123905439571256L, + CodedOutputStream.encodeZigZag64(CodedInputStream.decodeZigZag64(-75123905439571256L))); + } + + /** Tests writing a whole message with every field type. */ + public void testWriteWholeMessage() throws Exception { + final byte[] expectedBytes = TestUtil.getGoldenMessage().toByteArray(); + TestAllTypes message = TestUtil.getAllSet(); + + for (OutputType outputType : OutputType.values()) { + Coder coder = outputType.newCoder(message.getSerializedSize()); + message.writeTo(coder.stream()); + coder.stream().flush(); + byte[] rawBytes = coder.toByteArray(); + assertEqualBytes(outputType, expectedBytes, rawBytes); + } + + // Try different block sizes. + for (int blockSize = 1; blockSize < 256; blockSize *= 2) { + Coder coder = OutputType.STREAM.newCoder(blockSize); + message.writeTo(coder.stream()); + coder.stream().flush(); + assertEqualBytes(OutputType.STREAM, expectedBytes, coder.toByteArray()); + } + } + + /** + * Tests writing a whole message with every packed field type. Ensures the + * wire format of packed fields is compatible with C++. + */ + public void testWriteWholePackedFieldsMessage() throws Exception { + byte[] expectedBytes = TestUtil.getGoldenPackedFieldsMessage().toByteArray(); + TestPackedTypes message = TestUtil.getPackedSet(); + + for (OutputType outputType : OutputType.values()) { + Coder coder = outputType.newCoder(message.getSerializedSize()); + message.writeTo(coder.stream()); + coder.stream().flush(); + byte[] rawBytes = coder.toByteArray(); + assertEqualBytes(outputType, expectedBytes, rawBytes); + } + } + + /** + * Test writing a message containing a negative enum value. This used to + * fail because the size was not properly computed as a sign-extended varint. + */ + public void testWriteMessageWithNegativeEnumValue() throws Exception { + SparseEnumMessage message = + SparseEnumMessage.newBuilder().setSparseEnum(TestSparseEnum.SPARSE_E).build(); + assertTrue(message.getSparseEnum().getNumber() < 0); + for (OutputType outputType : OutputType.values()) { + Coder coder = outputType.newCoder(message.getSerializedSize()); + message.writeTo(coder.stream()); + coder.stream().flush(); + byte[] rawBytes = coder.toByteArray(); + SparseEnumMessage message2 = SparseEnumMessage.parseFrom(rawBytes); + assertEquals(TestSparseEnum.SPARSE_E, message2.getSparseEnum()); + } + } + + /** Test getTotalBytesWritten() */ + public void testGetTotalBytesWritten() throws Exception { + Coder coder = OutputType.STREAM.newCoder(4 * 1024); + + // Write some some bytes (more than the buffer can hold) and verify that totalWritten + // is correct. + byte[] value = "abcde".getBytes(Internal.UTF_8); + for (int i = 0; i < 1024; ++i) { + coder.stream().writeRawBytes(value, 0, value.length); + } + assertEquals(value.length * 1024, coder.stream().getTotalBytesWritten()); + + // Now write an encoded string. + String string = + "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"; + // Ensure we take the slower fast path. + assertTrue(CodedOutputStream.computeUInt32SizeNoTag(string.length()) + != CodedOutputStream.computeUInt32SizeNoTag(string.length() * Utf8.MAX_BYTES_PER_CHAR)); + + coder.stream().writeStringNoTag(string); + int stringSize = CodedOutputStream.computeStringSizeNoTag(string); + + // Verify that the total bytes written is correct + assertEquals((value.length * 1024) + stringSize, coder.stream().getTotalBytesWritten()); + } + + // TODO(dweis): Write a comprehensive test suite for CodedOutputStream that covers more than just + // this case. + public void testWriteStringNoTag_fastpath() throws Exception { + int bufferSize = 153; + String threeBytesPer = "\u0981"; + String string = threeBytesPer; + for (int i = 0; i < 50; i++) { + string += threeBytesPer; + } + // These checks ensure we will tickle the slower fast path. + assertEquals(1, CodedOutputStream.computeUInt32SizeNoTag(string.length())); + assertEquals( + 2, CodedOutputStream.computeUInt32SizeNoTag(string.length() * Utf8.MAX_BYTES_PER_CHAR)); + assertEquals(bufferSize, string.length() * Utf8.MAX_BYTES_PER_CHAR); + + for (OutputType outputType : OutputType.values()) { + Coder coder = outputType.newCoder(bufferSize + 2); + coder.stream().writeStringNoTag(string); + coder.stream().flush(); + } + } + + 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(Internal.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(OutputType.ARRAY, bytes(0x02, 0x33, 0x44, 0x00), destination); + assertEquals(3, codedStream.getTotalBytesWritten()); + } + + public void testSerializeUtf8_MultipleSmallWrites() throws Exception { + final String source = "abcdefghijklmnopqrstuvwxyz"; + + // Generate the expected output if the source string is written 2 bytes at a time. + ByteArrayOutputStream expectedBytesStream = new ByteArrayOutputStream(); + for (int pos = 0; pos < source.length(); pos += 2) { + String substr = source.substring(pos, pos + 2); + expectedBytesStream.write(2); + expectedBytesStream.write(substr.getBytes(Internal.UTF_8)); + } + final byte[] expectedBytes = expectedBytesStream.toByteArray(); + + // For each output type, write the source string 2 bytes at a time and verify the output. + for (OutputType outputType : OutputType.values()) { + Coder coder = outputType.newCoder(expectedBytes.length); + for (int pos = 0; pos < source.length(); pos += 2) { + String substr = source.substring(pos, pos + 2); + coder.stream().writeStringNoTag(substr); + } + coder.stream().flush(); + assertEqualBytes(outputType, expectedBytes, coder.toByteArray()); + } + } + + public void testSerializeInvalidUtf8() throws Exception { + String[] invalidStrings = new String[] {newString(Character.MIN_HIGH_SURROGATE), + "foobar" + newString(Character.MIN_HIGH_SURROGATE), newString(Character.MIN_LOW_SURROGATE), + "foobar" + newString(Character.MIN_LOW_SURROGATE), + newString(Character.MIN_HIGH_SURROGATE, Character.MIN_HIGH_SURROGATE)}; + + CodedOutputStream outputWithStream = CodedOutputStream.newInstance(new ByteArrayOutputStream()); + CodedOutputStream outputWithArray = CodedOutputStream.newInstance(new byte[10000]); + CodedOutputStream outputWithByteBuffer = + CodedOutputStream.newInstance(ByteBuffer.allocate(10000)); + for (String s : invalidStrings) { + // TODO(dweis): These should all fail; instead they are corrupting data. + CodedOutputStream.computeStringSizeNoTag(s); + outputWithStream.writeStringNoTag(s); + outputWithArray.writeStringNoTag(s); + outputWithByteBuffer.writeStringNoTag(s); + } + } + + // TODO(nathanmittler): This test can be deleted once we properly throw IOException while + // encoding invalid UTF-8 strings. + public void testSerializeInvalidUtf8FollowedByOutOfSpace() throws Exception { + final int notEnoughBytes = 4; + CodedOutputStream outputWithArray = CodedOutputStream.newInstance(new byte[notEnoughBytes]); + CodedOutputStream outputWithByteBuffer = + CodedOutputStream.newInstance(ByteBuffer.allocate(notEnoughBytes)); + + String invalidString = newString(Character.MIN_HIGH_SURROGATE, 'f', 'o', 'o', 'b', 'a', 'r'); + try { + outputWithArray.writeStringNoTag(invalidString); + fail("Expected OutOfSpaceException"); + } catch (OutOfSpaceException e) { + assertTrue(e.getCause() instanceof IndexOutOfBoundsException); + } + try { + outputWithByteBuffer.writeStringNoTag(invalidString); + fail("Expected OutOfSpaceException"); + } catch (OutOfSpaceException e) { + assertTrue(e.getCause() instanceof IndexOutOfBoundsException); + } + } + + /** Regression test for https://github.com/google/protobuf/issues/292 */ + public void testCorrectExceptionThrowWhenEncodingStringsWithoutEnoughSpace() throws Exception { + String testCase = "Foooooooo"; + assertEquals( + CodedOutputStream.computeUInt32SizeNoTag(testCase.length()), + CodedOutputStream.computeUInt32SizeNoTag(testCase.length() * 3)); + assertEquals(11, CodedOutputStream.computeStringSize(1, testCase)); + // Tag is one byte, varint describing string length is 1 byte, string length is 9 bytes. + // An array of size 1 will cause a failure when trying to write the varint. + for (OutputType outputType : + new OutputType[] {OutputType.ARRAY, OutputType.NIO_HEAP, OutputType.NIO_DIRECT}) { + for (int i = 0; i < 11; i++) { + Coder coder = outputType.newCoder(i); + try { + coder.stream().writeString(1, testCase); + fail("Should have thrown an out of space exception"); + } catch (CodedOutputStream.OutOfSpaceException expected) { + } + } + } + } + + public void testDifferentStringLengths() throws Exception { + // Test string serialization roundtrip using strings of the following lengths, + // with ASCII and Unicode characters requiring different UTF-8 byte counts per + // char, hence causing the length delimiter varint to sometimes require more + // bytes for the Unicode strings than the ASCII string of the same length. + int[] lengths = new int[] { + 0, + 1, + (1 << 4) - 1, // 1 byte for ASCII and Unicode + (1 << 7) - 1, // 1 byte for ASCII, 2 bytes for Unicode + (1 << 11) - 1, // 2 bytes for ASCII and Unicode + (1 << 14) - 1, // 2 bytes for ASCII, 3 bytes for Unicode + (1 << 17) - 1, + // 3 bytes for ASCII and Unicode + }; + for (OutputType outputType : OutputType.values()) { + for (int i : lengths) { + testEncodingOfString(outputType, 'q', i); // 1 byte per char + testEncodingOfString(outputType, '\u07FF', i); // 2 bytes per char + testEncodingOfString(outputType, '\u0981', i); // 3 bytes per char + } + } + } + + public void testNioEncodersWithInitialOffsets() throws Exception { + String value = "abc"; + for (Coder coder : new Coder[] {new NioHeapCoder(10, 2), new NioDirectCoder(10, 2)}) { + coder.stream().writeStringNoTag(value); + coder.stream().flush(); + assertEqualBytes(coder.getOutputType(), new byte[]{3, 'a', 'b', 'c'}, coder.toByteArray()); + } + } + + /** + * Parses the given bytes using writeRawLittleEndian32() and checks + * that the result matches the given value. + */ + private static void assertWriteLittleEndian32(byte[] data, int value) throws Exception { + for (OutputType outputType : OutputType.values()) { + Coder coder = outputType.newCoder(data.length); + coder.stream().writeFixed32NoTag(value); + coder.stream().flush(); + assertEqualBytes(outputType, data, coder.toByteArray()); + } + + // Try different block sizes. + for (int blockSize = 1; blockSize <= 16; blockSize *= 2) { + Coder coder = OutputType.STREAM.newCoder(blockSize); + coder.stream().writeFixed32NoTag(value); + coder.stream().flush(); + assertEqualBytes(OutputType.STREAM, data, coder.toByteArray()); + } + } + + /** + * Parses the given bytes using writeRawLittleEndian64() and checks + * that the result matches the given value. + */ + private static void assertWriteLittleEndian64(byte[] data, long value) throws Exception { + for (OutputType outputType : OutputType.values()) { + Coder coder = outputType.newCoder(data.length); + coder.stream().writeFixed64NoTag(value); + coder.stream().flush(); + assertEqualBytes(outputType, data, coder.toByteArray()); + } + + // Try different block sizes. + for (int blockSize = 1; blockSize <= 16; blockSize *= 2) { + Coder coder = OutputType.STREAM.newCoder(blockSize); + coder.stream().writeFixed64NoTag(value); + coder.stream().flush(); + assertEqualBytes(OutputType.STREAM, data, coder.toByteArray()); + } + } + + private static String newString(char... chars) { + return new String(chars); + } + + private static void testEncodingOfString(OutputType outputType, char c, int length) + throws Exception { + String fullString = fullString(c, length); + TestAllTypes testAllTypes = TestAllTypes.newBuilder().setOptionalString(fullString).build(); + Coder coder = outputType.newCoder(testAllTypes.getSerializedSize()); + testAllTypes.writeTo(coder.stream()); + coder.stream().flush(); + assertEquals( + "OuputType: " + outputType, + fullString, + TestAllTypes.parseFrom(coder.toByteArray()).getOptionalString()); + } + + private static String fullString(char c, int length) { + char[] result = new char[length]; + Arrays.fill(result, c); + return new String(result); + } + + /** + * Helper to construct a byte array from a bunch of bytes. The inputs are + * actually ints so that I can use hex notation and not get stupid errors + * about precision. + */ + private static byte[] bytes(int... bytesAsInts) { + byte[] bytes = new byte[bytesAsInts.length]; + for (int i = 0; i < bytesAsInts.length; i++) { + bytes[i] = (byte) bytesAsInts[i]; + } + return bytes; + } + + /** Arrays.asList() does not work with arrays of primitives. :( */ + private static List<Byte> toList(byte[] bytes) { + List<Byte> result = new ArrayList<Byte>(); + for (byte b : bytes) { + result.add(b); + } + return result; + } + + private static void assertEqualBytes(OutputType outputType, byte[] a, byte[] b) { + assertEquals(outputType.name(), toList(a), toList(b)); + } + + /** + * Writes the given value using writeRawVarint32() and writeRawVarint64() and + * checks that the result matches the given bytes. + */ + private static void assertWriteVarint(byte[] data, long value) throws Exception { + for (OutputType outputType : OutputType.values()) { + // Only test 32-bit write if the value fits into an int. + if (value == (int) value) { + Coder coder = outputType.newCoder(10); + coder.stream().writeUInt32NoTag((int) value); + coder.stream().flush(); + assertEqualBytes(outputType, data, coder.toByteArray()); + + // Also try computing size. + assertEquals(data.length, CodedOutputStream.computeUInt32SizeNoTag((int) value)); + } + + { + Coder coder = outputType.newCoder(10); + coder.stream().writeUInt64NoTag(value); + coder.stream().flush(); + assertEqualBytes(outputType, data, coder.toByteArray()); + + // Also try computing size. + assertEquals(data.length, CodedOutputStream.computeUInt64SizeNoTag(value)); + } + } + + // Try different block sizes. + for (int blockSize = 1; blockSize <= 16; blockSize *= 2) { + // Only test 32-bit write if the value fits into an int. + if (value == (int) value) { + Coder coder = OutputType.STREAM.newCoder(blockSize); + coder.stream().writeUInt64NoTag((int) value); + coder.stream().flush(); + assertEqualBytes(OutputType.STREAM, data, coder.toByteArray()); + + ByteArrayOutputStream rawOutput = new ByteArrayOutputStream(); + CodedOutputStream output = CodedOutputStream.newInstance(rawOutput, blockSize); + output.writeUInt32NoTag((int) value); + output.flush(); + assertEqualBytes(OutputType.STREAM, data, rawOutput.toByteArray()); + } + + { + Coder coder = OutputType.STREAM.newCoder(blockSize); + coder.stream().writeUInt64NoTag(value); + coder.stream().flush(); + assertEqualBytes(OutputType.STREAM, data, coder.toByteArray()); + } + } + } + + private static void assertVarintRoundTrip(OutputType outputType, long value) throws Exception { + { + Coder coder = outputType.newCoder(10); + coder.stream().writeUInt64NoTag(value); + coder.stream().flush(); + byte[] bytes = coder.toByteArray(); + assertEquals( + outputType.name(), bytes.length, CodedOutputStream.computeUInt64SizeNoTag(value)); + CodedInputStream input = CodedInputStream.newInstance(new ByteArrayInputStream(bytes)); + assertEquals(outputType.name(), value, input.readRawVarint64()); + } + + if (value == (int) value) { + Coder coder = outputType.newCoder(10); + coder.stream().writeUInt32NoTag((int) value); + coder.stream().flush(); + byte[] bytes = coder.toByteArray(); + assertEquals( + outputType.name(), bytes.length, CodedOutputStream.computeUInt32SizeNoTag((int) value)); + CodedInputStream input = CodedInputStream.newInstance(new ByteArrayInputStream(bytes)); + assertEquals(outputType.name(), value, input.readRawVarint32()); + } + } +} diff --git a/java/src/test/java/com/google/protobuf/DeprecatedFieldTest.java b/java/core/src/test/java/com/google/protobuf/DeprecatedFieldTest.java index e7905f79..ce85ae2e 100644 --- a/java/src/test/java/com/google/protobuf/DeprecatedFieldTest.java +++ b/java/core/src/test/java/com/google/protobuf/DeprecatedFieldTest.java @@ -36,6 +36,7 @@ import junit.framework.TestCase; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; + /** * Test field deprecation * diff --git a/java/src/test/java/com/google/protobuf/DescriptorsTest.java b/java/core/src/test/java/com/google/protobuf/DescriptorsTest.java index 30da2487..ef89b389 100644 --- a/java/src/test/java/com/google/protobuf/DescriptorsTest.java +++ b/java/core/src/test/java/com/google/protobuf/DescriptorsTest.java @@ -59,7 +59,6 @@ import protobuf_unittest.UnittestProto.TestMultipleExtensionRanges; import protobuf_unittest.UnittestProto.TestRequired; import protobuf_unittest.UnittestProto.TestReservedFields; import protobuf_unittest.UnittestProto.TestService; - import junit.framework.TestCase; import java.util.Arrays; @@ -274,6 +273,15 @@ public class DescriptorsTest extends TestCase { assertFalse(repeatedField.isRequired()); assertTrue(repeatedField.isRepeated()); } + + public void testFieldDescriptorJsonName() throws Exception { + FieldDescriptor requiredField = TestRequired.getDescriptor().findFieldByName("a"); + FieldDescriptor optionalField = TestAllTypes.getDescriptor().findFieldByName("optional_int32"); + FieldDescriptor repeatedField = TestAllTypes.getDescriptor().findFieldByName("repeated_int32"); + assertEquals("a", requiredField.getJsonName()); + assertEquals("optionalInt32", optionalField.getJsonName()); + assertEquals("repeatedInt32", repeatedField.getJsonName()); + } public void testFieldDescriptorDefault() throws Exception { Descriptor d = TestAllTypes.getDescriptor(); @@ -564,6 +572,42 @@ public class DescriptorsTest extends TestCase { } } + public void testUnknownFieldsDenied() throws Exception { + FileDescriptorProto fooProto = FileDescriptorProto.newBuilder() + .setName("foo.proto") + .addMessageType(DescriptorProto.newBuilder() + .setName("Foo") + .addField(FieldDescriptorProto.newBuilder() + .setLabel(FieldDescriptorProto.Label.LABEL_OPTIONAL) + .setTypeName("Bar") + .setName("bar") + .setNumber(1))) + .build(); + + try { + Descriptors.FileDescriptor.buildFrom(fooProto, new FileDescriptor[0]); + fail("DescriptorValidationException expected"); + } catch (DescriptorValidationException e) { + assertTrue(e.getMessage().indexOf("Bar") != -1); + assertTrue(e.getMessage().indexOf("is not defined") != -1); + } + } + + public void testUnknownFieldsAllowed() throws Exception { + FileDescriptorProto fooProto = FileDescriptorProto.newBuilder() + .setName("foo.proto") + .addDependency("bar.proto") + .addMessageType(DescriptorProto.newBuilder() + .setName("Foo") + .addField(FieldDescriptorProto.newBuilder() + .setLabel(FieldDescriptorProto.Label.LABEL_OPTIONAL) + .setTypeName("Bar") + .setName("bar") + .setNumber(1))) + .build(); + Descriptors.FileDescriptor.buildFrom(fooProto, new FileDescriptor[0], true); + } + public void testHiddenDependency() throws Exception { FileDescriptorProto barProto = FileDescriptorProto.newBuilder() .setName("bar.proto") diff --git a/java/src/test/java/com/google/protobuf/DoubleArrayListTest.java b/java/core/src/test/java/com/google/protobuf/DoubleArrayListTest.java index e7a73d70..85b418c4 100644 --- a/java/src/test/java/com/google/protobuf/DoubleArrayListTest.java +++ b/java/core/src/test/java/com/google/protobuf/DoubleArrayListTest.java @@ -73,20 +73,6 @@ public class DoubleArrayListTest extends TestCase { assertImmutable(list); } - public void testCopyConstructor() { - DoubleArrayList copy = new DoubleArrayList(TERTIARY_LIST); - assertEquals(TERTIARY_LIST, copy); - - copy = new DoubleArrayList(DoubleArrayList.emptyList()); - assertEquals(DoubleArrayList.emptyList(), copy); - - copy = new DoubleArrayList(asList(1D, 2D, 3D)); - assertEquals(asList(1D, 2D, 3D), copy); - - copy = new DoubleArrayList(Collections.<Double>emptyList()); - assertEquals(DoubleArrayList.emptyList(), copy); - } - public void testModificationWithIteration() { list.addAll(asList(1D, 2D, 3D, 4D)); Iterator<Double> iterator = list.iterator(); @@ -310,7 +296,7 @@ public class DoubleArrayListTest extends TestCase { } private void assertImmutable(DoubleArrayList list) { - if (list.contains(1)) { + if (list.contains(1D)) { throw new RuntimeException("Cannot test the immutability of lists that contain 1."); } @@ -413,7 +399,7 @@ public class DoubleArrayListTest extends TestCase { } try { - list.removeAll(Collections.singleton(1)); + list.removeAll(Collections.singleton(1D)); fail(); } catch (UnsupportedOperationException e) { // expected @@ -434,7 +420,7 @@ public class DoubleArrayListTest extends TestCase { } try { - list.retainAll(Collections.singleton(1)); + list.retainAll(Collections.singleton(1D)); fail(); } catch (UnsupportedOperationException e) { // expected diff --git a/java/src/test/java/com/google/protobuf/DynamicMessageTest.java b/java/core/src/test/java/com/google/protobuf/DynamicMessageTest.java index 55144e7c..8f3a74c1 100644 --- a/java/src/test/java/com/google/protobuf/DynamicMessageTest.java +++ b/java/core/src/test/java/com/google/protobuf/DynamicMessageTest.java @@ -33,13 +33,13 @@ package com.google.protobuf; import com.google.protobuf.Descriptors.EnumDescriptor; 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; import protobuf_unittest.UnittestProto.TestPackedTypes; import junit.framework.TestCase; + import java.util.Arrays; /** diff --git a/java/core/src/test/java/com/google/protobuf/EnumTest.java b/java/core/src/test/java/com/google/protobuf/EnumTest.java new file mode 100644 index 00000000..14c7406b --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/EnumTest.java @@ -0,0 +1,76 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// 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.UnittestLite.ForeignEnumLite; +import com.google.protobuf.UnittestLite.TestAllTypesLite; +import protobuf_unittest.UnittestProto.ForeignEnum; +import protobuf_unittest.UnittestProto.TestAllTypes; + +import junit.framework.TestCase; + +public class EnumTest extends TestCase { + + public void testForNumber() { + ForeignEnum e = ForeignEnum.forNumber(ForeignEnum.FOREIGN_BAR.getNumber()); + assertEquals(ForeignEnum.FOREIGN_BAR, e); + + e = ForeignEnum.forNumber(1000); + assertEquals(null, e); + } + + public void testForNumber_oneof() { + TestAllTypes.OneofFieldCase e = TestAllTypes.OneofFieldCase.forNumber( + TestAllTypes.OneofFieldCase.ONEOF_NESTED_MESSAGE.getNumber()); + assertEquals(TestAllTypes.OneofFieldCase.ONEOF_NESTED_MESSAGE, e); + + e = TestAllTypes.OneofFieldCase.forNumber(1000); + assertEquals(null, e); + } + + public void testForNumberLite() { + ForeignEnumLite e = ForeignEnumLite.forNumber(ForeignEnumLite.FOREIGN_LITE_BAR.getNumber()); + assertEquals(ForeignEnumLite.FOREIGN_LITE_BAR, e); + + e = ForeignEnumLite.forNumber(1000); + assertEquals(null, e); + } + + public void testForNumberLite_oneof() { + TestAllTypesLite.OneofFieldCase e = TestAllTypesLite.OneofFieldCase.forNumber( + TestAllTypesLite.OneofFieldCase.ONEOF_NESTED_MESSAGE.getNumber()); + assertEquals(TestAllTypesLite.OneofFieldCase.ONEOF_NESTED_MESSAGE, e); + + e = TestAllTypesLite.OneofFieldCase.forNumber(1000); + assertEquals(null, e); + } +} + diff --git a/java/src/test/java/com/google/protobuf/FieldPresenceTest.java b/java/core/src/test/java/com/google/protobuf/FieldPresenceTest.java index eaeec0b8..304cec4f 100644 --- a/java/src/test/java/com/google/protobuf/FieldPresenceTest.java +++ b/java/core/src/test/java/com/google/protobuf/FieldPresenceTest.java @@ -44,9 +44,9 @@ import junit.framework.TestCase; * non-message fields. */ public class FieldPresenceTest extends TestCase { - private static boolean hasMethod(Class clazz, String name) { + private static boolean hasMethod(Class<?> clazz, String name) { try { - if (clazz.getMethod(name, new Class[]{}) != null) { + if (clazz.getMethod(name) != null) { return true; } else { return false; @@ -56,90 +56,90 @@ public class FieldPresenceTest extends TestCase { } } - private static boolean isHasMethodRemoved( - Class classWithFieldPresence, - Class classWithoutFieldPresence, + private static void assertHasMethodRemoved( + Class<?> classWithFieldPresence, + Class<?> classWithoutFieldPresence, String camelName) { - return hasMethod(classWithFieldPresence, "get" + camelName) - && hasMethod(classWithFieldPresence, "has" + camelName) - && hasMethod(classWithoutFieldPresence, "get" + camelName) - && !hasMethod(classWithoutFieldPresence, "has" + camelName); + assertTrue(hasMethod(classWithFieldPresence, "get" + camelName)); + assertTrue(hasMethod(classWithFieldPresence, "has" + camelName)); + assertTrue(hasMethod(classWithoutFieldPresence, "get" + camelName)); + assertFalse(hasMethod(classWithoutFieldPresence, "has" + camelName)); } public void testHasMethod() { // Optional non-message fields don't have a hasFoo() method generated. - assertTrue(isHasMethodRemoved( + assertHasMethodRemoved( UnittestProto.TestAllTypes.class, TestAllTypes.class, - "OptionalInt32")); - assertTrue(isHasMethodRemoved( + "OptionalInt32"); + assertHasMethodRemoved( UnittestProto.TestAllTypes.class, TestAllTypes.class, - "OptionalString")); - assertTrue(isHasMethodRemoved( + "OptionalString"); + assertHasMethodRemoved( UnittestProto.TestAllTypes.class, TestAllTypes.class, - "OptionalBytes")); - assertTrue(isHasMethodRemoved( + "OptionalBytes"); + assertHasMethodRemoved( UnittestProto.TestAllTypes.class, TestAllTypes.class, - "OptionalNestedEnum")); + "OptionalNestedEnum"); - assertTrue(isHasMethodRemoved( + assertHasMethodRemoved( UnittestProto.TestAllTypes.Builder.class, TestAllTypes.Builder.class, - "OptionalInt32")); - assertTrue(isHasMethodRemoved( + "OptionalInt32"); + assertHasMethodRemoved( UnittestProto.TestAllTypes.Builder.class, TestAllTypes.Builder.class, - "OptionalString")); - assertTrue(isHasMethodRemoved( + "OptionalString"); + assertHasMethodRemoved( UnittestProto.TestAllTypes.Builder.class, TestAllTypes.Builder.class, - "OptionalBytes")); - assertTrue(isHasMethodRemoved( + "OptionalBytes"); + assertHasMethodRemoved( UnittestProto.TestAllTypes.Builder.class, TestAllTypes.Builder.class, - "OptionalNestedEnum")); + "OptionalNestedEnum"); // message fields still have the hasFoo() method generated. assertFalse(TestAllTypes.newBuilder().build().hasOptionalNestedMessage()); assertFalse(TestAllTypes.newBuilder().hasOptionalNestedMessage()); // oneof fields don't have hasFoo() methods (even for message types). - assertTrue(isHasMethodRemoved( + assertHasMethodRemoved( UnittestProto.TestAllTypes.class, TestAllTypes.class, - "OneofUint32")); - assertTrue(isHasMethodRemoved( + "OneofUint32"); + assertHasMethodRemoved( UnittestProto.TestAllTypes.class, TestAllTypes.class, - "OneofString")); - assertTrue(isHasMethodRemoved( + "OneofString"); + assertHasMethodRemoved( UnittestProto.TestAllTypes.class, TestAllTypes.class, - "OneofBytes")); - assertTrue(isHasMethodRemoved( + "OneofBytes"); + assertHasMethodRemoved( UnittestProto.TestAllTypes.class, TestAllTypes.class, - "OneofNestedMessage")); + "OneofNestedMessage"); - assertTrue(isHasMethodRemoved( + assertHasMethodRemoved( UnittestProto.TestAllTypes.Builder.class, TestAllTypes.Builder.class, - "OneofUint32")); - assertTrue(isHasMethodRemoved( + "OneofUint32"); + assertHasMethodRemoved( UnittestProto.TestAllTypes.Builder.class, TestAllTypes.Builder.class, - "OneofString")); - assertTrue(isHasMethodRemoved( + "OneofString"); + assertHasMethodRemoved( UnittestProto.TestAllTypes.Builder.class, TestAllTypes.Builder.class, - "OneofBytes")); - assertTrue(isHasMethodRemoved( + "OneofBytes"); + assertHasMethodRemoved( UnittestProto.TestAllTypes.Builder.class, TestAllTypes.Builder.class, - "OneofNestedMessage")); + "OneofNestedMessage"); } public void testOneofEquals() throws Exception { @@ -232,24 +232,24 @@ public class FieldPresenceTest extends TestCase { assertTrue(message.hasField(optionalNestedEnumField)); assertEquals(4, message.getAllFields().size()); } - + public void testMessageField() { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); assertFalse(builder.hasOptionalNestedMessage()); assertFalse(builder.build().hasOptionalNestedMessage()); - + TestAllTypes.NestedMessage.Builder nestedBuilder = builder.getOptionalNestedMessageBuilder(); assertTrue(builder.hasOptionalNestedMessage()); assertTrue(builder.build().hasOptionalNestedMessage()); - + nestedBuilder.setValue(1); assertEquals(1, builder.build().getOptionalNestedMessage().getValue()); - + builder.clearOptionalNestedMessage(); assertFalse(builder.hasOptionalNestedMessage()); assertFalse(builder.build().hasOptionalNestedMessage()); - + // Unlike non-message fields, if we set a message field to its default value (i.e., // default instance), the field should be seen as present. builder.setOptionalNestedMessage(TestAllTypes.NestedMessage.getDefaultInstance()); @@ -340,7 +340,7 @@ public class FieldPresenceTest extends TestCase { assertTrue(builder.buildPartial().isInitialized()); } - + // Test that unknown fields are dropped. public void testUnknownFields() throws Exception { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); @@ -348,7 +348,7 @@ public class FieldPresenceTest extends TestCase { builder.addRepeatedInt32(5678); TestAllTypes message = builder.build(); ByteString data = message.toByteString(); - + TestOptionalFieldsOnly optionalOnlyMessage = TestOptionalFieldsOnly.parseFrom(data); // UnknownFieldSet should be empty. @@ -360,7 +360,7 @@ public class FieldPresenceTest extends TestCase { // The repeated field is discarded because it's unknown to the optional-only // message. assertEquals(0, message.getRepeatedInt32Count()); - + DynamicMessage dynamicOptionalOnlyMessage = DynamicMessage.getDefaultInstance( TestOptionalFieldsOnly.getDescriptor()) diff --git a/java/src/test/java/com/google/protobuf/FloatArrayListTest.java b/java/core/src/test/java/com/google/protobuf/FloatArrayListTest.java index 8f3e93dc..88a75743 100644 --- a/java/src/test/java/com/google/protobuf/FloatArrayListTest.java +++ b/java/core/src/test/java/com/google/protobuf/FloatArrayListTest.java @@ -73,20 +73,6 @@ public class FloatArrayListTest extends TestCase { assertImmutable(list); } - public void testCopyConstructor() { - FloatArrayList copy = new FloatArrayList(TERTIARY_LIST); - assertEquals(TERTIARY_LIST, copy); - - copy = new FloatArrayList(FloatArrayList.emptyList()); - assertEquals(FloatArrayList.emptyList(), copy); - - copy = new FloatArrayList(asList(1F, 2F, 3F)); - assertEquals(asList(1F, 2F, 3F), copy); - - copy = new FloatArrayList(Collections.<Float>emptyList()); - assertEquals(FloatArrayList.emptyList(), copy); - } - public void testModificationWithIteration() { list.addAll(asList(1F, 2F, 3F, 4F)); Iterator<Float> iterator = list.iterator(); @@ -310,7 +296,7 @@ public class FloatArrayListTest extends TestCase { } private void assertImmutable(FloatArrayList list) { - if (list.contains(1)) { + if (list.contains(1F)) { throw new RuntimeException("Cannot test the immutability of lists that contain 1."); } @@ -413,7 +399,7 @@ public class FloatArrayListTest extends TestCase { } try { - list.removeAll(Collections.singleton(1)); + list.removeAll(Collections.singleton(1F)); fail(); } catch (UnsupportedOperationException e) { // expected @@ -434,7 +420,7 @@ public class FloatArrayListTest extends TestCase { } try { - list.retainAll(Collections.singleton(1)); + list.retainAll(Collections.singleton(1F)); fail(); } catch (UnsupportedOperationException e) { // expected diff --git a/java/src/test/java/com/google/protobuf/ForceFieldBuildersPreRun.java b/java/core/src/test/java/com/google/protobuf/ForceFieldBuildersPreRun.java index a92ba374..b7eaebf5 100644 --- a/java/src/test/java/com/google/protobuf/ForceFieldBuildersPreRun.java +++ b/java/core/src/test/java/com/google/protobuf/ForceFieldBuildersPreRun.java @@ -41,7 +41,7 @@ package com.google.protobuf; */ public class ForceFieldBuildersPreRun implements Runnable { - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public void run() { GeneratedMessage.enableAlwaysUseFieldBuildersForTesting(); } diff --git a/java/src/test/java/com/google/protobuf/GeneratedMessageTest.java b/java/core/src/test/java/com/google/protobuf/GeneratedMessageTest.java index 70812b95..a9b8b638 100644 --- a/java/src/test/java/com/google/protobuf/GeneratedMessageTest.java +++ b/java/core/src/test/java/com/google/protobuf/GeneratedMessageTest.java @@ -620,6 +620,21 @@ public class GeneratedMessageTest extends TestCase { TestUtil.assertExtensionsClear(TestAllExtensions.newBuilder().build()); } + public void testUnsetRepeatedExtensionGetField() { + TestAllExtensions message = TestAllExtensions.getDefaultInstance(); + Object value; + + value = message.getField(UnittestProto.repeatedStringExtension.getDescriptor()); + assertTrue(value instanceof List); + assertTrue(((List<?>) value).isEmpty()); + assertIsUnmodifiable((List<?>) value); + + value = message.getField(UnittestProto.repeatedNestedMessageExtension.getDescriptor()); + assertTrue(value instanceof List); + assertTrue(((List<?>) value).isEmpty()); + assertIsUnmodifiable((List<?>) value); + } + public void testExtensionReflectionGetters() throws Exception { TestAllExtensions.Builder builder = TestAllExtensions.newBuilder(); TestUtil.setAllExtensions(builder); @@ -708,7 +723,7 @@ public class GeneratedMessageTest extends TestCase { public void testLiteExtensionMessageOrBuilder() throws Exception { TestAllExtensionsLite.Builder builder = TestAllExtensionsLite.newBuilder(); - TestUtil.setAllExtensions(builder); + TestUtilLite.setAllExtensions(builder); TestUtil.assertAllExtensionsSet(builder); TestAllExtensionsLite message = builder.build(); @@ -717,8 +732,8 @@ public class GeneratedMessageTest extends TestCase { public void testLiteExtensionRepeatedSetters() throws Exception { TestAllExtensionsLite.Builder builder = TestAllExtensionsLite.newBuilder(); - TestUtil.setAllExtensions(builder); - TestUtil.modifyRepeatedExtensions(builder); + TestUtilLite.setAllExtensions(builder); + TestUtilLite.modifyRepeatedExtensions(builder); TestUtil.assertRepeatedExtensionsModified(builder); TestAllExtensionsLite message = builder.build(); @@ -745,7 +760,7 @@ public class GeneratedMessageTest extends TestCase { } public void testLiteExtensionCopy() throws Exception { - TestAllExtensionsLite original = TestUtil.getAllLiteExtensionsSet(); + TestAllExtensionsLite original = TestUtilLite.getAllLiteExtensionsSet(); TestAllExtensionsLite copy = TestAllExtensionsLite.newBuilder(original).build(); TestUtil.assertAllExtensionsSet(copy); diff --git a/java/src/test/java/com/google/protobuf/IntArrayListTest.java b/java/core/src/test/java/com/google/protobuf/IntArrayListTest.java index 3733eb30..efb8f3e2 100644 --- a/java/src/test/java/com/google/protobuf/IntArrayListTest.java +++ b/java/core/src/test/java/com/google/protobuf/IntArrayListTest.java @@ -72,20 +72,6 @@ public class IntArrayListTest extends TestCase { list.makeImmutable(); assertImmutable(list); } - - public void testCopyConstructor() { - IntArrayList copy = new IntArrayList(TERTIARY_LIST); - assertEquals(TERTIARY_LIST, copy); - - copy = new IntArrayList(IntArrayList.emptyList()); - assertEquals(IntArrayList.emptyList(), copy); - - copy = new IntArrayList(asList(1, 2, 3)); - assertEquals(asList(1, 2, 3), copy); - - copy = new IntArrayList(Collections.<Integer>emptyList()); - assertEquals(IntArrayList.emptyList(), copy); - } public void testModificationWithIteration() { list.addAll(asList(1, 2, 3, 4)); diff --git a/java/src/test/java/com/google/protobuf/IsValidUtf8Test.java b/java/core/src/test/java/com/google/protobuf/IsValidUtf8Test.java index 8751baae..756049b4 100644 --- a/java/src/test/java/com/google/protobuf/IsValidUtf8Test.java +++ b/java/core/src/test/java/com/google/protobuf/IsValidUtf8Test.java @@ -30,12 +30,18 @@ package com.google.protobuf; +import static com.google.protobuf.IsValidUtf8TestUtil.DIRECT_NIO_FACTORY; +import static com.google.protobuf.IsValidUtf8TestUtil.EXPECTED_ONE_BYTE_ROUNDTRIPPABLE_COUNT; +import static com.google.protobuf.IsValidUtf8TestUtil.EXPECTED_THREE_BYTE_ROUNDTRIPPABLE_COUNT; +import static com.google.protobuf.IsValidUtf8TestUtil.HEAP_NIO_FACTORY; +import static com.google.protobuf.IsValidUtf8TestUtil.LITERAL_FACTORY; +import static com.google.protobuf.IsValidUtf8TestUtil.testBytes; + +import com.google.protobuf.IsValidUtf8TestUtil.ByteStringFactory; import com.google.protobuf.IsValidUtf8TestUtil.Shard; import junit.framework.TestCase; -import java.io.UnsupportedEncodingException; - /** * Tests cases for {@link ByteString#isValidUtf8()}. This includes three * brute force tests that actually test every permutation of one byte, two byte, @@ -51,31 +57,33 @@ import java.io.UnsupportedEncodingException; * @author martinrb@google.com (Martin Buchholz) */ public class IsValidUtf8Test extends TestCase { - /** * Tests that round tripping of all two byte permutations work. */ - public void testIsValidUtf8_1Byte() throws UnsupportedEncodingException { - IsValidUtf8TestUtil.testBytes(1, - IsValidUtf8TestUtil.EXPECTED_ONE_BYTE_ROUNDTRIPPABLE_COUNT); + public void testIsValidUtf8_1Byte() { + testBytes(LITERAL_FACTORY, 1, EXPECTED_ONE_BYTE_ROUNDTRIPPABLE_COUNT); + testBytes(HEAP_NIO_FACTORY, 1, EXPECTED_ONE_BYTE_ROUNDTRIPPABLE_COUNT); + testBytes(DIRECT_NIO_FACTORY, 1, EXPECTED_ONE_BYTE_ROUNDTRIPPABLE_COUNT); } /** * Tests that round tripping of all two byte permutations work. */ - public void testIsValidUtf8_2Bytes() throws UnsupportedEncodingException { - IsValidUtf8TestUtil.testBytes(2, - IsValidUtf8TestUtil.EXPECTED_TWO_BYTE_ROUNDTRIPPABLE_COUNT); + public void testIsValidUtf8_2Bytes() { + testBytes(LITERAL_FACTORY, 2, IsValidUtf8TestUtil.EXPECTED_TWO_BYTE_ROUNDTRIPPABLE_COUNT); + testBytes(HEAP_NIO_FACTORY, 2, IsValidUtf8TestUtil.EXPECTED_TWO_BYTE_ROUNDTRIPPABLE_COUNT); + testBytes(DIRECT_NIO_FACTORY, 2, IsValidUtf8TestUtil.EXPECTED_TWO_BYTE_ROUNDTRIPPABLE_COUNT); } /** * Tests that round tripping of all three byte permutations work. */ - public void testIsValidUtf8_3Bytes() throws UnsupportedEncodingException { + public void testIsValidUtf8_3Bytes() { // Travis' OOM killer doesn't like this test if (System.getenv("TRAVIS") == null) { - IsValidUtf8TestUtil.testBytes(3, - IsValidUtf8TestUtil.EXPECTED_THREE_BYTE_ROUNDTRIPPABLE_COUNT); + testBytes(LITERAL_FACTORY, 3, EXPECTED_THREE_BYTE_ROUNDTRIPPABLE_COUNT); + testBytes(HEAP_NIO_FACTORY, 3, EXPECTED_THREE_BYTE_ROUNDTRIPPABLE_COUNT); + testBytes(DIRECT_NIO_FACTORY, 3, EXPECTED_THREE_BYTE_ROUNDTRIPPABLE_COUNT); } } @@ -85,8 +93,7 @@ public class IsValidUtf8Test extends TestCase { * {@link IsValidUtf8FourByteTest} is used for full coverage. This method * tests specific four-byte cases. */ - public void testIsValidUtf8_4BytesSamples() - throws UnsupportedEncodingException { + public void testIsValidUtf8_4BytesSamples() { // Valid 4 byte. assertValidUtf8(0xF0, 0xA4, 0xAD, 0xA2); @@ -119,9 +126,7 @@ public class IsValidUtf8Test extends TestCase { assertTrue(asBytes("\u024B62\u024B62").isValidUtf8()); // Mixed string - assertTrue( - asBytes("a\u020ac\u00a2b\\u024B62u020acc\u00a2de\u024B62") - .isValidUtf8()); + assertTrue(asBytes("a\u020ac\u00a2b\\u024B62u020acc\u00a2de\u024B62").isValidUtf8()); // Not a valid string assertInvalidUtf8(-1, 0, -1, 0); @@ -135,36 +140,35 @@ public class IsValidUtf8Test extends TestCase { return realBytes; } - private ByteString toByteString(int... bytes) { - return ByteString.copyFrom(toByteArray(bytes)); - } - - private void assertValidUtf8(int[] bytes, boolean not) { + private void assertValidUtf8(ByteStringFactory factory, int[] bytes, boolean not) { byte[] realBytes = toByteArray(bytes); assertTrue(not ^ Utf8.isValidUtf8(realBytes)); assertTrue(not ^ Utf8.isValidUtf8(realBytes, 0, bytes.length)); - ByteString lit = ByteString.copyFrom(realBytes); - ByteString sub = lit.substring(0, bytes.length); - assertTrue(not ^ lit.isValidUtf8()); + ByteString leaf = factory.newByteString(realBytes); + ByteString sub = leaf.substring(0, bytes.length); + assertTrue(not ^ leaf.isValidUtf8()); assertTrue(not ^ sub.isValidUtf8()); ByteString[] ropes = { - RopeByteString.newInstanceForTest(ByteString.EMPTY, lit), - RopeByteString.newInstanceForTest(ByteString.EMPTY, sub), - RopeByteString.newInstanceForTest(lit, ByteString.EMPTY), - RopeByteString.newInstanceForTest(sub, ByteString.EMPTY), - RopeByteString.newInstanceForTest(sub, lit) - }; + RopeByteString.newInstanceForTest(ByteString.EMPTY, leaf), + RopeByteString.newInstanceForTest(ByteString.EMPTY, sub), + RopeByteString.newInstanceForTest(leaf, ByteString.EMPTY), + RopeByteString.newInstanceForTest(sub, ByteString.EMPTY), + RopeByteString.newInstanceForTest(sub, leaf)}; for (ByteString rope : ropes) { assertTrue(not ^ rope.isValidUtf8()); } } private void assertValidUtf8(int... bytes) { - assertValidUtf8(bytes, false); + assertValidUtf8(LITERAL_FACTORY, bytes, false); + assertValidUtf8(HEAP_NIO_FACTORY, bytes, false); + assertValidUtf8(DIRECT_NIO_FACTORY, bytes, false); } private void assertInvalidUtf8(int... bytes) { - assertValidUtf8(bytes, true); + assertValidUtf8(LITERAL_FACTORY, bytes, true); + assertValidUtf8(HEAP_NIO_FACTORY, bytes, true); + assertValidUtf8(DIRECT_NIO_FACTORY, bytes, true); } private static ByteString asBytes(String s) { @@ -177,7 +181,6 @@ public class IsValidUtf8Test extends TestCase { for (Shard shard : IsValidUtf8TestUtil.FOUR_BYTE_SHARDS) { actual += shard.expected; } - assertEquals(IsValidUtf8TestUtil.EXPECTED_FOUR_BYTE_ROUNDTRIPPABLE_COUNT, - actual); + assertEquals(IsValidUtf8TestUtil.EXPECTED_FOUR_BYTE_ROUNDTRIPPABLE_COUNT, actual); } } diff --git a/java/src/test/java/com/google/protobuf/IsValidUtf8TestUtil.java b/java/core/src/test/java/com/google/protobuf/IsValidUtf8TestUtil.java index 321669f3..16a808bf 100644 --- a/java/src/test/java/com/google/protobuf/IsValidUtf8TestUtil.java +++ b/java/core/src/test/java/com/google/protobuf/IsValidUtf8TestUtil.java @@ -30,9 +30,13 @@ package com.google.protobuf; -import static junit.framework.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; -import java.io.UnsupportedEncodingException; +import java.lang.ref.SoftReference; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharsetDecoder; @@ -52,64 +56,105 @@ import java.util.logging.Logger; * @author jonp@google.com (Jon Perlow) * @author martinrb@google.com (Martin Buchholz) */ -class IsValidUtf8TestUtil { - private static Logger logger = Logger.getLogger( - IsValidUtf8TestUtil.class.getName()); +final class IsValidUtf8TestUtil { + private static Logger logger = Logger.getLogger(IsValidUtf8TestUtil.class.getName()); + + private IsValidUtf8TestUtil() {} + + static interface ByteStringFactory { + ByteString newByteString(byte[] bytes); + } + + static final ByteStringFactory LITERAL_FACTORY = new ByteStringFactory() { + @Override + public ByteString newByteString(byte[] bytes) { + return ByteString.wrap(bytes); + } + }; + + static final ByteStringFactory HEAP_NIO_FACTORY = new ByteStringFactory() { + @Override + public ByteString newByteString(byte[] bytes) { + return new NioByteString(ByteBuffer.wrap(bytes)); + } + }; + + private static ThreadLocal<SoftReference<ByteBuffer>> directBuffer = + new ThreadLocal<SoftReference<ByteBuffer>>(); + + /** + * Factory for direct {@link ByteBuffer} instances. To reduce direct memory usage, this + * uses a thread local direct buffer. This means that each call will overwrite the buffer's + * contents from the previous call, so the calling code must be careful not to continue using + * a buffer returned from a previous invocation. + */ + static final ByteStringFactory DIRECT_NIO_FACTORY = new ByteStringFactory() { + @Override + public ByteString newByteString(byte[] bytes) { + SoftReference<ByteBuffer> ref = directBuffer.get(); + ByteBuffer buffer = ref == null ? null : ref.get(); + if (buffer == null || buffer.capacity() < bytes.length) { + buffer = ByteBuffer.allocateDirect(bytes.length); + directBuffer.set(new SoftReference<ByteBuffer>(buffer)); + } + buffer.clear(); + buffer.put(bytes); + buffer.flip(); + return new NioByteString(buffer); + } + }; // 128 - [chars 0x0000 to 0x007f] - static long ONE_BYTE_ROUNDTRIPPABLE_CHARACTERS = 0x007f - 0x0000 + 1; + static final long ONE_BYTE_ROUNDTRIPPABLE_CHARACTERS = 0x007f - 0x0000 + 1; // 128 - static long EXPECTED_ONE_BYTE_ROUNDTRIPPABLE_COUNT = - ONE_BYTE_ROUNDTRIPPABLE_CHARACTERS; + static final long EXPECTED_ONE_BYTE_ROUNDTRIPPABLE_COUNT = ONE_BYTE_ROUNDTRIPPABLE_CHARACTERS; // 1920 [chars 0x0080 to 0x07FF] - static long TWO_BYTE_ROUNDTRIPPABLE_CHARACTERS = 0x07FF - 0x0080 + 1; + static final long TWO_BYTE_ROUNDTRIPPABLE_CHARACTERS = 0x07FF - 0x0080 + 1; // 18,304 - static long EXPECTED_TWO_BYTE_ROUNDTRIPPABLE_COUNT = + static final long EXPECTED_TWO_BYTE_ROUNDTRIPPABLE_COUNT = // Both bytes are one byte characters (long) Math.pow(EXPECTED_ONE_BYTE_ROUNDTRIPPABLE_COUNT, 2) + // The possible number of two byte characters TWO_BYTE_ROUNDTRIPPABLE_CHARACTERS; // 2048 - static long THREE_BYTE_SURROGATES = 2 * 1024; + static final long THREE_BYTE_SURROGATES = 2 * 1024; // 61,440 [chars 0x0800 to 0xFFFF, minus surrogates] - static long THREE_BYTE_ROUNDTRIPPABLE_CHARACTERS = + static final long THREE_BYTE_ROUNDTRIPPABLE_CHARACTERS = 0xFFFF - 0x0800 + 1 - THREE_BYTE_SURROGATES; // 2,650,112 - static long EXPECTED_THREE_BYTE_ROUNDTRIPPABLE_COUNT = + static final long EXPECTED_THREE_BYTE_ROUNDTRIPPABLE_COUNT = // All one byte characters (long) Math.pow(EXPECTED_ONE_BYTE_ROUNDTRIPPABLE_COUNT, 3) + // One two byte character and a one byte character - 2 * TWO_BYTE_ROUNDTRIPPABLE_CHARACTERS * - ONE_BYTE_ROUNDTRIPPABLE_CHARACTERS + - // Three byte characters + 2 * TWO_BYTE_ROUNDTRIPPABLE_CHARACTERS * ONE_BYTE_ROUNDTRIPPABLE_CHARACTERS + + // Three byte characters THREE_BYTE_ROUNDTRIPPABLE_CHARACTERS; // 1,048,576 [chars 0x10000L to 0x10FFFF] - static long FOUR_BYTE_ROUNDTRIPPABLE_CHARACTERS = 0x10FFFF - 0x10000L + 1; + static final long FOUR_BYTE_ROUNDTRIPPABLE_CHARACTERS = 0x10FFFF - 0x10000L + 1; // 289,571,839 - static long EXPECTED_FOUR_BYTE_ROUNDTRIPPABLE_COUNT = + static final long EXPECTED_FOUR_BYTE_ROUNDTRIPPABLE_COUNT = // All one byte characters (long) Math.pow(EXPECTED_ONE_BYTE_ROUNDTRIPPABLE_COUNT, 4) + // One and three byte characters - 2 * THREE_BYTE_ROUNDTRIPPABLE_CHARACTERS * - ONE_BYTE_ROUNDTRIPPABLE_CHARACTERS + + 2 * THREE_BYTE_ROUNDTRIPPABLE_CHARACTERS * ONE_BYTE_ROUNDTRIPPABLE_CHARACTERS + // Two two byte characters TWO_BYTE_ROUNDTRIPPABLE_CHARACTERS * TWO_BYTE_ROUNDTRIPPABLE_CHARACTERS + // Permutations of one and two byte characters - 3 * TWO_BYTE_ROUNDTRIPPABLE_CHARACTERS * - ONE_BYTE_ROUNDTRIPPABLE_CHARACTERS * - ONE_BYTE_ROUNDTRIPPABLE_CHARACTERS + + 3 * TWO_BYTE_ROUNDTRIPPABLE_CHARACTERS * ONE_BYTE_ROUNDTRIPPABLE_CHARACTERS + * ONE_BYTE_ROUNDTRIPPABLE_CHARACTERS + + // Four byte characters FOUR_BYTE_ROUNDTRIPPABLE_CHARACTERS; - static class Shard { + static final class Shard { final long index; final long start; final long lim; @@ -138,7 +183,7 @@ class IsValidUtf8TestUtil { // 97-111 are all 2342912 for (int i = 97; i <= 111; i++) { - expected[i] = 2342912; + expected[i] = 2342912; } // 113-117 are all 1048576 @@ -158,22 +203,18 @@ class IsValidUtf8TestUtil { return expected; } - static final List<Shard> FOUR_BYTE_SHARDS = generateFourByteShards( - 128, FOUR_BYTE_SHARDS_EXPECTED_ROUNTRIPPABLES); + static final List<Shard> FOUR_BYTE_SHARDS = + generateFourByteShards(128, FOUR_BYTE_SHARDS_EXPECTED_ROUNTRIPPABLES); - private static List<Shard> generateFourByteShards( - int numShards, long[] expected) { + private static List<Shard> generateFourByteShards(int numShards, long[] expected) { assertEquals(numShards, expected.length); List<Shard> shards = new ArrayList<Shard>(numShards); long LIM = 1L << 32; long increment = LIM / numShards; assertTrue(LIM % numShards == 0); for (int i = 0; i < numShards; i++) { - shards.add(new Shard(i, - increment * i, - increment * (i + 1), - expected[i])); + shards.add(new Shard(i, increment * i, increment * (i + 1), expected[i])); } return shards; } @@ -182,12 +223,12 @@ class IsValidUtf8TestUtil { * Helper to run the loop to test all the permutations for the number of bytes * specified. * + * @param factory the factory for {@link ByteString} instances. * @param numBytes the number of bytes in the byte array * @param expectedCount the expected number of roundtrippable permutations */ - static void testBytes(int numBytes, long expectedCount) - throws UnsupportedEncodingException { - testBytes(numBytes, expectedCount, 0, -1); + static void testBytes(ByteStringFactory factory, int numBytes, long expectedCount) { + testBytes(factory, numBytes, expectedCount, 0, -1); } /** @@ -195,14 +236,15 @@ class IsValidUtf8TestUtil { * specified. This overload is useful for debugging to get the loop to start * at a certain character. * + * @param factory the factory for {@link ByteString} instances. * @param numBytes the number of bytes in the byte array * @param expectedCount the expected number of roundtrippable permutations * @param start the starting bytes encoded as a long as big-endian * @param lim the limit of bytes to process encoded as a long as big-endian, * or -1 to mean the max limit for numBytes */ - static void testBytes(int numBytes, long expectedCount, long start, long lim) - throws UnsupportedEncodingException { + static void testBytes( + ByteStringFactory factory, int numBytes, long expectedCount, long start, long lim) { Random rnd = new Random(); byte[] bytes = new byte[numBytes]; @@ -217,7 +259,7 @@ class IsValidUtf8TestUtil { bytes[bytes.length - i - 1] = (byte) tmpByteChar; tmpByteChar = tmpByteChar >> 8; } - ByteString bs = ByteString.copyFrom(bytes); + ByteString bs = factory.newByteString(bytes); boolean isRoundTrippable = bs.isValidUtf8(); String s = new String(bytes, Internal.UTF_8); byte[] bytesReencoded = s.getBytes(Internal.UTF_8); @@ -236,14 +278,15 @@ class IsValidUtf8TestUtil { int i = rnd.nextInt(numBytes); int j = rnd.nextInt(numBytes); if (j < i) { - int tmp = i; i = j; j = tmp; + int tmp = i; + i = j; + j = tmp; } int state1 = Utf8.partialIsValidUtf8(Utf8.COMPLETE, bytes, 0, i); int state2 = Utf8.partialIsValidUtf8(state1, bytes, i, j); int state3 = Utf8.partialIsValidUtf8(state2, bytes, j, numBytes); if (isRoundTrippable != (state3 == Utf8.COMPLETE)) { - System.out.printf("state=%04x %04x %04x i=%d j=%d%n", - state1, state2, state3, i, j); + System.out.printf("state=%04x %04x %04x i=%d j=%d%n", state1, state2, state3, i, j); outputFailure(byteChar, bytes, bytesReencoded); } assertEquals(isRoundTrippable, (state3 == Utf8.COMPLETE)); @@ -251,36 +294,24 @@ class IsValidUtf8TestUtil { // Test ropes built out of small partial sequences ByteString rope = RopeByteString.newInstanceForTest( bs.substring(0, i), - RopeByteString.newInstanceForTest( - bs.substring(i, j), - bs.substring(j, numBytes))); + RopeByteString.newInstanceForTest(bs.substring(i, j), bs.substring(j, numBytes))); assertSame(RopeByteString.class, rope.getClass()); - ByteString[] byteStrings = { bs, bs.substring(0, numBytes), rope }; + ByteString[] byteStrings = {bs, bs.substring(0, numBytes), rope}; for (ByteString x : byteStrings) { - assertEquals(isRoundTrippable, - x.isValidUtf8()); - assertEquals(state3, - x.partialIsValidUtf8(Utf8.COMPLETE, 0, numBytes)); - - assertEquals(state1, - x.partialIsValidUtf8(Utf8.COMPLETE, 0, i)); - assertEquals(state1, - x.substring(0, i).partialIsValidUtf8(Utf8.COMPLETE, 0, i)); - assertEquals(state2, - x.partialIsValidUtf8(state1, i, j - i)); - assertEquals(state2, - x.substring(i, j).partialIsValidUtf8(state1, 0, j - i)); - assertEquals(state3, - x.partialIsValidUtf8(state2, j, numBytes - j)); - assertEquals(state3, - x.substring(j, numBytes) - .partialIsValidUtf8(state2, 0, numBytes - j)); + assertEquals(isRoundTrippable, x.isValidUtf8()); + assertEquals(state3, x.partialIsValidUtf8(Utf8.COMPLETE, 0, numBytes)); + + assertEquals(state1, x.partialIsValidUtf8(Utf8.COMPLETE, 0, i)); + assertEquals(state1, x.substring(0, i).partialIsValidUtf8(Utf8.COMPLETE, 0, i)); + assertEquals(state2, x.partialIsValidUtf8(state1, i, j - i)); + assertEquals(state2, x.substring(i, j).partialIsValidUtf8(state1, 0, j - i)); + assertEquals(state3, x.partialIsValidUtf8(state2, j, numBytes - j)); + assertEquals(state3, x.substring(j, numBytes).partialIsValidUtf8(state2, 0, numBytes - j)); } // ByteString reduplication should not affect its UTF-8 validity. - ByteString ropeADope = - RopeByteString.newInstanceForTest(bs, bs.substring(0, numBytes)); + ByteString ropeADope = RopeByteString.newInstanceForTest(bs, bs.substring(0, numBytes)); assertEquals(isRoundTrippable, ropeADope.isValidUtf8()); if (isRoundTrippable) { @@ -288,8 +319,7 @@ class IsValidUtf8TestUtil { } count++; if (byteChar != 0 && byteChar % 1000000L == 0) { - logger.info("Processed " + (byteChar / 1000000L) + - " million characters"); + logger.info("Processed " + (byteChar / 1000000L) + " million characters"); } } logger.info("Round tripped " + countRoundTripped + " of " + count); @@ -303,25 +333,26 @@ class IsValidUtf8TestUtil { * actual String class, it's possible for incompatibilities to develop * (although unlikely). * + * @param factory the factory for {@link ByteString} instances. * @param numBytes the number of bytes in the byte array * @param expectedCount the expected number of roundtrippable permutations * @param start the starting bytes encoded as a long as big-endian * @param lim the limit of bytes to process encoded as a long as big-endian, * or -1 to mean the max limit for numBytes */ - void testBytesUsingByteBuffers( - int numBytes, long expectedCount, long start, long lim) - throws UnsupportedEncodingException { - CharsetDecoder decoder = Internal.UTF_8.newDecoder() - .onMalformedInput(CodingErrorAction.REPLACE) - .onUnmappableCharacter(CodingErrorAction.REPLACE); - CharsetEncoder encoder = Internal.UTF_8.newEncoder() - .onMalformedInput(CodingErrorAction.REPLACE) - .onUnmappableCharacter(CodingErrorAction.REPLACE); + static void testBytesUsingByteBuffers( + ByteStringFactory factory, int numBytes, long expectedCount, long start, long lim) { + CharsetDecoder decoder = + Internal.UTF_8.newDecoder() + .onMalformedInput(CodingErrorAction.REPLACE) + .onUnmappableCharacter(CodingErrorAction.REPLACE); + CharsetEncoder encoder = + Internal.UTF_8.newEncoder() + .onMalformedInput(CodingErrorAction.REPLACE) + .onUnmappableCharacter(CodingErrorAction.REPLACE); byte[] bytes = new byte[numBytes]; int maxChars = (int) (decoder.maxCharsPerByte() * numBytes) + 1; - char[] charsDecoded = - new char[(int) (decoder.maxCharsPerByte() * numBytes) + 1]; + char[] charsDecoded = new char[(int) (decoder.maxCharsPerByte() * numBytes) + 1]; int maxBytes = (int) (encoder.maxBytesPerChar() * maxChars) + 1; byte[] bytesReencoded = new byte[maxBytes]; @@ -347,7 +378,7 @@ class IsValidUtf8TestUtil { bytes[bytes.length - i - 1] = (byte) tmpByteChar; tmpByteChar = tmpByteChar >> 8; } - boolean isRoundTrippable = ByteString.copyFrom(bytes).isValidUtf8(); + boolean isRoundTrippable = factory.newByteString(bytes).isValidUtf8(); CoderResult result = decoder.decode(bb, cb, true); assertFalse(result.isError()); result = decoder.flush(cb); @@ -382,8 +413,7 @@ class IsValidUtf8TestUtil { countRoundTripped++; } if (byteChar != 0 && byteChar % 1000000 == 0) { - logger.info("Processed " + (byteChar / 1000000) + - " million characters"); + logger.info("Processed " + (byteChar / 1000000) + " million characters"); } } logger.info("Round tripped " + countRoundTripped + " of " + count); @@ -394,10 +424,9 @@ class IsValidUtf8TestUtil { outputFailure(byteChar, bytes, after, after.length); } - private static void outputFailure(long byteChar, byte[] bytes, byte[] after, - int len) { - fail("Failure: (" + Long.toHexString(byteChar) + ") " + - toHexString(bytes) + " => " + toHexString(after, len)); + private static void outputFailure(long byteChar, byte[] bytes, byte[] after, int len) { + fail("Failure: (" + Long.toHexString(byteChar) + ") " + toHexString(bytes) + " => " + + toHexString(after, len)); } private static String toHexString(byte[] b) { @@ -416,5 +445,4 @@ class IsValidUtf8TestUtil { s.append("\""); return s.toString(); } - } diff --git a/java/src/test/java/com/google/protobuf/LazyFieldLiteTest.java b/java/core/src/test/java/com/google/protobuf/LazyFieldLiteTest.java index 211b5697..8d1de6dc 100644 --- a/java/src/test/java/com/google/protobuf/LazyFieldLiteTest.java +++ b/java/core/src/test/java/com/google/protobuf/LazyFieldLiteTest.java @@ -36,9 +36,10 @@ import static protobuf_unittest.UnittestProto.optionalInt64Extension; import protobuf_unittest.UnittestProto.TestAllExtensions; import protobuf_unittest.UnittestProto.TestAllTypes; -import java.io.IOException; import junit.framework.TestCase; +import java.io.IOException; + /** * Unit test for {@link LazyFieldLite}. * diff --git a/java/src/test/java/com/google/protobuf/LazyFieldTest.java b/java/core/src/test/java/com/google/protobuf/LazyFieldTest.java index 2b900065..ce473ae9 100644 --- a/java/src/test/java/com/google/protobuf/LazyFieldTest.java +++ b/java/core/src/test/java/com/google/protobuf/LazyFieldTest.java @@ -33,9 +33,10 @@ package com.google.protobuf; import protobuf_unittest.UnittestProto.TestAllExtensions; import protobuf_unittest.UnittestProto.TestAllTypes; -import java.io.IOException; import junit.framework.TestCase; +import java.io.IOException; + /** * Unit test for {@link LazyField}. * diff --git a/java/src/test/java/com/google/protobuf/LazyMessageLiteTest.java b/java/core/src/test/java/com/google/protobuf/LazyMessageLiteTest.java index afe0fffd..afe0fffd 100644 --- a/java/src/test/java/com/google/protobuf/LazyMessageLiteTest.java +++ b/java/core/src/test/java/com/google/protobuf/LazyMessageLiteTest.java diff --git a/java/src/test/java/com/google/protobuf/LazyStringArrayListTest.java b/java/core/src/test/java/com/google/protobuf/LazyStringArrayListTest.java index 0f42ac50..0f42ac50 100644 --- a/java/src/test/java/com/google/protobuf/LazyStringArrayListTest.java +++ b/java/core/src/test/java/com/google/protobuf/LazyStringArrayListTest.java diff --git a/java/src/test/java/com/google/protobuf/LazyStringEndToEndTest.java b/java/core/src/test/java/com/google/protobuf/LazyStringEndToEndTest.java index 0ef414aa..0ef414aa 100644 --- a/java/src/test/java/com/google/protobuf/LazyStringEndToEndTest.java +++ b/java/core/src/test/java/com/google/protobuf/LazyStringEndToEndTest.java diff --git a/java/src/test/java/com/google/protobuf/LiteEqualsAndHashTest.java b/java/core/src/test/java/com/google/protobuf/LiteEqualsAndHashTest.java index 035917c8..29e8d875 100644 --- a/java/src/test/java/com/google/protobuf/LiteEqualsAndHashTest.java +++ b/java/core/src/test/java/com/google/protobuf/LiteEqualsAndHashTest.java @@ -33,6 +33,8 @@ 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 protobuf_unittest.lite_equals_and_hash.LiteEqualsAndHash.TestOneofEquals; +import protobuf_unittest.lite_equals_and_hash.LiteEqualsAndHash.TestRecursiveOneof; import junit.framework.TestCase; @@ -83,6 +85,16 @@ public class LiteEqualsAndHashTest extends TestCase { assertFalse(bar.equals(barPrime)); } + public void testOneofEquals() throws Exception { + TestOneofEquals.Builder builder = TestOneofEquals.newBuilder(); + TestOneofEquals message1 = builder.build(); + // Set message2's name field to default value. The two messages should be different when we + // check with the oneof case. + builder.setName(""); + TestOneofEquals message2 = builder.build(); + assertFalse(message1.equals(message2)); + } + public void testEqualsAndHashCodeWithUnknownFields() throws InvalidProtocolBufferException { Foo fooWithOnlyValue = Foo.newBuilder() .setValue(1) @@ -105,4 +117,9 @@ public class LiteEqualsAndHashTest extends TestCase { assertFalse(o1.equals(o2)); assertFalse(o1.hashCode() == o2.hashCode()); } + + public void testRecursiveHashcode() { + // This tests that we don't infinite loop. + TestRecursiveOneof.getDefaultInstance().hashCode(); + } } diff --git a/java/src/test/java/com/google/protobuf/LiteTest.java b/java/core/src/test/java/com/google/protobuf/LiteTest.java index b1f298ff..88c3e0b2 100644 --- a/java/src/test/java/com/google/protobuf/LiteTest.java +++ b/java/core/src/test/java/com/google/protobuf/LiteTest.java @@ -33,22 +33,31 @@ package com.google.protobuf; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; +import com.google.protobuf.UnittestImportLite.ImportEnumLite; +import com.google.protobuf.UnittestImportPublicLite.PublicImportMessageLite; import com.google.protobuf.UnittestLite; import com.google.protobuf.UnittestLite.ForeignEnumLite; import com.google.protobuf.UnittestLite.ForeignMessageLite; import com.google.protobuf.UnittestLite.TestAllExtensionsLite; import com.google.protobuf.UnittestLite.TestAllTypesLite; +import com.google.protobuf.UnittestLite.TestAllTypesLite.NestedEnum; import com.google.protobuf.UnittestLite.TestAllTypesLite.NestedMessage; import com.google.protobuf.UnittestLite.TestAllTypesLite.OneofFieldCase; import com.google.protobuf.UnittestLite.TestAllTypesLite.OptionalGroup; import com.google.protobuf.UnittestLite.TestAllTypesLite.RepeatedGroup; import com.google.protobuf.UnittestLite.TestAllTypesLiteOrBuilder; import com.google.protobuf.UnittestLite.TestNestedExtensionLite; +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 protobuf_unittest.lite_equals_and_hash.LiteEqualsAndHash.TestOneofEquals; +import protobuf_unittest.lite_equals_and_hash.LiteEqualsAndHash.TestRecursiveOneof; import junit.framework.TestCase; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.NotSerializableException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; @@ -58,6 +67,7 @@ import java.io.ObjectOutputStream; * @author kenton@google.com Kenton Varda */ public class LiteTest extends TestCase { + @Override public void setUp() throws Exception { // Test that nested extensions are initialized correctly even if the outer // class has not been accessed directly. This was once a bug with lite @@ -128,33 +138,7 @@ public class LiteTest extends TestCase { assertEquals(7, message2.getExtension( UnittestLite.optionalNestedMessageExtensionLite).getBb()); } - - public void testSerialize() throws Exception { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - TestAllTypesLite expected = - TestAllTypesLite.newBuilder() - .setOptionalInt32(123) - .addRepeatedString("hello") - .setOptionalNestedMessage( - TestAllTypesLite.NestedMessage.newBuilder().setBb(7)) - .build(); - ObjectOutputStream out = new ObjectOutputStream(baos); - try { - out.writeObject(expected); - } finally { - out.close(); - } - ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); - ObjectInputStream in = new ObjectInputStream(bais); - TestAllTypesLite actual = (TestAllTypesLite) in.readObject(); - assertEquals(expected.getOptionalInt32(), actual.getOptionalInt32()); - assertEquals(expected.getRepeatedStringCount(), - actual.getRepeatedStringCount()); - assertEquals(expected.getRepeatedString(0), - actual.getRepeatedString(0)); - assertEquals(expected.getOptionalNestedMessage().getBb(), - actual.getOptionalNestedMessage().getBb()); - } + public void testClone() { TestAllTypesLite.Builder expected = TestAllTypesLite.newBuilder() @@ -327,11 +311,9 @@ public class LiteTest extends TestCase { assertEquals( ForeignMessageLite.getDefaultInstance(), message.getOptionalForeignMessage()); - // LITE_RUNTIME doesn't implement equals so we compare on a property and - // ensure the property isn't set on foreignMessage. - assertEquals(3, builder.getOptionalForeignMessage().getC()); + assertEquals(foreignMessageBuilder.build(), builder.getOptionalForeignMessage()); messageAfterBuild = builder.build(); - assertEquals(3, messageAfterBuild.getOptionalForeignMessage().getC()); + assertEquals(foreignMessageBuilder.build(), messageAfterBuild.getOptionalForeignMessage()); assertEquals( ForeignMessageLite.getDefaultInstance(), message.getOptionalForeignMessage()); @@ -339,7 +321,7 @@ public class LiteTest extends TestCase { assertEquals( ForeignMessageLite.getDefaultInstance(), builder.getOptionalForeignMessage()); - assertEquals(3, messageAfterBuild.getOptionalForeignMessage().getC()); + assertEquals(foreignMessageBuilder.build(), messageAfterBuild.getOptionalForeignMessage()); message = builder.build(); OptionalGroup optionalGroup = OptionalGroup.newBuilder() @@ -364,17 +346,15 @@ public class LiteTest extends TestCase { builder.setOptionalGroup(optionalGroupBuilder); assertEquals( OptionalGroup.getDefaultInstance(), message.getOptionalGroup()); - // LITE_RUNTIME doesn't implement equals so we compare on a property and - // ensure the property isn't set on optionalGroup. - assertEquals(3, builder.getOptionalGroup().getA()); + assertEquals(optionalGroupBuilder.build(), builder.getOptionalGroup()); messageAfterBuild = builder.build(); - assertEquals(3, messageAfterBuild.getOptionalGroup().getA()); + assertEquals(optionalGroupBuilder.build(), messageAfterBuild.getOptionalGroup()); assertEquals( OptionalGroup.getDefaultInstance(), message.getOptionalGroup()); builder.clearOptionalGroup(); assertEquals( OptionalGroup.getDefaultInstance(), builder.getOptionalGroup()); - assertEquals(3, messageAfterBuild.getOptionalGroup().getA()); + assertEquals(optionalGroupBuilder.build(), messageAfterBuild.getOptionalGroup()); message = builder.build(); builder.setOptionalInt32(1); @@ -425,17 +405,16 @@ public class LiteTest extends TestCase { assertEquals( NestedMessage.getDefaultInstance(), message.getOptionalLazyMessage()); - // LITE_RUNTIME doesn't implement equals so we compare on a property. - assertEquals(3, builder.getOptionalLazyMessage().getBb()); + assertEquals(nestedMessageBuilder.build(), builder.getOptionalLazyMessage()); messageAfterBuild = builder.build(); - assertEquals(3, messageAfterBuild.getOptionalLazyMessage().getBb()); + assertEquals(nestedMessageBuilder.build(), messageAfterBuild.getOptionalLazyMessage()); assertEquals( NestedMessage.getDefaultInstance(), message.getOptionalLazyMessage()); builder.clearOptionalLazyMessage(); assertEquals( NestedMessage.getDefaultInstance(), builder.getOptionalLazyMessage()); - assertEquals(3, messageAfterBuild.getOptionalLazyMessage().getBb()); + assertEquals(nestedMessageBuilder.build(), messageAfterBuild.getOptionalLazyMessage()); message = builder.build(); builder.setOptionalSfixed32(1); @@ -1125,8 +1104,7 @@ public class LiteTest extends TestCase { assertEquals(0, message.getRepeatedForeignMessageCount()); builder.setRepeatedForeignMessage( 0, ForeignMessageLite.getDefaultInstance()); - // LITE_RUNTIME doesn't implement equals so we compare on a property. - assertEquals(3, messageAfterBuild.getRepeatedForeignMessage(0).getC()); + assertEquals(foreignMessageBuilder.build(), messageAfterBuild.getRepeatedForeignMessage(0)); assertEquals( ForeignMessageLite.getDefaultInstance(), builder.getRepeatedForeignMessage(0)); @@ -1139,8 +1117,7 @@ public class LiteTest extends TestCase { builder.setRepeatedForeignMessage(0, foreignMessageBuilder); assertEquals( foreignMessage, messageAfterBuild.getRepeatedForeignMessage(0)); - // LITE_RUNTIME doesn't implement equals so we compare on a property. - assertEquals(3, builder.getRepeatedForeignMessage(0).getC()); + assertEquals(foreignMessageBuilder.build(), builder.getRepeatedForeignMessage(0)); builder.clearRepeatedForeignMessage(); message = builder.build(); @@ -1173,9 +1150,7 @@ public class LiteTest extends TestCase { messageAfterBuild = builder.build(); assertEquals(0, message.getRepeatedGroupCount()); builder.setRepeatedGroup(0, RepeatedGroup.getDefaultInstance()); - // LITE_RUNTIME doesn't implement equals so we compare on a property and - // ensure the property isn't set on repeatedGroup. - assertEquals(3, messageAfterBuild.getRepeatedGroup(0).getA()); + assertEquals(repeatedGroupBuilder.build(), messageAfterBuild.getRepeatedGroup(0)); assertEquals( RepeatedGroup.getDefaultInstance(), builder.getRepeatedGroup(0)); builder.clearRepeatedGroup(); @@ -1185,9 +1160,7 @@ public class LiteTest extends TestCase { messageAfterBuild = builder.build(); assertEquals(0, message.getRepeatedGroupCount()); builder.setRepeatedGroup(0, RepeatedGroup.getDefaultInstance()); - // LITE_RUNTIME doesn't implement equals so we compare on a property and - // ensure the property isn't set on repeatedGroup. - assertEquals(3, messageAfterBuild.getRepeatedGroup(0).getA()); + assertEquals(repeatedGroupBuilder.build(), messageAfterBuild.getRepeatedGroup(0)); assertEquals( RepeatedGroup.getDefaultInstance(), builder.getRepeatedGroup(0)); builder.clearRepeatedGroup(); @@ -1235,9 +1208,7 @@ public class LiteTest extends TestCase { messageAfterBuild = builder.build(); assertEquals(0, message.getRepeatedLazyMessageCount()); builder.setRepeatedLazyMessage(0, NestedMessage.getDefaultInstance()); - // LITE_RUNTIME doesn't implement equals so we compare on a property and - // ensure the property isn't set on repeatedGroup. - assertEquals(3, messageAfterBuild.getRepeatedLazyMessage(0).getBb()); + assertEquals(nestedMessageBuilder.build(), messageAfterBuild.getRepeatedLazyMessage(0)); assertEquals( NestedMessage.getDefaultInstance(), builder.getRepeatedLazyMessage(0)); builder.clearRepeatedLazyMessage(); @@ -1247,9 +1218,7 @@ public class LiteTest extends TestCase { messageAfterBuild = builder.build(); assertEquals(0, message.getRepeatedLazyMessageCount()); builder.setRepeatedLazyMessage(0, NestedMessage.getDefaultInstance()); - // LITE_RUNTIME doesn't implement equals so we compare on a property and - // ensure the property isn't set on repeatedGroup. - assertEquals(3, messageAfterBuild.getRepeatedLazyMessage(0).getBb()); + assertEquals(nestedMessageBuilder.build(), messageAfterBuild.getRepeatedLazyMessage(0)); assertEquals( NestedMessage.getDefaultInstance(), builder.getRepeatedLazyMessage(0)); builder.clearRepeatedLazyMessage(); @@ -1459,4 +1428,802 @@ public class LiteTest extends TestCase { 11, (int) extendableMessage.getExtension( UnittestLite.optionalFixed32ExtensionLite)); } + + public void testToStringDefaultInstance() throws Exception { + assertToStringEquals("", TestAllTypesLite.getDefaultInstance()); + } + + public void testToStringPrimitives() throws Exception { + TestAllTypesLite proto = TestAllTypesLite.newBuilder() + .setOptionalInt32(1) + .setOptionalInt64(9223372036854775807L) + .build(); + assertToStringEquals("optional_int32: 1\noptional_int64: 9223372036854775807", proto); + + proto = TestAllTypesLite.newBuilder() + .setOptionalBool(true) + .setOptionalNestedEnum(TestAllTypesLite.NestedEnum.BAZ) + .build(); + assertToStringEquals("optional_bool: true\noptional_nested_enum: BAZ", proto); + + proto = TestAllTypesLite.newBuilder() + .setOptionalFloat(2.72f) + .setOptionalDouble(3.14) + .build(); + assertToStringEquals("optional_double: 3.14\noptional_float: 2.72", proto); + } + + public void testToStringStringFields() throws Exception { + TestAllTypesLite proto = TestAllTypesLite.newBuilder() + .setOptionalString("foo\"bar\nbaz\\") + .build(); + assertToStringEquals("optional_string: \"foo\\\"bar\\nbaz\\\\\"", proto); + + proto = TestAllTypesLite.newBuilder() + .setOptionalString("\u6587") + .build(); + assertToStringEquals("optional_string: \"\\346\\226\\207\"", proto); + } + + public void testToStringNestedMessage() throws Exception { + TestAllTypesLite proto = TestAllTypesLite.newBuilder() + .setOptionalNestedMessage(TestAllTypesLite.NestedMessage.getDefaultInstance()) + .build(); + assertToStringEquals("optional_nested_message {\n}", proto); + + proto = TestAllTypesLite.newBuilder() + .setOptionalNestedMessage( + TestAllTypesLite.NestedMessage.newBuilder().setBb(7)) + .build(); + assertToStringEquals("optional_nested_message {\n bb: 7\n}", proto); + } + + public void testToStringRepeatedFields() throws Exception { + TestAllTypesLite proto = TestAllTypesLite.newBuilder() + .addRepeatedInt32(32) + .addRepeatedInt32(32) + .addRepeatedInt64(64) + .build(); + assertToStringEquals("repeated_int32: 32\nrepeated_int32: 32\nrepeated_int64: 64", proto); + + proto = TestAllTypesLite.newBuilder() + .addRepeatedLazyMessage( + TestAllTypesLite.NestedMessage.newBuilder().setBb(7)) + .addRepeatedLazyMessage( + TestAllTypesLite.NestedMessage.newBuilder().setBb(8)) + .build(); + assertToStringEquals( + "repeated_lazy_message {\n bb: 7\n}\nrepeated_lazy_message {\n bb: 8\n}", + proto); + } + + public void testToStringForeignFields() throws Exception { + TestAllTypesLite proto = TestAllTypesLite.newBuilder() + .setOptionalForeignEnum(ForeignEnumLite.FOREIGN_LITE_BAR) + .setOptionalForeignMessage( + ForeignMessageLite.newBuilder() + .setC(3)) + .build(); + assertToStringEquals( + "optional_foreign_enum: FOREIGN_LITE_BAR\noptional_foreign_message {\n c: 3\n}", + proto); + } + + public void testToStringExtensions() throws Exception { + TestAllExtensionsLite message = TestAllExtensionsLite.newBuilder() + .setExtension(UnittestLite.optionalInt32ExtensionLite, 123) + .addExtension(UnittestLite.repeatedStringExtensionLite, "spam") + .addExtension(UnittestLite.repeatedStringExtensionLite, "eggs") + .setExtension(UnittestLite.optionalNestedEnumExtensionLite, + TestAllTypesLite.NestedEnum.BAZ) + .setExtension(UnittestLite.optionalNestedMessageExtensionLite, + TestAllTypesLite.NestedMessage.newBuilder().setBb(7).build()) + .build(); + assertToStringEquals( + "[1]: 123\n[18] {\n bb: 7\n}\n[21]: 3\n[44]: \"spam\"\n[44]: \"eggs\"", + message); + } + + public void testToStringUnknownFields() throws Exception { + TestAllExtensionsLite messageWithExtensions = TestAllExtensionsLite.newBuilder() + .setExtension(UnittestLite.optionalInt32ExtensionLite, 123) + .addExtension(UnittestLite.repeatedStringExtensionLite, "spam") + .addExtension(UnittestLite.repeatedStringExtensionLite, "eggs") + .setExtension(UnittestLite.optionalNestedEnumExtensionLite, + TestAllTypesLite.NestedEnum.BAZ) + .setExtension(UnittestLite.optionalNestedMessageExtensionLite, + TestAllTypesLite.NestedMessage.newBuilder().setBb(7).build()) + .build(); + TestAllExtensionsLite messageWithUnknownFields = TestAllExtensionsLite.parseFrom( + messageWithExtensions.toByteArray()); + assertToStringEquals( + "1: 123\n18: \"\\b\\a\"\n21: 3\n44: \"spam\"\n44: \"eggs\"", + messageWithUnknownFields); + } + + public void testToStringLazyMessage() throws Exception { + TestAllTypesLite message = TestAllTypesLite.newBuilder() + .setOptionalLazyMessage(NestedMessage.newBuilder().setBb(1).build()) + .build(); + assertToStringEquals("optional_lazy_message {\n bb: 1\n}", message); + } + + public void testToStringGroup() throws Exception { + TestAllTypesLite message = TestAllTypesLite.newBuilder() + .setOptionalGroup(OptionalGroup.newBuilder().setA(1).build()) + .build(); + assertToStringEquals("optional_group {\n a: 1\n}", message); + } + + public void testToStringOneof() throws Exception { + TestAllTypesLite message = TestAllTypesLite.newBuilder() + .setOneofString("hello") + .build(); + assertToStringEquals("oneof_string: \"hello\"", message); + } + + // Asserts that the toString() representation of the message matches the expected. This verifies + // the first line starts with a comment; but, does not factor in said comment as part of the + // comparison as it contains unstable addresses. + private static void assertToStringEquals(String expected, MessageLite message) { + String toString = message.toString(); + assertEquals('#', toString.charAt(0)); + if (toString.indexOf("\n") >= 0) { + toString = toString.substring(toString.indexOf("\n") + 1); + } else { + toString = ""; + } + assertEquals(expected, toString); + } + + public void testParseLazy() throws Exception { + ByteString bb = TestAllTypesLite.newBuilder() + .setOptionalLazyMessage(NestedMessage.newBuilder() + .setBb(11) + .build()) + .build().toByteString(); + ByteString cc = TestAllTypesLite.newBuilder() + .setOptionalLazyMessage(NestedMessage.newBuilder() + .setCc(22) + .build()) + .build().toByteString(); + + ByteString concat = bb.concat(cc); + TestAllTypesLite message = TestAllTypesLite.parseFrom(concat); + + assertEquals(11, message.getOptionalLazyMessage().getBb()); + assertEquals(22L, message.getOptionalLazyMessage().getCc()); + } + + public void testParseLazy_oneOf() throws Exception { + ByteString bb = TestAllTypesLite.newBuilder() + .setOneofLazyNestedMessage(NestedMessage.newBuilder() + .setBb(11) + .build()) + .build().toByteString(); + ByteString cc = TestAllTypesLite.newBuilder() + .setOneofLazyNestedMessage(NestedMessage.newBuilder() + .setCc(22) + .build()) + .build().toByteString(); + + ByteString concat = bb.concat(cc); + TestAllTypesLite message = TestAllTypesLite.parseFrom(concat); + + assertEquals(11, message.getOneofLazyNestedMessage().getBb()); + assertEquals(22L, message.getOneofLazyNestedMessage().getCc()); + } + + public void testMergeFromStream_repeatedField() throws Exception { + TestAllTypesLite.Builder builder = TestAllTypesLite.newBuilder() + .addRepeatedString("hello"); + builder.mergeFrom(CodedInputStream.newInstance(builder.build().toByteArray())); + + assertEquals(2, builder.getRepeatedStringCount()); + } + + public void testMergeFromStream_invalidBytes() throws Exception { + TestAllTypesLite.Builder builder = TestAllTypesLite.newBuilder() + .setDefaultBool(true); + try { + builder.mergeFrom(CodedInputStream.newInstance("Invalid bytes".getBytes(Internal.UTF_8))); + fail(); + } catch (InvalidProtocolBufferException expected) {} + } + + public void testMergeFrom_sanity() throws Exception { + TestAllTypesLite one = TestUtilLite.getAllLiteSetBuilder().build(); + byte[] bytes = one.toByteArray(); + TestAllTypesLite two = TestAllTypesLite.parseFrom(bytes); + + one = one.toBuilder().mergeFrom(one).build(); + two = two.toBuilder().mergeFrom(bytes).build(); + assertEquals(one, two); + assertEquals(two, one); + assertEquals(one.hashCode(), two.hashCode()); + } + + public void testEquals_notEqual() throws Exception { + TestAllTypesLite one = TestUtilLite.getAllLiteSetBuilder().build(); + byte[] bytes = one.toByteArray(); + TestAllTypesLite two = one.toBuilder().mergeFrom(one).mergeFrom(bytes).build(); + + assertFalse(one.equals(two)); + assertFalse(two.equals(one)); + + assertFalse(one.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(one)); + + TestAllTypesLite oneFieldSet = TestAllTypesLite.newBuilder() + .setDefaultBool(true) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setDefaultBytes(ByteString.EMPTY) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setDefaultCord("") + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setDefaultCordBytes(ByteString.EMPTY) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setDefaultDouble(0) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setDefaultFixed32(0) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setDefaultFixed64(0) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setDefaultFloat(0) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setDefaultForeignEnum(ForeignEnumLite.FOREIGN_LITE_BAR) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setDefaultImportEnum(ImportEnumLite.IMPORT_LITE_BAR) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setDefaultInt32(0) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setDefaultInt64(0) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setDefaultNestedEnum(NestedEnum.BAR) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setDefaultSfixed32(0) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setDefaultSfixed64(0) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setDefaultSint32(0) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setDefaultSint64(0) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setDefaultString("") + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setDefaultStringBytes(ByteString.EMPTY) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setDefaultStringPiece("") + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setDefaultStringPieceBytes(ByteString.EMPTY) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setDefaultUint32(0) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setDefaultUint64(0) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .addRepeatedBool(true) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .addRepeatedBytes(ByteString.EMPTY) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .addRepeatedCord("") + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .addRepeatedCordBytes(ByteString.EMPTY) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .addRepeatedDouble(0) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .addRepeatedFixed32(0) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .addRepeatedFixed64(0) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .addRepeatedFloat(0) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .addRepeatedForeignEnum(ForeignEnumLite.FOREIGN_LITE_BAR) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .addRepeatedImportEnum(ImportEnumLite.IMPORT_LITE_BAR) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .addRepeatedInt32(0) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .addRepeatedInt64(0) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .addRepeatedNestedEnum(NestedEnum.BAR) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .addRepeatedSfixed32(0) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .addRepeatedSfixed64(0) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .addRepeatedSint32(0) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .addRepeatedSint64(0) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .addRepeatedString("") + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .addRepeatedStringBytes(ByteString.EMPTY) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .addRepeatedStringPiece("") + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .addRepeatedStringPieceBytes(ByteString.EMPTY) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .addRepeatedUint32(0) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .addRepeatedUint64(0) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setOptionalBool(true) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setOptionalBytes(ByteString.EMPTY) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setOptionalCord("") + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setOptionalCordBytes(ByteString.EMPTY) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setOptionalDouble(0) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setOptionalFixed32(0) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setOptionalFixed64(0) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setOptionalFloat(0) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setOptionalForeignEnum(ForeignEnumLite.FOREIGN_LITE_BAR) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setOptionalImportEnum(ImportEnumLite.IMPORT_LITE_BAR) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setOptionalInt32(0) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setOptionalInt64(0) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setOptionalNestedEnum(NestedEnum.BAR) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setOptionalSfixed32(0) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setOptionalSfixed64(0) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setOptionalSint32(0) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setOptionalSint64(0) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setOptionalString("") + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setOptionalStringBytes(ByteString.EMPTY) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setOptionalStringPiece("") + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setOptionalStringPieceBytes(ByteString.EMPTY) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setOptionalUint32(0) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setOptionalUint64(0) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setOneofBytes(ByteString.EMPTY) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setOneofLazyNestedMessage(NestedMessage.getDefaultInstance()) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setOneofNestedMessage(NestedMessage.getDefaultInstance()) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setOneofString("") + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setOneofStringBytes(ByteString.EMPTY) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setOneofUint32(0) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setOptionalForeignMessage(ForeignMessageLite.getDefaultInstance()) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setOptionalGroup(OptionalGroup.getDefaultInstance()) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setOptionalPublicImportMessage(PublicImportMessageLite.getDefaultInstance()) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + + oneFieldSet = TestAllTypesLite.newBuilder() + .setOptionalLazyMessage(NestedMessage.getDefaultInstance()) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + oneFieldSet = TestAllTypesLite.newBuilder() + .addRepeatedLazyMessage(NestedMessage.getDefaultInstance()) + .build(); + assertFalse(oneFieldSet.equals(TestAllTypesLite.getDefaultInstance())); + assertFalse(TestAllTypesLite.getDefaultInstance().equals(oneFieldSet)); + } + + public void testEquals() throws Exception { + // 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)); + } + + public void testOneofEquals() throws Exception { + TestOneofEquals.Builder builder = TestOneofEquals.newBuilder(); + TestOneofEquals message1 = builder.build(); + // Set message2's name field to default value. The two messages should be different when we + // check with the oneof case. + builder.setName(""); + TestOneofEquals message2 = builder.build(); + assertFalse(message1.equals(message2)); + } + + public void testEquals_sanity() throws Exception { + TestAllTypesLite one = TestUtilLite.getAllLiteSetBuilder().build(); + TestAllTypesLite two = TestAllTypesLite.parseFrom(one.toByteArray()); + assertEquals(one, two); + assertEquals(one.hashCode(), two.hashCode()); + + assertEquals( + one.toBuilder().mergeFrom(two).build(), + two.toBuilder().mergeFrom(two.toByteArray()).build()); + } + + public void testEqualsAndHashCodeWithUnknownFields() throws InvalidProtocolBufferException { + Foo fooWithOnlyValue = Foo.newBuilder() + .setValue(1) + .build(); + + Foo fooWithValueAndExtension = fooWithOnlyValue.toBuilder() + .setValue(1) + .setExtension(Bar.fooExt, Bar.newBuilder() + .setName("name") + .build()) + .build(); + + Foo fooWithValueAndUnknownFields = Foo.parseFrom(fooWithValueAndExtension.toByteArray()); + + assertEqualsAndHashCodeAreFalse(fooWithOnlyValue, fooWithValueAndUnknownFields); + assertEqualsAndHashCodeAreFalse(fooWithValueAndExtension, fooWithValueAndUnknownFields); + } + + // Test to ensure we avoid a class cast exception with oneofs. + public void testEquals_oneOfMessages() { + TestAllTypesLite mine = TestAllTypesLite.newBuilder() + .setOneofString("Hello") + .build(); + + TestAllTypesLite other = TestAllTypesLite.newBuilder() + .setOneofNestedMessage(NestedMessage.getDefaultInstance()) + .build(); + + assertFalse(mine.equals(other)); + assertFalse(other.equals(mine)); + } + + private void assertEqualsAndHashCodeAreFalse(Object o1, Object o2) { + assertFalse(o1.equals(o2)); + assertFalse(o1.hashCode() == o2.hashCode()); + } + + public void testRecursiveHashcode() { + // This tests that we don't infinite loop. + TestRecursiveOneof.getDefaultInstance().hashCode(); + } } diff --git a/java/src/test/java/com/google/protobuf/LiteralByteStringTest.java b/java/core/src/test/java/com/google/protobuf/LiteralByteStringTest.java index 7dfda2ae..2e7792a8 100644 --- a/java/src/test/java/com/google/protobuf/LiteralByteStringTest.java +++ b/java/core/src/test/java/com/google/protobuf/LiteralByteStringTest.java @@ -47,9 +47,9 @@ import java.util.List; import java.util.NoSuchElementException; /** - * Test {@link LiteralByteString} by setting up a reference string in {@link #setUp()}. - * This class is designed to be extended for testing extensions of {@link LiteralByteString} - * such as {@link BoundedByteString}, see {@link BoundedByteStringTest}. + * Test {@code LiteralByteString} by setting up a reference string in {@link #setUp()}. + * This class is designed to be extended for testing extensions of {@code LiteralByteString} + * such as {@code BoundedByteString}, see {@link BoundedByteStringTest}. * * @author carlanton@google.com (Carl Haverl) */ @@ -75,9 +75,7 @@ public class LiteralByteStringTest extends TestCase { } protected String getActualClassName(Object object) { - String actualClassName = object.getClass().getName(); - actualClassName = actualClassName.substring(actualClassName.lastIndexOf('.') + 1); - return actualClassName; + return object.getClass().getSimpleName(); } public void testByteAt() { @@ -306,25 +304,75 @@ public class LiteralByteStringTest extends TestCase { Arrays.equals(referenceBytes, roundTripBytes)); } - public void testWriteTo_mutating() throws IOException { + public void testWriteToShouldNotExposeInternalBufferToOutputStream() throws IOException { OutputStream os = new OutputStream() { @Override public void write(byte[] b, int off, int len) { - for (int x = 0; x < len; ++x) { - b[off + x] = (byte) 0; - } + Arrays.fill(b, off, off + len, (byte) 0); } @Override public void write(int b) { - // Purposefully left blank. + throw new UnsupportedOperationException(); } }; stringUnderTest.writeTo(os); - byte[] newBytes = stringUnderTest.toByteArray(); assertTrue(classUnderTest + ".writeTo() must not grant access to underlying array", - Arrays.equals(referenceBytes, newBytes)); + Arrays.equals(referenceBytes, stringUnderTest.toByteArray())); + } + + public void testWriteToInternalShouldExposeInternalBufferToOutputStream() throws IOException { + OutputStream os = new OutputStream() { + @Override + public void write(byte[] b, int off, int len) { + Arrays.fill(b, off, off + len, (byte) 0); + } + + @Override + public void write(int b) { + throw new UnsupportedOperationException(); + } + }; + + stringUnderTest.writeToInternal(os, 0, stringUnderTest.size()); + byte[] allZeros = new byte[stringUnderTest.size()]; + assertTrue(classUnderTest + ".writeToInternal() must grant access to underlying array", + Arrays.equals(allZeros, stringUnderTest.toByteArray())); + } + + public void testWriteToShouldExposeInternalBufferToByteOutput() throws IOException { + ByteOutput out = new ByteOutput() { + @Override + public void write(byte value) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void write(byte[] value, int offset, int length) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void writeLazy(byte[] value, int offset, int length) throws IOException { + Arrays.fill(value, offset, offset + length, (byte) 0); + } + + @Override + public void write(ByteBuffer value) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void writeLazy(ByteBuffer value) throws IOException { + throw new UnsupportedOperationException(); + } + }; + + stringUnderTest.writeTo(out); + byte[] allZeros = new byte[stringUnderTest.size()]; + assertTrue(classUnderTest + ".writeToInternal() must grant access to underlying array", + Arrays.equals(allZeros, stringUnderTest.toByteArray())); } public void testNewOutput() throws IOException { @@ -350,14 +398,14 @@ public class LiteralByteStringTest extends TestCase { public void testToString() throws UnsupportedEncodingException { String testString = "I love unicode \u1234\u5678 characters"; - LiteralByteString unicode = new LiteralByteString(testString.getBytes(Internal.UTF_8)); + ByteString unicode = ByteString.wrap(testString.getBytes(Internal.UTF_8)); String roundTripString = unicode.toString(UTF_8); assertEquals(classUnderTest + " unicode must match", testString, roundTripString); } - public void testCharsetToString() throws UnsupportedEncodingException { + public void testCharsetToString() { String testString = "I love unicode \u1234\u5678 characters"; - LiteralByteString unicode = new LiteralByteString(testString.getBytes(Internal.UTF_8)); + ByteString unicode = ByteString.wrap(testString.getBytes(Internal.UTF_8)); String roundTripString = unicode.toString(Internal.UTF_8); assertEquals(classUnderTest + " unicode must match", testString, roundTripString); } @@ -365,7 +413,7 @@ public class LiteralByteStringTest extends TestCase { public void testToString_returnsCanonicalEmptyString() { assertSame(classUnderTest + " must be the same string references", ByteString.EMPTY.toString(Internal.UTF_8), - new LiteralByteString(new byte[]{}).toString(Internal.UTF_8)); + ByteString.wrap(new byte[]{}).toString(Internal.UTF_8)); } public void testToString_raisesException() { @@ -377,7 +425,7 @@ public class LiteralByteStringTest extends TestCase { } try { - new LiteralByteString(referenceBytes).toString("invalid"); + ByteString.wrap(referenceBytes).toString("invalid"); fail("Should have thrown an exception."); } catch (UnsupportedEncodingException expected) { // This is success @@ -390,15 +438,15 @@ public class LiteralByteStringTest extends TestCase { assertFalse(classUnderTest + " must not equal the empty string", stringUnderTest.equals(ByteString.EMPTY)); assertEquals(classUnderTest + " empty strings must be equal", - new LiteralByteString(new byte[]{}), stringUnderTest.substring(55, 55)); + ByteString.wrap(new byte[]{}), stringUnderTest.substring(55, 55)); assertEquals(classUnderTest + " must equal another string with the same value", - stringUnderTest, new LiteralByteString(referenceBytes)); + stringUnderTest, ByteString.wrap(referenceBytes)); byte[] mungedBytes = new byte[referenceBytes.length]; System.arraycopy(referenceBytes, 0, mungedBytes, 0, referenceBytes.length); mungedBytes[mungedBytes.length - 5] = (byte) (mungedBytes[mungedBytes.length - 5] ^ 0xFF); assertFalse(classUnderTest + " must not equal every string with the same length", - stringUnderTest.equals(new LiteralByteString(mungedBytes))); + stringUnderTest.equals(ByteString.wrap(mungedBytes))); } public void testHashCode() { diff --git a/java/src/test/java/com/google/protobuf/LongArrayListTest.java b/java/core/src/test/java/com/google/protobuf/LongArrayListTest.java index 3a52ec7f..0a8f9ed2 100644 --- a/java/src/test/java/com/google/protobuf/LongArrayListTest.java +++ b/java/core/src/test/java/com/google/protobuf/LongArrayListTest.java @@ -73,20 +73,6 @@ public class LongArrayListTest extends TestCase { assertImmutable(list); } - public void testCopyConstructor() { - LongArrayList copy = new LongArrayList(TERTIARY_LIST); - assertEquals(TERTIARY_LIST, copy); - - copy = new LongArrayList(LongArrayList.emptyList()); - assertEquals(LongArrayList.emptyList(), copy); - - copy = new LongArrayList(asList(1L, 2L, 3L)); - assertEquals(asList(1L, 2L, 3L), copy); - - copy = new LongArrayList(Collections.<Long>emptyList()); - assertEquals(LongArrayList.emptyList(), copy); - } - public void testModificationWithIteration() { list.addAll(asList(1L, 2L, 3L, 4L)); Iterator<Long> iterator = list.iterator(); @@ -310,7 +296,7 @@ public class LongArrayListTest extends TestCase { } private void assertImmutable(LongArrayList list) { - if (list.contains(1)) { + if (list.contains(1L)) { throw new RuntimeException("Cannot test the immutability of lists that contain 1."); } @@ -413,7 +399,7 @@ public class LongArrayListTest extends TestCase { } try { - list.removeAll(Collections.singleton(1)); + list.removeAll(Collections.singleton(1L)); fail(); } catch (UnsupportedOperationException e) { // expected @@ -434,7 +420,7 @@ public class LongArrayListTest extends TestCase { } try { - list.retainAll(Collections.singleton(1)); + list.retainAll(Collections.singleton(1L)); fail(); } catch (UnsupportedOperationException e) { // expected diff --git a/java/src/test/java/com/google/protobuf/MapForProto2LiteTest.java b/java/core/src/test/java/com/google/protobuf/MapForProto2LiteTest.java index 3d8c9bc4..d79d0029 100644 --- a/java/src/test/java/com/google/protobuf/MapForProto2LiteTest.java +++ b/java/core/src/test/java/com/google/protobuf/MapForProto2LiteTest.java @@ -432,7 +432,7 @@ public class MapForProto2LiteTest extends TestCase { assertEquals(1, messageWithUnknownEnums.getInt32ToInt32Field().get(1).intValue()); assertEquals(54321, messageWithUnknownEnums.getInt32ToInt32Field().get(2).intValue()); } - + public void testIterationOrder() throws Exception { TestMap.Builder builder = TestMap.newBuilder(); diff --git a/java/src/test/java/com/google/protobuf/MapForProto2Test.java b/java/core/src/test/java/com/google/protobuf/MapForProto2Test.java index 1fa3cbdb..73154c0f 100644 --- a/java/src/test/java/com/google/protobuf/MapForProto2Test.java +++ b/java/core/src/test/java/com/google/protobuf/MapForProto2Test.java @@ -36,7 +36,6 @@ import map_test.MapForProto2TestProto.TestMap.MessageValue; import map_test.MapForProto2TestProto.TestMap.MessageWithRequiredFields; import map_test.MapForProto2TestProto.TestRecursiveMap; import map_test.MapForProto2TestProto.TestUnknownEnumValue; - import junit.framework.TestCase; import java.util.ArrayList; diff --git a/java/src/test/java/com/google/protobuf/MapTest.java b/java/core/src/test/java/com/google/protobuf/MapTest.java index 0e5c1284..1dc5787d 100644 --- a/java/src/test/java/com/google/protobuf/MapTest.java +++ b/java/core/src/test/java/com/google/protobuf/MapTest.java @@ -37,7 +37,6 @@ import com.google.protobuf.Descriptors.FieldDescriptor; import map_test.MapTestProto.TestMap; import map_test.MapTestProto.TestMap.MessageValue; import map_test.MapTestProto.TestOnChangeEventPropagation; - import junit.framework.TestCase; import java.util.ArrayList; diff --git a/java/src/test/java/com/google/protobuf/MessageTest.java b/java/core/src/test/java/com/google/protobuf/MessageTest.java index abcd3a1d..dcd1aba7 100644 --- a/java/src/test/java/com/google/protobuf/MessageTest.java +++ b/java/core/src/test/java/com/google/protobuf/MessageTest.java @@ -30,11 +30,11 @@ package com.google.protobuf; -import protobuf_unittest.UnittestProto.TestAllTypes; +import protobuf_unittest.UnittestProto.ForeignMessage; import protobuf_unittest.UnittestProto.TestAllExtensions; +import protobuf_unittest.UnittestProto.TestAllTypes; import protobuf_unittest.UnittestProto.TestRequired; import protobuf_unittest.UnittestProto.TestRequiredForeign; -import protobuf_unittest.UnittestProto.ForeignMessage; import junit.framework.TestCase; diff --git a/java/src/test/java/com/google/protobuf/NestedBuildersTest.java b/java/core/src/test/java/com/google/protobuf/NestedBuildersTest.java index 23653126..542e28c0 100644 --- a/java/src/test/java/com/google/protobuf/NestedBuildersTest.java +++ b/java/core/src/test/java/com/google/protobuf/NestedBuildersTest.java @@ -35,8 +35,8 @@ import protobuf_unittest.Wheel; import junit.framework.TestCase; -import java.util.List; import java.util.ArrayList; +import java.util.List; /** * Test cases that exercise end-to-end use cases involving diff --git a/java/core/src/test/java/com/google/protobuf/NioByteStringTest.java b/java/core/src/test/java/com/google/protobuf/NioByteStringTest.java new file mode 100644 index 00000000..6be5b93c --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/NioByteStringTest.java @@ -0,0 +1,620 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// 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 static com.google.protobuf.Internal.UTF_8; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.nio.BufferOverflowException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.List; +import java.util.NoSuchElementException; + +/** + * Tests for {@link NioByteString}. + */ +public class NioByteStringTest extends TestCase { + private static final ByteString EMPTY = new NioByteString(ByteBuffer.wrap(new byte[0])); + private static final String CLASSNAME = NioByteString.class.getSimpleName(); + private static final byte[] BYTES = ByteStringTest.getTestBytes(1234, 11337766L); + private static final int EXPECTED_HASH = ByteString.wrap(BYTES).hashCode(); + + private final ByteBuffer backingBuffer = ByteBuffer.wrap(BYTES.clone()); + private final ByteString testString = new NioByteString(backingBuffer); + + public void testExpectedType() { + String actualClassName = getActualClassName(testString); + assertEquals(CLASSNAME + " should match type exactly", CLASSNAME, actualClassName); + } + + protected String getActualClassName(Object object) { + String actualClassName = object.getClass().getName(); + actualClassName = actualClassName.substring(actualClassName.lastIndexOf('.') + 1); + return actualClassName; + } + + public void testByteAt() { + boolean stillEqual = true; + for (int i = 0; stillEqual && i < BYTES.length; ++i) { + stillEqual = (BYTES[i] == testString.byteAt(i)); + } + assertTrue(CLASSNAME + " must capture the right bytes", stillEqual); + } + + public void testByteIterator() { + boolean stillEqual = true; + ByteString.ByteIterator iter = testString.iterator(); + for (int i = 0; stillEqual && i < BYTES.length; ++i) { + stillEqual = (iter.hasNext() && BYTES[i] == iter.nextByte()); + } + assertTrue(CLASSNAME + " must capture the right bytes", stillEqual); + assertFalse(CLASSNAME + " must have exhausted the itertor", iter.hasNext()); + + try { + iter.nextByte(); + fail("Should have thrown an exception."); + } catch (NoSuchElementException e) { + // This is success + } + } + + public void testByteIterable() { + boolean stillEqual = true; + int j = 0; + for (byte quantum : testString) { + stillEqual = (BYTES[j] == quantum); + ++j; + } + assertTrue(CLASSNAME + " must capture the right bytes as Bytes", stillEqual); + assertEquals(CLASSNAME + " iterable character count", BYTES.length, j); + } + + public void testSize() { + assertEquals(CLASSNAME + " must have the expected size", BYTES.length, + testString.size()); + } + + public void testGetTreeDepth() { + assertEquals(CLASSNAME + " must have depth 0", 0, testString.getTreeDepth()); + } + + public void testIsBalanced() { + assertTrue(CLASSNAME + " is technically balanced", testString.isBalanced()); + } + + public void testCopyTo_ByteArrayOffsetLength() { + int destinationOffset = 50; + int length = 100; + byte[] destination = new byte[destinationOffset + length]; + int sourceOffset = 213; + testString.copyTo(destination, sourceOffset, destinationOffset, length); + boolean stillEqual = true; + for (int i = 0; stillEqual && i < length; ++i) { + stillEqual = BYTES[i + sourceOffset] == destination[i + destinationOffset]; + } + assertTrue(CLASSNAME + ".copyTo(4 arg) must give the expected bytes", stillEqual); + } + + public void testCopyTo_ByteArrayOffsetLengthErrors() { + int destinationOffset = 50; + int length = 100; + byte[] destination = new byte[destinationOffset + length]; + + try { + // Copy one too many bytes + testString.copyTo(destination, testString.size() + 1 - length, + destinationOffset, length); + fail("Should have thrown an exception when copying too many bytes of a " + + CLASSNAME); + } catch (IndexOutOfBoundsException expected) { + // This is success + } + + try { + // Copy with illegal negative sourceOffset + testString.copyTo(destination, -1, destinationOffset, length); + fail("Should have thrown an exception when given a negative sourceOffset in " + + CLASSNAME); + } catch (IndexOutOfBoundsException expected) { + // This is success + } + + try { + // Copy with illegal negative destinationOffset + testString.copyTo(destination, 0, -1, length); + fail("Should have thrown an exception when given a negative destinationOffset in " + + CLASSNAME); + } catch (IndexOutOfBoundsException expected) { + // This is success + } + + try { + // Copy with illegal negative size + testString.copyTo(destination, 0, 0, -1); + fail("Should have thrown an exception when given a negative size in " + + CLASSNAME); + } catch (IndexOutOfBoundsException expected) { + // This is success + } + + try { + // Copy with illegal too-large sourceOffset + testString.copyTo(destination, 2 * testString.size(), 0, length); + fail("Should have thrown an exception when the destinationOffset is too large in " + + CLASSNAME); + } catch (IndexOutOfBoundsException expected) { + // This is success + } + + try { + // Copy with illegal too-large destinationOffset + testString.copyTo(destination, 0, 2 * destination.length, length); + fail("Should have thrown an exception when the destinationOffset is too large in " + + CLASSNAME); + } catch (IndexOutOfBoundsException expected) { + // This is success + } + } + + public void testCopyTo_ByteBuffer() { + // Same length. + ByteBuffer myBuffer = ByteBuffer.allocate(BYTES.length); + testString.copyTo(myBuffer); + myBuffer.flip(); + assertEquals(CLASSNAME + ".copyTo(ByteBuffer) must give back the same bytes", + backingBuffer, myBuffer); + + // Target buffer bigger than required. + myBuffer = ByteBuffer.allocate(testString.size() + 1); + testString.copyTo(myBuffer); + myBuffer.flip(); + assertEquals(backingBuffer, myBuffer); + + // Target buffer has no space. + myBuffer = ByteBuffer.allocate(0); + try { + testString.copyTo(myBuffer); + fail("Should have thrown an exception when target ByteBuffer has insufficient capacity"); + } catch (BufferOverflowException e) { + // Expected. + } + + // Target buffer too small. + myBuffer = ByteBuffer.allocate(1); + try { + testString.copyTo(myBuffer); + fail("Should have thrown an exception when target ByteBuffer has insufficient capacity"); + } catch (BufferOverflowException e) { + // Expected. + } + } + + public void testMarkSupported() { + InputStream stream = testString.newInput(); + assertTrue(CLASSNAME + ".newInput() must support marking", stream.markSupported()); + } + + public void testMarkAndReset() throws IOException { + int fraction = testString.size() / 3; + + InputStream stream = testString.newInput(); + stream.mark(testString.size()); // First, mark() the end. + + skipFully(stream, fraction); // Skip a large fraction, but not all. + assertEquals( + CLASSNAME + ": after skipping to the 'middle', half the bytes are available", + (testString.size() - fraction), stream.available()); + stream.reset(); + assertEquals( + CLASSNAME + ": after resetting, all bytes are available", + testString.size(), stream.available()); + + skipFully(stream, testString.size()); // Skip to the end. + assertEquals( + CLASSNAME + ": after skipping to the end, no more bytes are available", + 0, stream.available()); + } + + /** + * Discards {@code n} bytes of data from the input stream. This method + * will block until the full amount has been skipped. Does not close the + * stream. + * <p>Copied from com.google.common.io.ByteStreams to avoid adding dependency. + * + * @param in the input stream to read from + * @param n the number of bytes to skip + * @throws EOFException if this stream reaches the end before skipping all + * the bytes + * @throws IOException if an I/O error occurs, or the stream does not + * support skipping + */ + static void skipFully(InputStream in, long n) throws IOException { + long toSkip = n; + while (n > 0) { + long amt = in.skip(n); + if (amt == 0) { + // Force a blocking read to avoid infinite loop + if (in.read() == -1) { + long skipped = toSkip - n; + throw new EOFException("reached end of stream after skipping " + + skipped + " bytes; " + toSkip + " bytes expected"); + } + n--; + } else { + n -= amt; + } + } + } + + public void testAsReadOnlyByteBuffer() { + ByteBuffer byteBuffer = testString.asReadOnlyByteBuffer(); + byte[] roundTripBytes = new byte[BYTES.length]; + assertTrue(byteBuffer.remaining() == BYTES.length); + assertTrue(byteBuffer.isReadOnly()); + byteBuffer.get(roundTripBytes); + assertTrue(CLASSNAME + ".asReadOnlyByteBuffer() must give back the same bytes", + Arrays.equals(BYTES, roundTripBytes)); + } + + public void testAsReadOnlyByteBufferList() { + List<ByteBuffer> byteBuffers = testString.asReadOnlyByteBufferList(); + int bytesSeen = 0; + byte[] roundTripBytes = new byte[BYTES.length]; + for (ByteBuffer byteBuffer : byteBuffers) { + int thisLength = byteBuffer.remaining(); + assertTrue(byteBuffer.isReadOnly()); + assertTrue(bytesSeen + thisLength <= BYTES.length); + byteBuffer.get(roundTripBytes, bytesSeen, thisLength); + bytesSeen += thisLength; + } + assertTrue(bytesSeen == BYTES.length); + assertTrue(CLASSNAME + ".asReadOnlyByteBufferTest() must give back the same bytes", + Arrays.equals(BYTES, roundTripBytes)); + } + + public void testToByteArray() { + byte[] roundTripBytes = testString.toByteArray(); + assertTrue(CLASSNAME + ".toByteArray() must give back the same bytes", + Arrays.equals(BYTES, roundTripBytes)); + } + + public void testWriteTo() throws IOException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + testString.writeTo(bos); + byte[] roundTripBytes = bos.toByteArray(); + assertTrue(CLASSNAME + ".writeTo() must give back the same bytes", + Arrays.equals(BYTES, roundTripBytes)); + } + + public void testWriteToShouldNotExposeInternalBufferToOutputStream() throws IOException { + OutputStream os = new OutputStream() { + @Override + public void write(byte[] b, int off, int len) { + Arrays.fill(b, off, off + len, (byte) 0); + } + + @Override + public void write(int b) { + throw new UnsupportedOperationException(); + } + }; + + byte[] original = Arrays.copyOf(BYTES, BYTES.length); + testString.writeTo(os); + assertTrue(CLASSNAME + ".writeTo() must NOT grant access to underlying buffer", + Arrays.equals(original, BYTES)); + } + + public void testWriteToInternalShouldExposeInternalBufferToOutputStream() throws IOException { + OutputStream os = new OutputStream() { + @Override + public void write(byte[] b, int off, int len) { + Arrays.fill(b, off, off + len, (byte) 0); + } + + @Override + public void write(int b) { + throw new UnsupportedOperationException(); + } + }; + + testString.writeToInternal(os, 0, testString.size()); + byte[] allZeros = new byte[testString.size()]; + assertTrue(CLASSNAME + ".writeToInternal() must grant access to underlying buffer", + Arrays.equals(allZeros, backingBuffer.array())); + } + + public void testWriteToShouldExposeInternalBufferToByteOutput() throws IOException { + ByteOutput out = new ByteOutput() { + @Override + public void write(byte value) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void write(byte[] value, int offset, int length) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void writeLazy(byte[] value, int offset, int length) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void write(ByteBuffer value) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void writeLazy(ByteBuffer value) throws IOException { + Arrays.fill(value.array(), value.arrayOffset(), value.arrayOffset() + value.limit(), + (byte) 0); + } + }; + + testString.writeTo(out); + byte[] allZeros = new byte[testString.size()]; + assertTrue(CLASSNAME + ".writeTo() must grant access to underlying buffer", + Arrays.equals(allZeros, backingBuffer.array())); + } + + public void testNewOutput() throws IOException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ByteString.Output output = ByteString.newOutput(); + testString.writeTo(output); + assertEquals("Output Size returns correct result", + output.size(), testString.size()); + output.writeTo(bos); + assertTrue("Output.writeTo() must give back the same bytes", + Arrays.equals(BYTES, bos.toByteArray())); + + // write the output stream to itself! This should cause it to double + output.writeTo(output); + assertEquals("Writing an output stream to itself is successful", + testString.concat(testString), output.toByteString()); + + output.reset(); + assertEquals("Output.reset() resets the output", 0, output.size()); + assertEquals("Output.reset() resets the output", + EMPTY, output.toByteString()); + } + + public void testToString() { + String testString = "I love unicode \u1234\u5678 characters"; + ByteString unicode = forString(testString); + String roundTripString = unicode.toString(UTF_8); + assertEquals(CLASSNAME + " unicode must match", testString, roundTripString); + } + + public void testCharsetToString() { + String testString = "I love unicode \u1234\u5678 characters"; + ByteString unicode = forString(testString); + String roundTripString = unicode.toString(UTF_8); + assertEquals(CLASSNAME + " unicode must match", testString, roundTripString); + } + + public void testToString_returnsCanonicalEmptyString() { + assertSame(CLASSNAME + " must be the same string references", + EMPTY.toString(UTF_8), + new NioByteString(ByteBuffer.wrap(new byte[0])).toString(UTF_8)); + } + + public void testToString_raisesException() { + try { + EMPTY.toString("invalid"); + fail("Should have thrown an exception."); + } catch (UnsupportedEncodingException expected) { + // This is success + } + + try { + testString.toString("invalid"); + fail("Should have thrown an exception."); + } catch (UnsupportedEncodingException expected) { + // This is success + } + } + + public void testEquals() { + assertEquals(CLASSNAME + " must not equal null", false, testString.equals(null)); + assertEquals(CLASSNAME + " must equal self", testString, testString); + assertFalse(CLASSNAME + " must not equal the empty string", + testString.equals(EMPTY)); + assertEquals(CLASSNAME + " empty strings must be equal", + EMPTY, testString.substring(55, 55)); + assertEquals(CLASSNAME + " must equal another string with the same value", + testString, new NioByteString(backingBuffer)); + + byte[] mungedBytes = mungedBytes(); + assertFalse(CLASSNAME + " must not equal every string with the same length", + testString.equals(new NioByteString(ByteBuffer.wrap(mungedBytes)))); + } + + public void testEqualsLiteralByteString() { + ByteString literal = ByteString.copyFrom(BYTES); + assertEquals(CLASSNAME + " must equal LiteralByteString with same value", literal, + testString); + assertEquals(CLASSNAME + " must equal LiteralByteString with same value", testString, + literal); + assertFalse(CLASSNAME + " must not equal the empty string", + testString.equals(ByteString.EMPTY)); + assertEquals(CLASSNAME + " empty strings must be equal", + ByteString.EMPTY, testString.substring(55, 55)); + + literal = ByteString.copyFrom(mungedBytes()); + assertFalse(CLASSNAME + " must not equal every LiteralByteString with the same length", + testString.equals(literal)); + assertFalse(CLASSNAME + " must not equal every LiteralByteString with the same length", + literal.equals(testString)); + } + + public void testEqualsRopeByteString() { + ByteString p1 = ByteString.copyFrom(BYTES, 0, 5); + ByteString p2 = ByteString.copyFrom(BYTES, 5, BYTES.length - 5); + ByteString rope = p1.concat(p2); + + assertEquals(CLASSNAME + " must equal RopeByteString with same value", rope, + testString); + assertEquals(CLASSNAME + " must equal RopeByteString with same value", testString, + rope); + assertFalse(CLASSNAME + " must not equal the empty string", + testString.equals(ByteString.EMPTY.concat(ByteString.EMPTY))); + assertEquals(CLASSNAME + " empty strings must be equal", + ByteString.EMPTY.concat(ByteString.EMPTY), testString.substring(55, 55)); + + byte[] mungedBytes = mungedBytes(); + p1 = ByteString.copyFrom(mungedBytes, 0, 5); + p2 = ByteString.copyFrom(mungedBytes, 5, mungedBytes.length - 5); + rope = p1.concat(p2); + assertFalse(CLASSNAME + " must not equal every RopeByteString with the same length", + testString.equals(rope)); + assertFalse(CLASSNAME + " must not equal every RopeByteString with the same length", + rope.equals(testString)); + } + + private byte[] mungedBytes() { + byte[] mungedBytes = new byte[BYTES.length]; + System.arraycopy(BYTES, 0, mungedBytes, 0, BYTES.length); + mungedBytes[mungedBytes.length - 5] = (byte) (mungedBytes[mungedBytes.length - 5] ^ 0xFF); + return mungedBytes; + } + + public void testHashCode() { + int hash = testString.hashCode(); + assertEquals(CLASSNAME + " must have expected hashCode", EXPECTED_HASH, hash); + } + + public void testPeekCachedHashCode() { + ByteString newString = new NioByteString(backingBuffer); + assertEquals(CLASSNAME + ".peekCachedHashCode() should return zero at first", 0, + newString.peekCachedHashCode()); + newString.hashCode(); + assertEquals(CLASSNAME + ".peekCachedHashCode should return zero at first", + EXPECTED_HASH, newString.peekCachedHashCode()); + } + + public void testPartialHash() { + // partialHash() is more strenuously tested elsewhere by testing hashes of substrings. + // This test would fail if the expected hash were 1. It's not. + int hash = testString.partialHash(testString.size(), 0, testString.size()); + assertEquals(CLASSNAME + ".partialHash() must yield expected hashCode", + EXPECTED_HASH, hash); + } + + public void testNewInput() throws IOException { + InputStream input = testString.newInput(); + assertEquals("InputStream.available() returns correct value", + testString.size(), input.available()); + boolean stillEqual = true; + for (byte referenceByte : BYTES) { + int expectedInt = (referenceByte & 0xFF); + stillEqual = (expectedInt == input.read()); + } + assertEquals("InputStream.available() returns correct value", + 0, input.available()); + assertTrue(CLASSNAME + " must give the same bytes from the InputStream", stillEqual); + assertEquals(CLASSNAME + " InputStream must now be exhausted", -1, input.read()); + } + + public void testNewInput_skip() throws IOException { + InputStream input = testString.newInput(); + int stringSize = testString.size(); + int nearEndIndex = stringSize * 2 / 3; + long skipped1 = input.skip(nearEndIndex); + assertEquals("InputStream.skip()", skipped1, nearEndIndex); + assertEquals("InputStream.available()", + stringSize - skipped1, input.available()); + assertTrue("InputStream.mark() is available", input.markSupported()); + input.mark(0); + assertEquals("InputStream.skip(), read()", + testString.byteAt(nearEndIndex) & 0xFF, input.read()); + assertEquals("InputStream.available()", + stringSize - skipped1 - 1, input.available()); + long skipped2 = input.skip(stringSize); + assertEquals("InputStream.skip() incomplete", + skipped2, stringSize - skipped1 - 1); + assertEquals("InputStream.skip(), no more input", 0, input.available()); + assertEquals("InputStream.skip(), no more input", -1, input.read()); + input.reset(); + assertEquals("InputStream.reset() succeded", + stringSize - skipped1, input.available()); + assertEquals("InputStream.reset(), read()", + testString.byteAt(nearEndIndex) & 0xFF, input.read()); + } + + public void testNewCodedInput() throws IOException { + CodedInputStream cis = testString.newCodedInput(); + byte[] roundTripBytes = cis.readRawBytes(BYTES.length); + assertTrue(CLASSNAME + " must give the same bytes back from the CodedInputStream", + Arrays.equals(BYTES, roundTripBytes)); + assertTrue(CLASSNAME + " CodedInputStream must now be exhausted", cis.isAtEnd()); + } + + /** + * Make sure we keep things simple when concatenating with empty. See also + * {@link ByteStringTest#testConcat_empty()}. + */ + public void testConcat_empty() { + assertSame(CLASSNAME + " concatenated with empty must give " + CLASSNAME, + testString.concat(EMPTY), testString); + assertSame("empty concatenated with " + CLASSNAME + " must give " + CLASSNAME, + EMPTY.concat(testString), testString); + } + + public void testJavaSerialization() throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(out); + oos.writeObject(testString); + oos.close(); + byte[] pickled = out.toByteArray(); + InputStream in = new ByteArrayInputStream(pickled); + ObjectInputStream ois = new ObjectInputStream(in); + Object o = ois.readObject(); + assertTrue("Didn't get a ByteString back", o instanceof ByteString); + assertEquals("Should get an equal ByteString back", testString, o); + } + + private static ByteString forString(String str) { + return new NioByteString(ByteBuffer.wrap(str.getBytes(UTF_8))); + } +} diff --git a/java/core/src/test/java/com/google/protobuf/ParseExceptionsTest.java b/java/core/src/test/java/com/google/protobuf/ParseExceptionsTest.java new file mode 100644 index 00000000..bf1f1d71 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/ParseExceptionsTest.java @@ -0,0 +1,273 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.google.protobuf.DescriptorProtos.DescriptorProto; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Tests the exceptions thrown when parsing from a stream. The methods on the {@link Parser} + * interface are specified to only throw {@link InvalidProtocolBufferException}. But we really want + * to distinguish between invalid protos vs. actual I/O errors (like failures reading from a + * socket, etc.). So, when we're not using the parser directly, an {@link IOException} should be + * thrown where appropriate, instead of always an {@link InvalidProtocolBufferException}. + * + * @author jh@squareup.com (Joshua Humphries) + */ +@RunWith(JUnit4.class) +public class ParseExceptionsTest { + + private interface ParseTester { + DescriptorProto parse(InputStream in) throws IOException; + } + + private byte serializedProto[]; + + private void setup() { + serializedProto = DescriptorProto.getDescriptor().toProto().toByteArray(); + } + + private void setupDelimited() { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + try { + DescriptorProto.getDescriptor().toProto().writeDelimitedTo(bos); + } catch (IOException e) { + fail("Exception not expected: " + e); + } + serializedProto = bos.toByteArray(); + } + + @Test public void message_parseFrom_InputStream() { + setup(); + verifyExceptions( + new ParseTester() { + @Override + public DescriptorProto parse(InputStream in) throws IOException { + return DescriptorProto.parseFrom(in); + } + }); + } + + @Test public void message_parseFrom_InputStreamAndExtensionRegistry() { + setup(); + verifyExceptions( + new ParseTester() { + @Override + public DescriptorProto parse(InputStream in) throws IOException { + return DescriptorProto.parseFrom(in, ExtensionRegistry.newInstance()); + } + }); + } + + @Test public void message_parseFrom_CodedInputStream() { + setup(); + verifyExceptions( + new ParseTester() { + @Override + public DescriptorProto parse(InputStream in) throws IOException { + return DescriptorProto.parseFrom(CodedInputStream.newInstance(in)); + } + }); + } + + @Test public void message_parseFrom_CodedInputStreamAndExtensionRegistry() { + setup(); + verifyExceptions( + new ParseTester() { + @Override + public DescriptorProto parse(InputStream in) throws IOException { + return DescriptorProto.parseFrom( + CodedInputStream.newInstance(in), ExtensionRegistry.newInstance()); + } + }); + } + + @Test public void message_parseDelimitedFrom_InputStream() { + setupDelimited(); + verifyExceptions( + new ParseTester() { + @Override + public DescriptorProto parse(InputStream in) throws IOException { + return DescriptorProto.parseDelimitedFrom(in); + } + }); + } + + @Test public void message_parseDelimitedFrom_InputStreamAndExtensionRegistry() { + setupDelimited(); + verifyExceptions( + new ParseTester() { + @Override + public DescriptorProto parse(InputStream in) throws IOException { + return DescriptorProto.parseDelimitedFrom(in, ExtensionRegistry.newInstance()); + } + }); + } + + @Test public void messageBuilder_mergeFrom_InputStream() { + setup(); + verifyExceptions( + new ParseTester() { + @Override + public DescriptorProto parse(InputStream in) throws IOException { + return DescriptorProto.newBuilder().mergeFrom(in).build(); + } + }); + } + + @Test public void messageBuilder_mergeFrom_InputStreamAndExtensionRegistry() { + setup(); + verifyExceptions( + new ParseTester() { + @Override + public DescriptorProto parse(InputStream in) throws IOException { + return DescriptorProto.newBuilder() + .mergeFrom(in, ExtensionRegistry.newInstance()) + .build(); + } + }); + } + + @Test public void messageBuilder_mergeFrom_CodedInputStream() { + setup(); + verifyExceptions( + new ParseTester() { + @Override + public DescriptorProto parse(InputStream in) throws IOException { + return DescriptorProto.newBuilder().mergeFrom(CodedInputStream.newInstance(in)).build(); + } + }); + } + + @Test public void messageBuilder_mergeFrom_CodedInputStreamAndExtensionRegistry() { + setup(); + verifyExceptions( + new ParseTester() { + @Override + public DescriptorProto parse(InputStream in) throws IOException { + return DescriptorProto.newBuilder() + .mergeFrom(CodedInputStream.newInstance(in), ExtensionRegistry.newInstance()) + .build(); + } + }); + } + + @Test public void messageBuilder_mergeDelimitedFrom_InputStream() { + setupDelimited(); + verifyExceptions( + new ParseTester() { + @Override + public DescriptorProto parse(InputStream in) throws IOException { + DescriptorProto.Builder builder = DescriptorProto.newBuilder(); + builder.mergeDelimitedFrom(in); + return builder.build(); + } + }); + } + + @Test public void messageBuilder_mergeDelimitedFrom_InputStreamAndExtensionRegistry() { + setupDelimited(); + verifyExceptions( + new ParseTester() { + @Override + public DescriptorProto parse(InputStream in) throws IOException { + DescriptorProto.Builder builder = DescriptorProto.newBuilder(); + builder.mergeDelimitedFrom(in, ExtensionRegistry.newInstance()); + return builder.build(); + } + }); + } + + private void verifyExceptions(ParseTester parseTester) { + // No exception + try { + assertEquals(DescriptorProto.getDescriptor().toProto(), + parseTester.parse(new ByteArrayInputStream(serializedProto))); + } catch (IOException e) { + fail("No exception expected: " + e); + } + + // IOException + try { + // using a "broken" stream that will throw part-way through reading the message + parseTester.parse(broken(new ByteArrayInputStream(serializedProto))); + fail("IOException expected but not thrown"); + } catch (IOException e) { + assertFalse(e instanceof InvalidProtocolBufferException); + } + + // InvalidProtocolBufferException + try { + // make the serialized proto invalid + for (int i = 0; i < 50; i++) { + serializedProto[i] = -1; + } + parseTester.parse(new ByteArrayInputStream(serializedProto)); + fail("InvalidProtocolBufferException expected but not thrown"); + } catch (IOException e) { + assertTrue(e instanceof InvalidProtocolBufferException); + } + } + + private InputStream broken(InputStream i) { + return new FilterInputStream(i) { + int count = 0; + + @Override public int read() throws IOException { + if (count++ >= 50) { + throw new IOException("I'm broken!"); + } + return super.read(); + } + + @Override public int read(byte b[], int off, int len) throws IOException { + if ((count += len) >= 50) { + throw new IOException("I'm broken!"); + } + return super.read(b, off, len); + } + }; + } +} diff --git a/java/src/test/java/com/google/protobuf/ParserTest.java b/java/core/src/test/java/com/google/protobuf/ParserTest.java index 5a92bacf..30842d2c 100644 --- a/java/src/test/java/com/google/protobuf/ParserTest.java +++ b/java/core/src/test/java/com/google/protobuf/ParserTest.java @@ -33,15 +33,15 @@ package com.google.protobuf; import com.google.protobuf.UnittestLite.TestAllTypesLite; import com.google.protobuf.UnittestLite.TestPackedExtensionsLite; import com.google.protobuf.UnittestLite.TestParsingMergeLite; +import protobuf_unittest.UnittestOptimizeFor; import protobuf_unittest.UnittestOptimizeFor.TestOptimizedForSize; import protobuf_unittest.UnittestOptimizeFor.TestRequiredOptimizedForSize; -import protobuf_unittest.UnittestOptimizeFor; +import protobuf_unittest.UnittestProto; import protobuf_unittest.UnittestProto.ForeignMessage; import protobuf_unittest.UnittestProto.TestAllTypes; import protobuf_unittest.UnittestProto.TestEmptyMessage; import protobuf_unittest.UnittestProto.TestParsingMerge; import protobuf_unittest.UnittestProto.TestRequired; -import protobuf_unittest.UnittestProto; import junit.framework.TestCase; @@ -179,16 +179,16 @@ public class ParserTest extends TestCase { public void testParseExtensions() throws Exception { assertRoundTripEquals(TestUtil.getAllExtensionsSet(), TestUtil.getExtensionRegistry()); - assertRoundTripEquals(TestUtil.getAllLiteExtensionsSet(), - TestUtil.getExtensionRegistryLite()); + assertRoundTripEquals( + TestUtilLite.getAllLiteExtensionsSet(), TestUtilLite.getExtensionRegistryLite()); } public void testParsePacked() throws Exception { assertRoundTripEquals(TestUtil.getPackedSet()); assertRoundTripEquals(TestUtil.getPackedExtensionsSet(), TestUtil.getExtensionRegistry()); - assertRoundTripEquals(TestUtil.getLitePackedExtensionsSet(), - TestUtil.getExtensionRegistryLite()); + assertRoundTripEquals( + TestUtilLite.getLitePackedExtensionsSet(), TestUtilLite.getExtensionRegistryLite()); } public void testParseDelimitedTo() throws Exception { @@ -198,8 +198,7 @@ public class ParserTest extends TestCase { normalMessage.writeDelimitedTo(output); // Write MessageLite with packed extension fields. - TestPackedExtensionsLite packedMessage = - TestUtil.getLitePackedExtensionsSet(); + TestPackedExtensionsLite packedMessage = TestUtilLite.getLitePackedExtensionsSet(); packedMessage.writeDelimitedTo(output); InputStream input = new ByteArrayInputStream(output.toByteArray()); @@ -208,8 +207,9 @@ public class ParserTest extends TestCase { normalMessage.getParserForType().parseDelimitedFrom(input)); assertMessageEquals( packedMessage, - packedMessage.getParserForType().parseDelimitedFrom( - input, TestUtil.getExtensionRegistryLite())); + packedMessage + .getParserForType() + .parseDelimitedFrom(input, TestUtilLite.getExtensionRegistryLite())); } public void testParseUnknownFields() throws Exception { diff --git a/java/src/test/java/com/google/protobuf/ProtobufArrayListTest.java b/java/core/src/test/java/com/google/protobuf/ProtobufArrayListTest.java index f9f5e9c7..3f45e226 100644 --- a/java/src/test/java/com/google/protobuf/ProtobufArrayListTest.java +++ b/java/core/src/test/java/com/google/protobuf/ProtobufArrayListTest.java @@ -63,20 +63,6 @@ public class ProtobufArrayListTest extends TestCase { assertImmutable(ProtobufArrayList.<Integer>emptyList()); } - public void testCopyConstructor() { - ProtobufArrayList<Integer> copy = new ProtobufArrayList<Integer>(TERTIARY_LIST); - assertEquals(TERTIARY_LIST, copy); - - copy = new ProtobufArrayList<Integer>(IntArrayList.emptyList()); - assertEquals(ProtobufArrayList.emptyList(), copy); - - copy = new ProtobufArrayList<Integer>(asList(1, 2, 3)); - assertEquals(asList(1, 2, 3), copy); - - copy = new ProtobufArrayList<Integer>(Collections.<Integer>emptyList()); - assertEquals(ProtobufArrayList.emptyList(), copy); - } - public void testModificationWithIteration() { list.addAll(asList(1, 2, 3, 4)); Iterator<Integer> iterator = list.iterator(); @@ -243,7 +229,7 @@ public class ProtobufArrayListTest extends TestCase { } try { - list.removeAll(Collections.<Double>emptyList()); + list.removeAll(Collections.emptyList()); fail(); } catch (UnsupportedOperationException e) { // expected @@ -264,7 +250,7 @@ public class ProtobufArrayListTest extends TestCase { } try { - list.retainAll(Collections.<Double>emptyList()); + list.retainAll(Collections.emptyList()); fail(); } catch (UnsupportedOperationException e) { // expected diff --git a/java/src/test/java/com/google/protobuf/RepeatedFieldBuilderTest.java b/java/core/src/test/java/com/google/protobuf/RepeatedFieldBuilderTest.java index 49d52321..49d52321 100644 --- a/java/src/test/java/com/google/protobuf/RepeatedFieldBuilderTest.java +++ b/java/core/src/test/java/com/google/protobuf/RepeatedFieldBuilderTest.java diff --git a/java/src/test/java/com/google/protobuf/RopeByteStringSubstringTest.java b/java/core/src/test/java/com/google/protobuf/RopeByteStringSubstringTest.java index cc385599..dc56f2e9 100644 --- a/java/src/test/java/com/google/protobuf/RopeByteStringSubstringTest.java +++ b/java/core/src/test/java/com/google/protobuf/RopeByteStringSubstringTest.java @@ -96,7 +96,7 @@ public class RopeByteStringSubstringTest extends LiteralByteStringTest { } @Override - public void testCharsetToString() throws UnsupportedEncodingException { + public void testCharsetToString() { String sourceString = "I love unicode \u1234\u5678 characters"; ByteString sourceByteString = ByteString.copyFromUtf8(sourceString); int copies = 250; diff --git a/java/src/test/java/com/google/protobuf/RopeByteStringTest.java b/java/core/src/test/java/com/google/protobuf/RopeByteStringTest.java index 4ec3a409..4ec3a409 100644 --- a/java/src/test/java/com/google/protobuf/RopeByteStringTest.java +++ b/java/core/src/test/java/com/google/protobuf/RopeByteStringTest.java diff --git a/java/src/test/java/com/google/protobuf/ServiceTest.java b/java/core/src/test/java/com/google/protobuf/ServiceTest.java index ff980d66..b902737d 100644 --- a/java/src/test/java/com/google/protobuf/ServiceTest.java +++ b/java/core/src/test/java/com/google/protobuf/ServiceTest.java @@ -35,22 +35,22 @@ import com.google.protobuf.Descriptors.MethodDescriptor; import google.protobuf.no_generic_services_test.UnittestNoGenericServices; import protobuf_unittest.MessageWithNoOuter; import protobuf_unittest.ServiceWithNoOuter; -import protobuf_unittest.UnittestProto.TestAllTypes; -import protobuf_unittest.UnittestProto.TestService; -import protobuf_unittest.UnittestProto.FooRequest; -import protobuf_unittest.UnittestProto.FooResponse; import protobuf_unittest.UnittestProto.BarRequest; import protobuf_unittest.UnittestProto.BarResponse; +import protobuf_unittest.UnittestProto.FooRequest; +import protobuf_unittest.UnittestProto.FooResponse; +import protobuf_unittest.UnittestProto.TestAllTypes; +import protobuf_unittest.UnittestProto.TestService; + +import junit.framework.TestCase; import org.easymock.classextension.EasyMock; -import org.easymock.classextension.IMocksControl; import org.easymock.IArgumentMatcher; +import org.easymock.classextension.IMocksControl; import java.util.HashSet; import java.util.Set; -import junit.framework.TestCase; - /** * Tests services and stubs. * @@ -175,12 +175,14 @@ public class ServiceTest extends TestCase { MethodDescriptor fooMethod = ServiceWithNoOuter.getDescriptor().findMethodByName("Foo"); MessageWithNoOuter request = MessageWithNoOuter.getDefaultInstance(); - RpcCallback<Message> callback = new RpcCallback<Message>() { - public void run(Message parameter) { - // No reason this should be run. - fail(); - } - }; + RpcCallback<Message> callback = + new RpcCallback<Message>() { + @Override + public void run(Message parameter) { + // No reason this should be run. + fail(); + } + }; RpcCallback<TestAllTypes> specializedCallback = RpcUtil.specializeCallback(callback); @@ -290,7 +292,9 @@ public class ServiceTest extends TestCase { public boolean isCalled() { return called; } public void reset() { called = false; } - public void run(Type message) { called = true; } + @Override + public void run(Type message) { + called = true; } } /** Implementation of the wrapsCallback() argument matcher. */ @@ -301,6 +305,7 @@ public class ServiceTest extends TestCase { this.callback = callback; } + @Override @SuppressWarnings("unchecked") public boolean matches(Object actual) { if (!(actual instanceof RpcCallback)) { @@ -313,6 +318,7 @@ public class ServiceTest extends TestCase { return callback.isCalled(); } + @Override public void appendTo(StringBuffer buffer) { buffer.append("wrapsCallback(mockCallback)"); } diff --git a/java/src/test/java/com/google/protobuf/SingleFieldBuilderTest.java b/java/core/src/test/java/com/google/protobuf/SingleFieldBuilderTest.java index 58b80007..58b80007 100644 --- a/java/src/test/java/com/google/protobuf/SingleFieldBuilderTest.java +++ b/java/core/src/test/java/com/google/protobuf/SingleFieldBuilderTest.java diff --git a/java/src/test/java/com/google/protobuf/SmallSortedMapTest.java b/java/core/src/test/java/com/google/protobuf/SmallSortedMapTest.java index 366086d3..e96ecd65 100644 --- a/java/src/test/java/com/google/protobuf/SmallSortedMapTest.java +++ b/java/core/src/test/java/com/google/protobuf/SmallSortedMapTest.java @@ -56,14 +56,17 @@ public class SmallSortedMapTest extends TestCase { this.value = value; } + @Override public K getKey() { return key; } + @Override public V getValue() { return value; } + @Override public V setValue(V value) { V oldValue = this.value; this.value = value; diff --git a/java/src/test/java/com/google/protobuf/TestBadIdentifiers.java b/java/core/src/test/java/com/google/protobuf/TestBadIdentifiers.java index 2c60fe0e..2c60fe0e 100644 --- a/java/src/test/java/com/google/protobuf/TestBadIdentifiers.java +++ b/java/core/src/test/java/com/google/protobuf/TestBadIdentifiers.java diff --git a/java/src/test/java/com/google/protobuf/TestUtil.java b/java/core/src/test/java/com/google/protobuf/TestUtil.java index 792e8665..08b2a76d 100644 --- a/java/src/test/java/com/google/protobuf/TestUtil.java +++ b/java/core/src/test/java/com/google/protobuf/TestUtil.java @@ -30,205 +30,200 @@ package com.google.protobuf; -import protobuf_unittest.UnittestProto; -import com.google.protobuf.UnittestLite; - +import static com.google.protobuf.UnittestLite.defaultBoolExtensionLite; +import static com.google.protobuf.UnittestLite.defaultBytesExtensionLite; +import static com.google.protobuf.UnittestLite.defaultCordExtensionLite; +import static com.google.protobuf.UnittestLite.defaultDoubleExtensionLite; +import static com.google.protobuf.UnittestLite.defaultFixed32ExtensionLite; +import static com.google.protobuf.UnittestLite.defaultFixed64ExtensionLite; +import static com.google.protobuf.UnittestLite.defaultFloatExtensionLite; +import static com.google.protobuf.UnittestLite.defaultForeignEnumExtensionLite; +import static com.google.protobuf.UnittestLite.defaultImportEnumExtensionLite; +import static com.google.protobuf.UnittestLite.defaultInt32ExtensionLite; +import static com.google.protobuf.UnittestLite.defaultInt64ExtensionLite; +import static com.google.protobuf.UnittestLite.defaultNestedEnumExtensionLite; +import static com.google.protobuf.UnittestLite.defaultSfixed32ExtensionLite; +import static com.google.protobuf.UnittestLite.defaultSfixed64ExtensionLite; +import static com.google.protobuf.UnittestLite.defaultSint32ExtensionLite; +import static com.google.protobuf.UnittestLite.defaultSint64ExtensionLite; +import static com.google.protobuf.UnittestLite.defaultStringExtensionLite; +import static com.google.protobuf.UnittestLite.defaultStringPieceExtensionLite; +import static com.google.protobuf.UnittestLite.defaultUint32ExtensionLite; +import static com.google.protobuf.UnittestLite.defaultUint64ExtensionLite; +import static com.google.protobuf.UnittestLite.oneofBytesExtensionLite; +import static com.google.protobuf.UnittestLite.oneofNestedMessageExtensionLite; +import static com.google.protobuf.UnittestLite.oneofStringExtensionLite; +import static com.google.protobuf.UnittestLite.oneofUint32ExtensionLite; +import static com.google.protobuf.UnittestLite.optionalBoolExtensionLite; +import static com.google.protobuf.UnittestLite.optionalBytesExtensionLite; +import static com.google.protobuf.UnittestLite.optionalCordExtensionLite; +import static com.google.protobuf.UnittestLite.optionalDoubleExtensionLite; +import static com.google.protobuf.UnittestLite.optionalFixed32ExtensionLite; +import static com.google.protobuf.UnittestLite.optionalFixed64ExtensionLite; +import static com.google.protobuf.UnittestLite.optionalFloatExtensionLite; +import static com.google.protobuf.UnittestLite.optionalForeignEnumExtensionLite; +import static com.google.protobuf.UnittestLite.optionalForeignMessageExtensionLite; +import static com.google.protobuf.UnittestLite.optionalGroupExtensionLite; +import static com.google.protobuf.UnittestLite.optionalImportEnumExtensionLite; +import static com.google.protobuf.UnittestLite.optionalImportMessageExtensionLite; +import static com.google.protobuf.UnittestLite.optionalInt32ExtensionLite; +import static com.google.protobuf.UnittestLite.optionalInt64ExtensionLite; +import static com.google.protobuf.UnittestLite.optionalLazyMessageExtensionLite; +import static com.google.protobuf.UnittestLite.optionalNestedEnumExtensionLite; +import static com.google.protobuf.UnittestLite.optionalNestedMessageExtensionLite; +import static com.google.protobuf.UnittestLite.optionalPublicImportMessageExtensionLite; +import static com.google.protobuf.UnittestLite.optionalSfixed32ExtensionLite; +import static com.google.protobuf.UnittestLite.optionalSfixed64ExtensionLite; +import static com.google.protobuf.UnittestLite.optionalSint32ExtensionLite; +import static com.google.protobuf.UnittestLite.optionalSint64ExtensionLite; +import static com.google.protobuf.UnittestLite.optionalStringExtensionLite; +import static com.google.protobuf.UnittestLite.optionalStringPieceExtensionLite; +import static com.google.protobuf.UnittestLite.optionalUint32ExtensionLite; +import static com.google.protobuf.UnittestLite.optionalUint64ExtensionLite; +import static com.google.protobuf.UnittestLite.packedBoolExtensionLite; +import static com.google.protobuf.UnittestLite.packedDoubleExtensionLite; +import static com.google.protobuf.UnittestLite.packedEnumExtensionLite; +import static com.google.protobuf.UnittestLite.packedFixed32ExtensionLite; +import static com.google.protobuf.UnittestLite.packedFixed64ExtensionLite; +import static com.google.protobuf.UnittestLite.packedFloatExtensionLite; +import static com.google.protobuf.UnittestLite.packedInt32ExtensionLite; +import static com.google.protobuf.UnittestLite.packedInt64ExtensionLite; +import static com.google.protobuf.UnittestLite.packedSfixed32ExtensionLite; +import static com.google.protobuf.UnittestLite.packedSfixed64ExtensionLite; +import static com.google.protobuf.UnittestLite.packedSint32ExtensionLite; +import static com.google.protobuf.UnittestLite.packedSint64ExtensionLite; +import static com.google.protobuf.UnittestLite.packedUint32ExtensionLite; +import static com.google.protobuf.UnittestLite.packedUint64ExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedBoolExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedBytesExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedCordExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedDoubleExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedFixed32ExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedFixed64ExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedFloatExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedForeignEnumExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedForeignMessageExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedGroupExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedImportEnumExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedImportMessageExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedInt32ExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedInt64ExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedLazyMessageExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedNestedEnumExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedNestedMessageExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedSfixed32ExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedSfixed64ExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedSint32ExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedSint64ExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedStringExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedStringPieceExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedUint32ExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedUint64ExtensionLite; +import static protobuf_unittest.UnittestProto.OptionalGroup_extension; +import static protobuf_unittest.UnittestProto.RepeatedGroup_extension; +import static protobuf_unittest.UnittestProto.defaultBoolExtension; +import static protobuf_unittest.UnittestProto.defaultBytesExtension; +import static protobuf_unittest.UnittestProto.defaultCordExtension; +import static protobuf_unittest.UnittestProto.defaultDoubleExtension; +import static protobuf_unittest.UnittestProto.defaultFixed32Extension; +import static protobuf_unittest.UnittestProto.defaultFixed64Extension; +import static protobuf_unittest.UnittestProto.defaultFloatExtension; +import static protobuf_unittest.UnittestProto.defaultForeignEnumExtension; +import static protobuf_unittest.UnittestProto.defaultImportEnumExtension; // The static imports are to avoid 100+ char lines. The following is roughly equivalent to // import static protobuf_unittest.UnittestProto.*; import static protobuf_unittest.UnittestProto.defaultInt32Extension; import static protobuf_unittest.UnittestProto.defaultInt64Extension; -import static protobuf_unittest.UnittestProto.defaultUint32Extension; -import static protobuf_unittest.UnittestProto.defaultUint64Extension; -import static protobuf_unittest.UnittestProto.defaultSint32Extension; -import static protobuf_unittest.UnittestProto.defaultSint64Extension; -import static protobuf_unittest.UnittestProto.defaultFixed32Extension; -import static protobuf_unittest.UnittestProto.defaultFixed64Extension; +import static protobuf_unittest.UnittestProto.defaultNestedEnumExtension; import static protobuf_unittest.UnittestProto.defaultSfixed32Extension; import static protobuf_unittest.UnittestProto.defaultSfixed64Extension; -import static protobuf_unittest.UnittestProto.defaultFloatExtension; -import static protobuf_unittest.UnittestProto.defaultDoubleExtension; -import static protobuf_unittest.UnittestProto.defaultBoolExtension; +import static protobuf_unittest.UnittestProto.defaultSint32Extension; +import static protobuf_unittest.UnittestProto.defaultSint64Extension; import static protobuf_unittest.UnittestProto.defaultStringExtension; -import static protobuf_unittest.UnittestProto.defaultBytesExtension; -import static protobuf_unittest.UnittestProto.defaultNestedEnumExtension; -import static protobuf_unittest.UnittestProto.defaultForeignEnumExtension; -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.defaultUint32Extension; +import static protobuf_unittest.UnittestProto.defaultUint64Extension; +import static protobuf_unittest.UnittestProto.oneofBytesExtension; 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; -import static protobuf_unittest.UnittestProto.optionalUint64Extension; -import static protobuf_unittest.UnittestProto.optionalSint32Extension; -import static protobuf_unittest.UnittestProto.optionalSint64Extension; -import static protobuf_unittest.UnittestProto.optionalFixed32Extension; -import static protobuf_unittest.UnittestProto.optionalFixed64Extension; -import static protobuf_unittest.UnittestProto.optionalSfixed32Extension; -import static protobuf_unittest.UnittestProto.optionalSfixed64Extension; -import static protobuf_unittest.UnittestProto.optionalFloatExtension; -import static protobuf_unittest.UnittestProto.optionalDoubleExtension; +import static protobuf_unittest.UnittestProto.oneofUint32Extension; import static protobuf_unittest.UnittestProto.optionalBoolExtension; -import static protobuf_unittest.UnittestProto.optionalStringExtension; import static protobuf_unittest.UnittestProto.optionalBytesExtension; -import static protobuf_unittest.UnittestProto.optionalGroupExtension; import static protobuf_unittest.UnittestProto.optionalCordExtension; +import static protobuf_unittest.UnittestProto.optionalDoubleExtension; +import static protobuf_unittest.UnittestProto.optionalFixed32Extension; +import static protobuf_unittest.UnittestProto.optionalFixed64Extension; +import static protobuf_unittest.UnittestProto.optionalFloatExtension; import static protobuf_unittest.UnittestProto.optionalForeignEnumExtension; import static protobuf_unittest.UnittestProto.optionalForeignMessageExtension; +import static protobuf_unittest.UnittestProto.optionalGroupExtension; import static protobuf_unittest.UnittestProto.optionalImportEnumExtension; import static protobuf_unittest.UnittestProto.optionalImportMessageExtension; +import static protobuf_unittest.UnittestProto.optionalInt32Extension; +import static protobuf_unittest.UnittestProto.optionalInt64Extension; +import static protobuf_unittest.UnittestProto.optionalLazyMessageExtension; import static protobuf_unittest.UnittestProto.optionalNestedEnumExtension; import static protobuf_unittest.UnittestProto.optionalNestedMessageExtension; import static protobuf_unittest.UnittestProto.optionalPublicImportMessageExtension; -import static protobuf_unittest.UnittestProto.optionalLazyMessageExtension; +import static protobuf_unittest.UnittestProto.optionalSfixed32Extension; +import static protobuf_unittest.UnittestProto.optionalSfixed64Extension; +import static protobuf_unittest.UnittestProto.optionalSint32Extension; +import static protobuf_unittest.UnittestProto.optionalSint64Extension; +import static protobuf_unittest.UnittestProto.optionalStringExtension; import static protobuf_unittest.UnittestProto.optionalStringPieceExtension; - -import static protobuf_unittest.UnittestProto.repeatedInt32Extension; -import static protobuf_unittest.UnittestProto.repeatedInt64Extension; -import static protobuf_unittest.UnittestProto.repeatedUint32Extension; -import static protobuf_unittest.UnittestProto.repeatedUint64Extension; -import static protobuf_unittest.UnittestProto.repeatedSint32Extension; -import static protobuf_unittest.UnittestProto.repeatedSint64Extension; +import static protobuf_unittest.UnittestProto.optionalUint32Extension; +import static protobuf_unittest.UnittestProto.optionalUint64Extension; +import static protobuf_unittest.UnittestProto.packedBoolExtension; +import static protobuf_unittest.UnittestProto.packedDoubleExtension; +import static protobuf_unittest.UnittestProto.packedEnumExtension; +import static protobuf_unittest.UnittestProto.packedFixed32Extension; +import static protobuf_unittest.UnittestProto.packedFixed64Extension; +import static protobuf_unittest.UnittestProto.packedFloatExtension; +import static protobuf_unittest.UnittestProto.packedInt32Extension; +import static protobuf_unittest.UnittestProto.packedInt64Extension; +import static protobuf_unittest.UnittestProto.packedSfixed32Extension; +import static protobuf_unittest.UnittestProto.packedSfixed64Extension; +import static protobuf_unittest.UnittestProto.packedSint32Extension; +import static protobuf_unittest.UnittestProto.packedSint64Extension; +import static protobuf_unittest.UnittestProto.packedUint32Extension; +import static protobuf_unittest.UnittestProto.packedUint64Extension; +import static protobuf_unittest.UnittestProto.repeatedBoolExtension; +import static protobuf_unittest.UnittestProto.repeatedBytesExtension; +import static protobuf_unittest.UnittestProto.repeatedCordExtension; +import static protobuf_unittest.UnittestProto.repeatedDoubleExtension; import static protobuf_unittest.UnittestProto.repeatedFixed32Extension; import static protobuf_unittest.UnittestProto.repeatedFixed64Extension; -import static protobuf_unittest.UnittestProto.repeatedSfixed32Extension; -import static protobuf_unittest.UnittestProto.repeatedSfixed64Extension; import static protobuf_unittest.UnittestProto.repeatedFloatExtension; -import static protobuf_unittest.UnittestProto.repeatedDoubleExtension; -import static protobuf_unittest.UnittestProto.repeatedBoolExtension; -import static protobuf_unittest.UnittestProto.repeatedStringExtension; -import static protobuf_unittest.UnittestProto.repeatedBytesExtension; -import static protobuf_unittest.UnittestProto.repeatedGroupExtension; -import static protobuf_unittest.UnittestProto.repeatedNestedMessageExtension; +import static protobuf_unittest.UnittestProto.repeatedForeignEnumExtension; import static protobuf_unittest.UnittestProto.repeatedForeignMessageExtension; +import static protobuf_unittest.UnittestProto.repeatedGroupExtension; +import static protobuf_unittest.UnittestProto.repeatedImportEnumExtension; import static protobuf_unittest.UnittestProto.repeatedImportMessageExtension; +import static protobuf_unittest.UnittestProto.repeatedInt32Extension; +import static protobuf_unittest.UnittestProto.repeatedInt64Extension; import static protobuf_unittest.UnittestProto.repeatedLazyMessageExtension; import static protobuf_unittest.UnittestProto.repeatedNestedEnumExtension; -import static protobuf_unittest.UnittestProto.repeatedForeignEnumExtension; -import static protobuf_unittest.UnittestProto.repeatedImportEnumExtension; +import static protobuf_unittest.UnittestProto.repeatedNestedMessageExtension; +import static protobuf_unittest.UnittestProto.repeatedSfixed32Extension; +import static protobuf_unittest.UnittestProto.repeatedSfixed64Extension; +import static protobuf_unittest.UnittestProto.repeatedSint32Extension; +import static protobuf_unittest.UnittestProto.repeatedSint64Extension; +import static protobuf_unittest.UnittestProto.repeatedStringExtension; import static protobuf_unittest.UnittestProto.repeatedStringPieceExtension; -import static protobuf_unittest.UnittestProto.repeatedCordExtension; - -import static protobuf_unittest.UnittestProto.OptionalGroup_extension; -import static protobuf_unittest.UnittestProto.RepeatedGroup_extension; - -import static protobuf_unittest.UnittestProto.packedInt32Extension; -import static protobuf_unittest.UnittestProto.packedInt64Extension; -import static protobuf_unittest.UnittestProto.packedUint32Extension; -import static protobuf_unittest.UnittestProto.packedUint64Extension; -import static protobuf_unittest.UnittestProto.packedSint32Extension; -import static protobuf_unittest.UnittestProto.packedSint64Extension; -import static protobuf_unittest.UnittestProto.packedFixed32Extension; -import static protobuf_unittest.UnittestProto.packedFixed64Extension; -import static protobuf_unittest.UnittestProto.packedSfixed32Extension; -import static protobuf_unittest.UnittestProto.packedSfixed64Extension; -import static protobuf_unittest.UnittestProto.packedFloatExtension; -import static protobuf_unittest.UnittestProto.packedDoubleExtension; -import static protobuf_unittest.UnittestProto.packedBoolExtension; -import static protobuf_unittest.UnittestProto.packedEnumExtension; - -import static com.google.protobuf.UnittestLite.defaultInt32ExtensionLite; -import static com.google.protobuf.UnittestLite.defaultInt64ExtensionLite; -import static com.google.protobuf.UnittestLite.defaultUint32ExtensionLite; -import static com.google.protobuf.UnittestLite.defaultUint64ExtensionLite; -import static com.google.protobuf.UnittestLite.defaultSint32ExtensionLite; -import static com.google.protobuf.UnittestLite.defaultSint64ExtensionLite; -import static com.google.protobuf.UnittestLite.defaultFixed32ExtensionLite; -import static com.google.protobuf.UnittestLite.defaultFixed64ExtensionLite; -import static com.google.protobuf.UnittestLite.defaultSfixed32ExtensionLite; -import static com.google.protobuf.UnittestLite.defaultSfixed64ExtensionLite; -import static com.google.protobuf.UnittestLite.defaultFloatExtensionLite; -import static com.google.protobuf.UnittestLite.defaultDoubleExtensionLite; -import static com.google.protobuf.UnittestLite.defaultBoolExtensionLite; -import static com.google.protobuf.UnittestLite.defaultStringExtensionLite; -import static com.google.protobuf.UnittestLite.defaultBytesExtensionLite; -import static com.google.protobuf.UnittestLite.defaultNestedEnumExtensionLite; -import static com.google.protobuf.UnittestLite.defaultForeignEnumExtensionLite; -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; -import static com.google.protobuf.UnittestLite.optionalUint64ExtensionLite; -import static com.google.protobuf.UnittestLite.optionalSint32ExtensionLite; -import static com.google.protobuf.UnittestLite.optionalSint64ExtensionLite; -import static com.google.protobuf.UnittestLite.optionalFixed32ExtensionLite; -import static com.google.protobuf.UnittestLite.optionalFixed64ExtensionLite; -import static com.google.protobuf.UnittestLite.optionalSfixed32ExtensionLite; -import static com.google.protobuf.UnittestLite.optionalSfixed64ExtensionLite; -import static com.google.protobuf.UnittestLite.optionalFloatExtensionLite; -import static com.google.protobuf.UnittestLite.optionalDoubleExtensionLite; -import static com.google.protobuf.UnittestLite.optionalBoolExtensionLite; -import static com.google.protobuf.UnittestLite.optionalStringExtensionLite; -import static com.google.protobuf.UnittestLite.optionalBytesExtensionLite; -import static com.google.protobuf.UnittestLite.optionalGroupExtensionLite; -import static com.google.protobuf.UnittestLite.optionalNestedMessageExtensionLite; -import static com.google.protobuf.UnittestLite.optionalForeignEnumExtensionLite; -import static com.google.protobuf.UnittestLite.optionalForeignMessageExtensionLite; -import static com.google.protobuf.UnittestLite.optionalImportEnumExtensionLite; -import static com.google.protobuf.UnittestLite.optionalImportMessageExtensionLite; -import static com.google.protobuf.UnittestLite.optionalNestedEnumExtensionLite; -import static com.google.protobuf.UnittestLite.optionalPublicImportMessageExtensionLite; -import static com.google.protobuf.UnittestLite.optionalLazyMessageExtensionLite; -import static com.google.protobuf.UnittestLite.optionalStringPieceExtensionLite; -import static com.google.protobuf.UnittestLite.optionalCordExtensionLite; - -import static com.google.protobuf.UnittestLite.repeatedInt32ExtensionLite; -import static com.google.protobuf.UnittestLite.repeatedInt64ExtensionLite; -import static com.google.protobuf.UnittestLite.repeatedUint32ExtensionLite; -import static com.google.protobuf.UnittestLite.repeatedUint64ExtensionLite; -import static com.google.protobuf.UnittestLite.repeatedSint32ExtensionLite; -import static com.google.protobuf.UnittestLite.repeatedSint64ExtensionLite; -import static com.google.protobuf.UnittestLite.repeatedFixed32ExtensionLite; -import static com.google.protobuf.UnittestLite.repeatedFixed64ExtensionLite; -import static com.google.protobuf.UnittestLite.repeatedSfixed32ExtensionLite; -import static com.google.protobuf.UnittestLite.repeatedSfixed64ExtensionLite; -import static com.google.protobuf.UnittestLite.repeatedFloatExtensionLite; -import static com.google.protobuf.UnittestLite.repeatedDoubleExtensionLite; -import static com.google.protobuf.UnittestLite.repeatedBoolExtensionLite; -import static com.google.protobuf.UnittestLite.repeatedStringExtensionLite; -import static com.google.protobuf.UnittestLite.repeatedBytesExtensionLite; -import static com.google.protobuf.UnittestLite.repeatedGroupExtensionLite; -import static com.google.protobuf.UnittestLite.repeatedNestedMessageExtensionLite; -import static com.google.protobuf.UnittestLite.repeatedForeignMessageExtensionLite; -import static com.google.protobuf.UnittestLite.repeatedImportMessageExtensionLite; -import static com.google.protobuf.UnittestLite.repeatedLazyMessageExtensionLite; -import static com.google.protobuf.UnittestLite.repeatedNestedEnumExtensionLite; -import static com.google.protobuf.UnittestLite.repeatedForeignEnumExtensionLite; -import static com.google.protobuf.UnittestLite.repeatedImportEnumExtensionLite; -import static com.google.protobuf.UnittestLite.repeatedStringPieceExtensionLite; -import static com.google.protobuf.UnittestLite.repeatedCordExtensionLite; - -import static com.google.protobuf.UnittestLite.OptionalGroup_extension_lite; -import static com.google.protobuf.UnittestLite.RepeatedGroup_extension_lite; - -import static com.google.protobuf.UnittestLite.packedInt32ExtensionLite; -import static com.google.protobuf.UnittestLite.packedInt64ExtensionLite; -import static com.google.protobuf.UnittestLite.packedUint32ExtensionLite; -import static com.google.protobuf.UnittestLite.packedUint64ExtensionLite; -import static com.google.protobuf.UnittestLite.packedSint32ExtensionLite; -import static com.google.protobuf.UnittestLite.packedSint64ExtensionLite; -import static com.google.protobuf.UnittestLite.packedFixed32ExtensionLite; -import static com.google.protobuf.UnittestLite.packedFixed64ExtensionLite; -import static com.google.protobuf.UnittestLite.packedSfixed32ExtensionLite; -import static com.google.protobuf.UnittestLite.packedSfixed64ExtensionLite; -import static com.google.protobuf.UnittestLite.packedFloatExtensionLite; -import static com.google.protobuf.UnittestLite.packedDoubleExtensionLite; -import static com.google.protobuf.UnittestLite.packedBoolExtensionLite; -import static com.google.protobuf.UnittestLite.packedEnumExtensionLite; +import static protobuf_unittest.UnittestProto.repeatedUint32Extension; +import static protobuf_unittest.UnittestProto.repeatedUint64Extension; +import com.google.protobuf.UnittestImportLite.ImportEnumLite; +import com.google.protobuf.UnittestLite.ForeignEnumLite; +import com.google.protobuf.UnittestLite.TestAllExtensionsLiteOrBuilder; +import com.google.protobuf.UnittestLite.TestAllTypesLite; +import com.google.protobuf.UnittestLite.TestPackedExtensionsLite; +import com.google.protobuf.test.UnittestImport.ImportEnum; +import com.google.protobuf.test.UnittestImport.ImportMessage; +import com.google.protobuf.test.UnittestImportPublic.PublicImportMessage; +import protobuf_unittest.UnittestProto; +import protobuf_unittest.UnittestProto.ForeignEnum; +import protobuf_unittest.UnittestProto.ForeignMessage; import protobuf_unittest.UnittestProto.TestAllExtensions; import protobuf_unittest.UnittestProto.TestAllExtensionsOrBuilder; import protobuf_unittest.UnittestProto.TestAllTypes; @@ -237,21 +232,6 @@ import protobuf_unittest.UnittestProto.TestOneof2; import protobuf_unittest.UnittestProto.TestPackedExtensions; import protobuf_unittest.UnittestProto.TestPackedTypes; import protobuf_unittest.UnittestProto.TestUnpackedTypes; -import protobuf_unittest.UnittestProto.ForeignMessage; -import protobuf_unittest.UnittestProto.ForeignEnum; -import com.google.protobuf.test.UnittestImport.ImportEnum; -import com.google.protobuf.test.UnittestImport.ImportMessage; -import com.google.protobuf.test.UnittestImportPublic.PublicImportMessage; - -import com.google.protobuf.UnittestLite.TestAllTypesLite; -import com.google.protobuf.UnittestLite.TestAllExtensionsLite; -import com.google.protobuf.UnittestLite.TestAllExtensionsLiteOrBuilder; -import com.google.protobuf.UnittestLite.TestPackedExtensionsLite; -import com.google.protobuf.UnittestLite.ForeignMessageLite; -import com.google.protobuf.UnittestLite.ForeignEnumLite; -import com.google.protobuf.UnittestImportLite.ImportEnumLite; -import com.google.protobuf.UnittestImportLite.ImportMessageLite; -import com.google.protobuf.UnittestImportPublicLite.PublicImportMessageLite; import junit.framework.Assert; @@ -309,12 +289,6 @@ public final class TestUtil { return builder.build(); } - public static TestAllExtensionsLite getAllLiteExtensionsSet() { - TestAllExtensionsLite.Builder builder = TestAllExtensionsLite.newBuilder(); - setAllExtensions(builder); - return builder.build(); - } - public static TestPackedTypes getPackedSet() { TestPackedTypes.Builder builder = TestPackedTypes.newBuilder(); setPackedFields(builder); @@ -333,13 +307,6 @@ public final class TestUtil { return builder.build(); } - public static TestPackedExtensionsLite getLitePackedExtensionsSet() { - TestPackedExtensionsLite.Builder builder = - TestPackedExtensionsLite.newBuilder(); - setPackedExtensions(builder); - return builder.build(); - } - /** * Set every field of {@code message} to the values expected by * {@code assertAllFieldsSet()}. @@ -1229,23 +1196,13 @@ public final class TestUtil { return registry.getUnmodifiable(); } - public static ExtensionRegistryLite getExtensionRegistryLite() { - ExtensionRegistryLite registry = ExtensionRegistryLite.newInstance(); - registerAllExtensionsLite(registry); - return registry.getUnmodifiable(); - } - /** * Register all of {@code TestAllExtensions}'s extensions with the * given {@link ExtensionRegistry}. */ public static void registerAllExtensions(ExtensionRegistry registry) { UnittestProto.registerAllExtensions(registry); - registerAllExtensionsLite(registry); - } - - public static void registerAllExtensionsLite(ExtensionRegistryLite registry) { - UnittestLite.registerAllExtensions(registry); + TestUtilLite.registerAllExtensionsLite(registry); } /** @@ -2039,195 +1996,6 @@ public final class TestUtil { // Lite extensions /** - * Set every field of {@code message} to the values expected by - * {@code assertAllExtensionsSet()}. - */ - public static void setAllExtensions(TestAllExtensionsLite.Builder message) { - message.setExtension(optionalInt32ExtensionLite , 101); - message.setExtension(optionalInt64ExtensionLite , 102L); - message.setExtension(optionalUint32ExtensionLite , 103); - message.setExtension(optionalUint64ExtensionLite , 104L); - message.setExtension(optionalSint32ExtensionLite , 105); - message.setExtension(optionalSint64ExtensionLite , 106L); - message.setExtension(optionalFixed32ExtensionLite , 107); - message.setExtension(optionalFixed64ExtensionLite , 108L); - message.setExtension(optionalSfixed32ExtensionLite, 109); - message.setExtension(optionalSfixed64ExtensionLite, 110L); - message.setExtension(optionalFloatExtensionLite , 111F); - message.setExtension(optionalDoubleExtensionLite , 112D); - message.setExtension(optionalBoolExtensionLite , true); - message.setExtension(optionalStringExtensionLite , "115"); - message.setExtension(optionalBytesExtensionLite , toBytes("116")); - - message.setExtension(optionalGroupExtensionLite, - OptionalGroup_extension_lite.newBuilder().setA(117).build()); - message.setExtension(optionalNestedMessageExtensionLite, - TestAllTypesLite.NestedMessage.newBuilder().setBb(118).build()); - message.setExtension(optionalForeignMessageExtensionLite, - ForeignMessageLite.newBuilder().setC(119).build()); - message.setExtension(optionalImportMessageExtensionLite, - ImportMessageLite.newBuilder().setD(120).build()); - message.setExtension(optionalPublicImportMessageExtensionLite, - PublicImportMessageLite.newBuilder().setE(126).build()); - message.setExtension(optionalLazyMessageExtensionLite, - TestAllTypesLite.NestedMessage.newBuilder().setBb(127).build()); - - message.setExtension(optionalNestedEnumExtensionLite, TestAllTypesLite.NestedEnum.BAZ); - message.setExtension(optionalForeignEnumExtensionLite, ForeignEnumLite.FOREIGN_LITE_BAZ); - message.setExtension(optionalImportEnumExtensionLite, ImportEnumLite.IMPORT_LITE_BAZ); - - message.setExtension(optionalStringPieceExtensionLite, "124"); - message.setExtension(optionalCordExtensionLite, "125"); - - // ----------------------------------------------------------------- - - message.addExtension(repeatedInt32ExtensionLite , 201); - message.addExtension(repeatedInt64ExtensionLite , 202L); - message.addExtension(repeatedUint32ExtensionLite , 203); - message.addExtension(repeatedUint64ExtensionLite , 204L); - message.addExtension(repeatedSint32ExtensionLite , 205); - message.addExtension(repeatedSint64ExtensionLite , 206L); - message.addExtension(repeatedFixed32ExtensionLite , 207); - message.addExtension(repeatedFixed64ExtensionLite , 208L); - message.addExtension(repeatedSfixed32ExtensionLite, 209); - message.addExtension(repeatedSfixed64ExtensionLite, 210L); - message.addExtension(repeatedFloatExtensionLite , 211F); - message.addExtension(repeatedDoubleExtensionLite , 212D); - message.addExtension(repeatedBoolExtensionLite , true); - message.addExtension(repeatedStringExtensionLite , "215"); - message.addExtension(repeatedBytesExtensionLite , toBytes("216")); - - message.addExtension(repeatedGroupExtensionLite, - RepeatedGroup_extension_lite.newBuilder().setA(217).build()); - message.addExtension(repeatedNestedMessageExtensionLite, - TestAllTypesLite.NestedMessage.newBuilder().setBb(218).build()); - message.addExtension(repeatedForeignMessageExtensionLite, - ForeignMessageLite.newBuilder().setC(219).build()); - message.addExtension(repeatedImportMessageExtensionLite, - ImportMessageLite.newBuilder().setD(220).build()); - message.addExtension(repeatedLazyMessageExtensionLite, - TestAllTypesLite.NestedMessage.newBuilder().setBb(227).build()); - - message.addExtension(repeatedNestedEnumExtensionLite, TestAllTypesLite.NestedEnum.BAR); - message.addExtension(repeatedForeignEnumExtensionLite, ForeignEnumLite.FOREIGN_LITE_BAR); - message.addExtension(repeatedImportEnumExtensionLite, ImportEnumLite.IMPORT_LITE_BAR); - - message.addExtension(repeatedStringPieceExtensionLite, "224"); - message.addExtension(repeatedCordExtensionLite, "225"); - - // Add a second one of each field. - message.addExtension(repeatedInt32ExtensionLite , 301); - message.addExtension(repeatedInt64ExtensionLite , 302L); - message.addExtension(repeatedUint32ExtensionLite , 303); - message.addExtension(repeatedUint64ExtensionLite , 304L); - message.addExtension(repeatedSint32ExtensionLite , 305); - message.addExtension(repeatedSint64ExtensionLite , 306L); - message.addExtension(repeatedFixed32ExtensionLite , 307); - message.addExtension(repeatedFixed64ExtensionLite , 308L); - message.addExtension(repeatedSfixed32ExtensionLite, 309); - message.addExtension(repeatedSfixed64ExtensionLite, 310L); - message.addExtension(repeatedFloatExtensionLite , 311F); - message.addExtension(repeatedDoubleExtensionLite , 312D); - message.addExtension(repeatedBoolExtensionLite , false); - message.addExtension(repeatedStringExtensionLite , "315"); - message.addExtension(repeatedBytesExtensionLite , toBytes("316")); - - message.addExtension(repeatedGroupExtensionLite, - RepeatedGroup_extension_lite.newBuilder().setA(317).build()); - message.addExtension(repeatedNestedMessageExtensionLite, - TestAllTypesLite.NestedMessage.newBuilder().setBb(318).build()); - message.addExtension(repeatedForeignMessageExtensionLite, - ForeignMessageLite.newBuilder().setC(319).build()); - message.addExtension(repeatedImportMessageExtensionLite, - ImportMessageLite.newBuilder().setD(320).build()); - message.addExtension(repeatedLazyMessageExtensionLite, - TestAllTypesLite.NestedMessage.newBuilder().setBb(327).build()); - - message.addExtension(repeatedNestedEnumExtensionLite, TestAllTypesLite.NestedEnum.BAZ); - message.addExtension(repeatedForeignEnumExtensionLite, ForeignEnumLite.FOREIGN_LITE_BAZ); - message.addExtension(repeatedImportEnumExtensionLite, ImportEnumLite.IMPORT_LITE_BAZ); - - message.addExtension(repeatedStringPieceExtensionLite, "324"); - message.addExtension(repeatedCordExtensionLite, "325"); - - // ----------------------------------------------------------------- - - message.setExtension(defaultInt32ExtensionLite , 401); - message.setExtension(defaultInt64ExtensionLite , 402L); - message.setExtension(defaultUint32ExtensionLite , 403); - message.setExtension(defaultUint64ExtensionLite , 404L); - message.setExtension(defaultSint32ExtensionLite , 405); - message.setExtension(defaultSint64ExtensionLite , 406L); - message.setExtension(defaultFixed32ExtensionLite , 407); - message.setExtension(defaultFixed64ExtensionLite , 408L); - message.setExtension(defaultSfixed32ExtensionLite, 409); - message.setExtension(defaultSfixed64ExtensionLite, 410L); - message.setExtension(defaultFloatExtensionLite , 411F); - message.setExtension(defaultDoubleExtensionLite , 412D); - message.setExtension(defaultBoolExtensionLite , false); - message.setExtension(defaultStringExtensionLite , "415"); - message.setExtension(defaultBytesExtensionLite , toBytes("416")); - - message.setExtension(defaultNestedEnumExtensionLite, TestAllTypesLite.NestedEnum.FOO); - message.setExtension(defaultForeignEnumExtensionLite, ForeignEnumLite.FOREIGN_LITE_FOO); - message.setExtension(defaultImportEnumExtensionLite, ImportEnumLite.IMPORT_LITE_FOO); - - 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")); - } - - // ------------------------------------------------------------------- - - /** - * Modify the repeated extensions of {@code message} to contain the values - * expected by {@code assertRepeatedExtensionsModified()}. - */ - public static void modifyRepeatedExtensions( - TestAllExtensionsLite.Builder message) { - message.setExtension(repeatedInt32ExtensionLite , 1, 501); - message.setExtension(repeatedInt64ExtensionLite , 1, 502L); - message.setExtension(repeatedUint32ExtensionLite , 1, 503); - message.setExtension(repeatedUint64ExtensionLite , 1, 504L); - message.setExtension(repeatedSint32ExtensionLite , 1, 505); - message.setExtension(repeatedSint64ExtensionLite , 1, 506L); - message.setExtension(repeatedFixed32ExtensionLite , 1, 507); - message.setExtension(repeatedFixed64ExtensionLite , 1, 508L); - message.setExtension(repeatedSfixed32ExtensionLite, 1, 509); - message.setExtension(repeatedSfixed64ExtensionLite, 1, 510L); - message.setExtension(repeatedFloatExtensionLite , 1, 511F); - message.setExtension(repeatedDoubleExtensionLite , 1, 512D); - message.setExtension(repeatedBoolExtensionLite , 1, true); - message.setExtension(repeatedStringExtensionLite , 1, "515"); - message.setExtension(repeatedBytesExtensionLite , 1, toBytes("516")); - - message.setExtension(repeatedGroupExtensionLite, 1, - RepeatedGroup_extension_lite.newBuilder().setA(517).build()); - message.setExtension(repeatedNestedMessageExtensionLite, 1, - TestAllTypesLite.NestedMessage.newBuilder().setBb(518).build()); - message.setExtension(repeatedForeignMessageExtensionLite, 1, - ForeignMessageLite.newBuilder().setC(519).build()); - message.setExtension(repeatedImportMessageExtensionLite, 1, - ImportMessageLite.newBuilder().setD(520).build()); - message.setExtension(repeatedLazyMessageExtensionLite, 1, - TestAllTypesLite.NestedMessage.newBuilder().setBb(527).build()); - - message.setExtension(repeatedNestedEnumExtensionLite , 1, TestAllTypesLite.NestedEnum.FOO); - message.setExtension(repeatedForeignEnumExtensionLite, 1, ForeignEnumLite.FOREIGN_LITE_FOO); - message.setExtension(repeatedImportEnumExtensionLite , 1, ImportEnumLite.IMPORT_LITE_FOO); - - message.setExtension(repeatedStringPieceExtensionLite, 1, "524"); - message.setExtension(repeatedCordExtensionLite, 1, "525"); - } - - // ------------------------------------------------------------------- - - /** * Assert (using {@code junit.framework.Assert}} that all extensions of * {@code message} are set to the values assigned by {@code setAllExtensions}. */ @@ -2726,38 +2494,6 @@ public final class TestUtil { assertEqualsExactType("525", message.getExtension(repeatedCordExtensionLite, 1)); } - public static void setPackedExtensions(TestPackedExtensionsLite.Builder message) { - message.addExtension(packedInt32ExtensionLite , 601); - message.addExtension(packedInt64ExtensionLite , 602L); - message.addExtension(packedUint32ExtensionLite , 603); - message.addExtension(packedUint64ExtensionLite , 604L); - message.addExtension(packedSint32ExtensionLite , 605); - message.addExtension(packedSint64ExtensionLite , 606L); - message.addExtension(packedFixed32ExtensionLite , 607); - message.addExtension(packedFixed64ExtensionLite , 608L); - message.addExtension(packedSfixed32ExtensionLite, 609); - message.addExtension(packedSfixed64ExtensionLite, 610L); - message.addExtension(packedFloatExtensionLite , 611F); - message.addExtension(packedDoubleExtensionLite , 612D); - message.addExtension(packedBoolExtensionLite , true); - message.addExtension(packedEnumExtensionLite, ForeignEnumLite.FOREIGN_LITE_BAR); - // Add a second one of each field. - message.addExtension(packedInt32ExtensionLite , 701); - message.addExtension(packedInt64ExtensionLite , 702L); - message.addExtension(packedUint32ExtensionLite , 703); - message.addExtension(packedUint64ExtensionLite , 704L); - message.addExtension(packedSint32ExtensionLite , 705); - message.addExtension(packedSint64ExtensionLite , 706L); - message.addExtension(packedFixed32ExtensionLite , 707); - message.addExtension(packedFixed64ExtensionLite , 708L); - message.addExtension(packedSfixed32ExtensionLite, 709); - message.addExtension(packedSfixed64ExtensionLite, 710L); - message.addExtension(packedFloatExtensionLite , 711F); - message.addExtension(packedDoubleExtensionLite , 712D); - message.addExtension(packedBoolExtensionLite , false); - message.addExtension(packedEnumExtensionLite, ForeignEnumLite.FOREIGN_LITE_BAZ); - } - public static void assertPackedExtensionsSet(TestPackedExtensionsLite message) { Assert.assertEquals(2, message.getExtensionCount(packedInt32ExtensionLite )); Assert.assertEquals(2, message.getExtensionCount(packedInt64ExtensionLite )); @@ -4109,7 +3845,7 @@ public final class TestUtil { private int invalidations; - //@Override (Java 1.6 override semantics, but we must support 1.5) + @Override public void markDirty() { invalidations++; } diff --git a/java/core/src/test/java/com/google/protobuf/TestUtilLite.java b/java/core/src/test/java/com/google/protobuf/TestUtilLite.java new file mode 100644 index 00000000..8f33fa14 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/TestUtilLite.java @@ -0,0 +1,559 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// 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 static com.google.protobuf.UnittestLite.OptionalGroup_extension_lite; +import static com.google.protobuf.UnittestLite.RepeatedGroup_extension_lite; +import static com.google.protobuf.UnittestLite.defaultBoolExtensionLite; +import static com.google.protobuf.UnittestLite.defaultBytesExtensionLite; +import static com.google.protobuf.UnittestLite.defaultCordExtensionLite; +import static com.google.protobuf.UnittestLite.defaultDoubleExtensionLite; +import static com.google.protobuf.UnittestLite.defaultFixed32ExtensionLite; +import static com.google.protobuf.UnittestLite.defaultFixed64ExtensionLite; +import static com.google.protobuf.UnittestLite.defaultFloatExtensionLite; +import static com.google.protobuf.UnittestLite.defaultForeignEnumExtensionLite; +import static com.google.protobuf.UnittestLite.defaultImportEnumExtensionLite; +import static com.google.protobuf.UnittestLite.defaultInt32ExtensionLite; +import static com.google.protobuf.UnittestLite.defaultInt64ExtensionLite; +import static com.google.protobuf.UnittestLite.defaultNestedEnumExtensionLite; +import static com.google.protobuf.UnittestLite.defaultSfixed32ExtensionLite; +import static com.google.protobuf.UnittestLite.defaultSfixed64ExtensionLite; +import static com.google.protobuf.UnittestLite.defaultSint32ExtensionLite; +import static com.google.protobuf.UnittestLite.defaultSint64ExtensionLite; +import static com.google.protobuf.UnittestLite.defaultStringExtensionLite; +import static com.google.protobuf.UnittestLite.defaultStringPieceExtensionLite; +import static com.google.protobuf.UnittestLite.defaultUint32ExtensionLite; +import static com.google.protobuf.UnittestLite.defaultUint64ExtensionLite; +import static com.google.protobuf.UnittestLite.oneofBytesExtensionLite; +import static com.google.protobuf.UnittestLite.oneofNestedMessageExtensionLite; +import static com.google.protobuf.UnittestLite.oneofStringExtensionLite; +import static com.google.protobuf.UnittestLite.oneofUint32ExtensionLite; +import static com.google.protobuf.UnittestLite.optionalBoolExtensionLite; +import static com.google.protobuf.UnittestLite.optionalBytesExtensionLite; +import static com.google.protobuf.UnittestLite.optionalCordExtensionLite; +import static com.google.protobuf.UnittestLite.optionalDoubleExtensionLite; +import static com.google.protobuf.UnittestLite.optionalFixed32ExtensionLite; +import static com.google.protobuf.UnittestLite.optionalFixed64ExtensionLite; +import static com.google.protobuf.UnittestLite.optionalFloatExtensionLite; +import static com.google.protobuf.UnittestLite.optionalForeignEnumExtensionLite; +import static com.google.protobuf.UnittestLite.optionalForeignMessageExtensionLite; +import static com.google.protobuf.UnittestLite.optionalGroupExtensionLite; +import static com.google.protobuf.UnittestLite.optionalImportEnumExtensionLite; +import static com.google.protobuf.UnittestLite.optionalImportMessageExtensionLite; +import static com.google.protobuf.UnittestLite.optionalInt32ExtensionLite; +import static com.google.protobuf.UnittestLite.optionalInt64ExtensionLite; +import static com.google.protobuf.UnittestLite.optionalLazyMessageExtensionLite; +import static com.google.protobuf.UnittestLite.optionalNestedEnumExtensionLite; +import static com.google.protobuf.UnittestLite.optionalNestedMessageExtensionLite; +import static com.google.protobuf.UnittestLite.optionalPublicImportMessageExtensionLite; +import static com.google.protobuf.UnittestLite.optionalSfixed32ExtensionLite; +import static com.google.protobuf.UnittestLite.optionalSfixed64ExtensionLite; +import static com.google.protobuf.UnittestLite.optionalSint32ExtensionLite; +import static com.google.protobuf.UnittestLite.optionalSint64ExtensionLite; +import static com.google.protobuf.UnittestLite.optionalStringExtensionLite; +import static com.google.protobuf.UnittestLite.optionalStringPieceExtensionLite; +import static com.google.protobuf.UnittestLite.optionalUint32ExtensionLite; +import static com.google.protobuf.UnittestLite.optionalUint64ExtensionLite; +import static com.google.protobuf.UnittestLite.packedBoolExtensionLite; +import static com.google.protobuf.UnittestLite.packedDoubleExtensionLite; +import static com.google.protobuf.UnittestLite.packedEnumExtensionLite; +import static com.google.protobuf.UnittestLite.packedFixed32ExtensionLite; +import static com.google.protobuf.UnittestLite.packedFixed64ExtensionLite; +import static com.google.protobuf.UnittestLite.packedFloatExtensionLite; +import static com.google.protobuf.UnittestLite.packedInt32ExtensionLite; +import static com.google.protobuf.UnittestLite.packedInt64ExtensionLite; +import static com.google.protobuf.UnittestLite.packedSfixed32ExtensionLite; +import static com.google.protobuf.UnittestLite.packedSfixed64ExtensionLite; +import static com.google.protobuf.UnittestLite.packedSint32ExtensionLite; +import static com.google.protobuf.UnittestLite.packedSint64ExtensionLite; +import static com.google.protobuf.UnittestLite.packedUint32ExtensionLite; +import static com.google.protobuf.UnittestLite.packedUint64ExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedBoolExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedBytesExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedCordExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedDoubleExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedFixed32ExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedFixed64ExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedFloatExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedForeignEnumExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedForeignMessageExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedGroupExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedImportEnumExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedImportMessageExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedInt32ExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedInt64ExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedLazyMessageExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedNestedEnumExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedNestedMessageExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedSfixed32ExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedSfixed64ExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedSint32ExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedSint64ExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedStringExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedStringPieceExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedUint32ExtensionLite; +import static com.google.protobuf.UnittestLite.repeatedUint64ExtensionLite; + +import com.google.protobuf.UnittestImportLite.ImportEnumLite; +import com.google.protobuf.UnittestImportLite.ImportMessageLite; +import com.google.protobuf.UnittestImportPublicLite.PublicImportMessageLite; +import com.google.protobuf.UnittestLite.ForeignEnumLite; +import com.google.protobuf.UnittestLite.ForeignMessageLite; +import com.google.protobuf.UnittestLite.TestAllExtensionsLite; +import com.google.protobuf.UnittestLite.TestAllTypesLite; +import com.google.protobuf.UnittestLite.TestPackedExtensionsLite; + +/** + * Contains methods for setting fields of {@code TestAllTypesLite}, {@code TestAllExtensionsLite}, + * and {@code TestPackedExtensionsLite}. This is analogous to the functionality in TestUtil.java but + * does not depend on the presence of any non-lite protos. + * + * <p>This code is not to be used outside of {@code com.google.protobuf} and + * subpackages. + */ +public final class TestUtilLite { + private TestUtilLite() {} + + /** Helper to convert a String to ByteString. */ + static ByteString toBytes(String str) { + return ByteString.copyFrom(str.getBytes(Internal.UTF_8)); + } + + /** + * Get a {@code TestAllTypesLite.Builder} with all fields set as they would be by + * {@link #setAllFields(TestAllTypesLite.Builder)}. + */ + public static TestAllTypesLite.Builder getAllLiteSetBuilder() { + TestAllTypesLite.Builder builder = TestAllTypesLite.newBuilder(); + setAllFields(builder); + return builder; + } + + /** + * Get a {@code TestAllExtensionsLite} with all fields set as they would be by + * {@link #setAllExtensions(TestAllExtensionsLite.Builder)}. + */ + public static TestAllExtensionsLite getAllLiteExtensionsSet() { + TestAllExtensionsLite.Builder builder = TestAllExtensionsLite.newBuilder(); + setAllExtensions(builder); + return builder.build(); + } + + public static TestPackedExtensionsLite getLitePackedExtensionsSet() { + TestPackedExtensionsLite.Builder builder = TestPackedExtensionsLite.newBuilder(); + setPackedExtensions(builder); + return builder.build(); + } + + /** + * Set every field of {@code builder} to the values expected by + * {@code assertAllFieldsSet()}. + */ + public static void setAllFields(TestAllTypesLite.Builder builder) { + builder.setOptionalInt32 (101); + builder.setOptionalInt64 (102); + builder.setOptionalUint32 (103); + builder.setOptionalUint64 (104); + builder.setOptionalSint32 (105); + builder.setOptionalSint64 (106); + builder.setOptionalFixed32 (107); + builder.setOptionalFixed64 (108); + builder.setOptionalSfixed32(109); + builder.setOptionalSfixed64(110); + builder.setOptionalFloat (111); + builder.setOptionalDouble (112); + builder.setOptionalBool (true); + builder.setOptionalString ("115"); + builder.setOptionalBytes (toBytes("116")); + + builder.setOptionalGroup( + TestAllTypesLite.OptionalGroup.newBuilder().setA(117).build()); + builder.setOptionalNestedMessage( + TestAllTypesLite.NestedMessage.newBuilder().setBb(118).build()); + builder.setOptionalForeignMessage( + ForeignMessageLite.newBuilder().setC(119).build()); + builder.setOptionalImportMessage( + ImportMessageLite.newBuilder().setD(120).build()); + builder.setOptionalPublicImportMessage( + PublicImportMessageLite.newBuilder().setE(126).build()); + builder.setOptionalLazyMessage( + TestAllTypesLite.NestedMessage.newBuilder().setBb(127).build()); + + builder.setOptionalNestedEnum (TestAllTypesLite.NestedEnum.BAZ); + builder.setOptionalForeignEnum(ForeignEnumLite.FOREIGN_LITE_BAZ); + builder.setOptionalImportEnum (ImportEnumLite.IMPORT_LITE_BAZ); + + builder.setOptionalStringPiece("124"); + builder.setOptionalCord("125"); + + // ----------------------------------------------------------------- + + builder.addRepeatedInt32 (201); + builder.addRepeatedInt64 (202); + builder.addRepeatedUint32 (203); + builder.addRepeatedUint64 (204); + builder.addRepeatedSint32 (205); + builder.addRepeatedSint64 (206); + builder.addRepeatedFixed32 (207); + builder.addRepeatedFixed64 (208); + builder.addRepeatedSfixed32(209); + builder.addRepeatedSfixed64(210); + builder.addRepeatedFloat (211); + builder.addRepeatedDouble (212); + builder.addRepeatedBool (true); + builder.addRepeatedString ("215"); + builder.addRepeatedBytes (toBytes("216")); + + builder.addRepeatedGroup( + TestAllTypesLite.RepeatedGroup.newBuilder().setA(217).build()); + builder.addRepeatedNestedMessage( + TestAllTypesLite.NestedMessage.newBuilder().setBb(218).build()); + builder.addRepeatedForeignMessage( + ForeignMessageLite.newBuilder().setC(219).build()); + builder.addRepeatedImportMessage( + ImportMessageLite.newBuilder().setD(220).build()); + builder.addRepeatedLazyMessage( + TestAllTypesLite.NestedMessage.newBuilder().setBb(227).build()); + + builder.addRepeatedNestedEnum (TestAllTypesLite.NestedEnum.BAR); + builder.addRepeatedForeignEnum(ForeignEnumLite.FOREIGN_LITE_BAR); + builder.addRepeatedImportEnum (ImportEnumLite.IMPORT_LITE_BAR); + + builder.addRepeatedStringPiece("224"); + builder.addRepeatedCord("225"); + + // Add a second one of each field. + builder.addRepeatedInt32 (301); + builder.addRepeatedInt64 (302); + builder.addRepeatedUint32 (303); + builder.addRepeatedUint64 (304); + builder.addRepeatedSint32 (305); + builder.addRepeatedSint64 (306); + builder.addRepeatedFixed32 (307); + builder.addRepeatedFixed64 (308); + builder.addRepeatedSfixed32(309); + builder.addRepeatedSfixed64(310); + builder.addRepeatedFloat (311); + builder.addRepeatedDouble (312); + builder.addRepeatedBool (false); + builder.addRepeatedString ("315"); + builder.addRepeatedBytes (toBytes("316")); + + builder.addRepeatedGroup( + TestAllTypesLite.RepeatedGroup.newBuilder().setA(317).build()); + builder.addRepeatedNestedMessage( + TestAllTypesLite.NestedMessage.newBuilder().setBb(318).build()); + builder.addRepeatedForeignMessage( + ForeignMessageLite.newBuilder().setC(319).build()); + builder.addRepeatedImportMessage( + ImportMessageLite.newBuilder().setD(320).build()); + builder.addRepeatedLazyMessage( + TestAllTypesLite.NestedMessage.newBuilder().setBb(327).build()); + + builder.addRepeatedNestedEnum (TestAllTypesLite.NestedEnum.BAZ); + builder.addRepeatedForeignEnum(ForeignEnumLite.FOREIGN_LITE_BAZ); + builder.addRepeatedImportEnum (ImportEnumLite.IMPORT_LITE_BAZ); + + builder.addRepeatedStringPiece("324"); + builder.addRepeatedCord("325"); + + // ----------------------------------------------------------------- + + builder.setDefaultInt32 (401); + builder.setDefaultInt64 (402); + builder.setDefaultUint32 (403); + builder.setDefaultUint64 (404); + builder.setDefaultSint32 (405); + builder.setDefaultSint64 (406); + builder.setDefaultFixed32 (407); + builder.setDefaultFixed64 (408); + builder.setDefaultSfixed32(409); + builder.setDefaultSfixed64(410); + builder.setDefaultFloat (411); + builder.setDefaultDouble (412); + builder.setDefaultBool (false); + builder.setDefaultString ("415"); + builder.setDefaultBytes (toBytes("416")); + + builder.setDefaultNestedEnum (TestAllTypesLite.NestedEnum.FOO); + builder.setDefaultForeignEnum(ForeignEnumLite.FOREIGN_LITE_FOO); + builder.setDefaultImportEnum (ImportEnumLite.IMPORT_LITE_FOO); + + builder.setDefaultStringPiece("424"); + builder.setDefaultCord("425"); + + builder.setOneofUint32(601); + builder.setOneofNestedMessage( + TestAllTypesLite.NestedMessage.newBuilder().setBb(602).build()); + builder.setOneofString("603"); + builder.setOneofBytes(toBytes("604")); + } + + /** + * Get an unmodifiable {@link ExtensionRegistryLite} containing all the + * extensions of {@code TestAllExtensionsLite}. + */ + public static ExtensionRegistryLite getExtensionRegistryLite() { + ExtensionRegistryLite registry = ExtensionRegistryLite.newInstance(); + registerAllExtensionsLite(registry); + return registry.getUnmodifiable(); + } + + /** + * Register all of {@code TestAllExtensionsLite}'s extensions with the + * given {@link ExtensionRegistryLite}. + */ + public static void registerAllExtensionsLite(ExtensionRegistryLite registry) { + UnittestLite.registerAllExtensions(registry); + } + + // =================================================================== + // Lite extensions + + /** + * Set every field of {@code message} to the values expected by + * {@code assertAllExtensionsSet()}. + */ + public static void setAllExtensions(TestAllExtensionsLite.Builder message) { + message.setExtension(optionalInt32ExtensionLite , 101); + message.setExtension(optionalInt64ExtensionLite , 102L); + message.setExtension(optionalUint32ExtensionLite , 103); + message.setExtension(optionalUint64ExtensionLite , 104L); + message.setExtension(optionalSint32ExtensionLite , 105); + message.setExtension(optionalSint64ExtensionLite , 106L); + message.setExtension(optionalFixed32ExtensionLite , 107); + message.setExtension(optionalFixed64ExtensionLite , 108L); + message.setExtension(optionalSfixed32ExtensionLite, 109); + message.setExtension(optionalSfixed64ExtensionLite, 110L); + message.setExtension(optionalFloatExtensionLite , 111F); + message.setExtension(optionalDoubleExtensionLite , 112D); + message.setExtension(optionalBoolExtensionLite , true); + message.setExtension(optionalStringExtensionLite , "115"); + message.setExtension(optionalBytesExtensionLite , toBytes("116")); + + message.setExtension(optionalGroupExtensionLite, + OptionalGroup_extension_lite.newBuilder().setA(117).build()); + message.setExtension(optionalNestedMessageExtensionLite, + TestAllTypesLite.NestedMessage.newBuilder().setBb(118).build()); + message.setExtension(optionalForeignMessageExtensionLite, + ForeignMessageLite.newBuilder().setC(119).build()); + message.setExtension(optionalImportMessageExtensionLite, + ImportMessageLite.newBuilder().setD(120).build()); + message.setExtension(optionalPublicImportMessageExtensionLite, + PublicImportMessageLite.newBuilder().setE(126).build()); + message.setExtension(optionalLazyMessageExtensionLite, + TestAllTypesLite.NestedMessage.newBuilder().setBb(127).build()); + + message.setExtension(optionalNestedEnumExtensionLite, TestAllTypesLite.NestedEnum.BAZ); + message.setExtension(optionalForeignEnumExtensionLite, ForeignEnumLite.FOREIGN_LITE_BAZ); + message.setExtension(optionalImportEnumExtensionLite, ImportEnumLite.IMPORT_LITE_BAZ); + + message.setExtension(optionalStringPieceExtensionLite, "124"); + message.setExtension(optionalCordExtensionLite, "125"); + + // ----------------------------------------------------------------- + + message.addExtension(repeatedInt32ExtensionLite , 201); + message.addExtension(repeatedInt64ExtensionLite , 202L); + message.addExtension(repeatedUint32ExtensionLite , 203); + message.addExtension(repeatedUint64ExtensionLite , 204L); + message.addExtension(repeatedSint32ExtensionLite , 205); + message.addExtension(repeatedSint64ExtensionLite , 206L); + message.addExtension(repeatedFixed32ExtensionLite , 207); + message.addExtension(repeatedFixed64ExtensionLite , 208L); + message.addExtension(repeatedSfixed32ExtensionLite, 209); + message.addExtension(repeatedSfixed64ExtensionLite, 210L); + message.addExtension(repeatedFloatExtensionLite , 211F); + message.addExtension(repeatedDoubleExtensionLite , 212D); + message.addExtension(repeatedBoolExtensionLite , true); + message.addExtension(repeatedStringExtensionLite , "215"); + message.addExtension(repeatedBytesExtensionLite , toBytes("216")); + + message.addExtension(repeatedGroupExtensionLite, + RepeatedGroup_extension_lite.newBuilder().setA(217).build()); + message.addExtension(repeatedNestedMessageExtensionLite, + TestAllTypesLite.NestedMessage.newBuilder().setBb(218).build()); + message.addExtension(repeatedForeignMessageExtensionLite, + ForeignMessageLite.newBuilder().setC(219).build()); + message.addExtension(repeatedImportMessageExtensionLite, + ImportMessageLite.newBuilder().setD(220).build()); + message.addExtension(repeatedLazyMessageExtensionLite, + TestAllTypesLite.NestedMessage.newBuilder().setBb(227).build()); + + message.addExtension(repeatedNestedEnumExtensionLite, TestAllTypesLite.NestedEnum.BAR); + message.addExtension(repeatedForeignEnumExtensionLite, ForeignEnumLite.FOREIGN_LITE_BAR); + message.addExtension(repeatedImportEnumExtensionLite, ImportEnumLite.IMPORT_LITE_BAR); + + message.addExtension(repeatedStringPieceExtensionLite, "224"); + message.addExtension(repeatedCordExtensionLite, "225"); + + // Add a second one of each field. + message.addExtension(repeatedInt32ExtensionLite , 301); + message.addExtension(repeatedInt64ExtensionLite , 302L); + message.addExtension(repeatedUint32ExtensionLite , 303); + message.addExtension(repeatedUint64ExtensionLite , 304L); + message.addExtension(repeatedSint32ExtensionLite , 305); + message.addExtension(repeatedSint64ExtensionLite , 306L); + message.addExtension(repeatedFixed32ExtensionLite , 307); + message.addExtension(repeatedFixed64ExtensionLite , 308L); + message.addExtension(repeatedSfixed32ExtensionLite, 309); + message.addExtension(repeatedSfixed64ExtensionLite, 310L); + message.addExtension(repeatedFloatExtensionLite , 311F); + message.addExtension(repeatedDoubleExtensionLite , 312D); + message.addExtension(repeatedBoolExtensionLite , false); + message.addExtension(repeatedStringExtensionLite , "315"); + message.addExtension(repeatedBytesExtensionLite , toBytes("316")); + + message.addExtension(repeatedGroupExtensionLite, + RepeatedGroup_extension_lite.newBuilder().setA(317).build()); + message.addExtension(repeatedNestedMessageExtensionLite, + TestAllTypesLite.NestedMessage.newBuilder().setBb(318).build()); + message.addExtension(repeatedForeignMessageExtensionLite, + ForeignMessageLite.newBuilder().setC(319).build()); + message.addExtension(repeatedImportMessageExtensionLite, + ImportMessageLite.newBuilder().setD(320).build()); + message.addExtension(repeatedLazyMessageExtensionLite, + TestAllTypesLite.NestedMessage.newBuilder().setBb(327).build()); + + message.addExtension(repeatedNestedEnumExtensionLite, TestAllTypesLite.NestedEnum.BAZ); + message.addExtension(repeatedForeignEnumExtensionLite, ForeignEnumLite.FOREIGN_LITE_BAZ); + message.addExtension(repeatedImportEnumExtensionLite, ImportEnumLite.IMPORT_LITE_BAZ); + + message.addExtension(repeatedStringPieceExtensionLite, "324"); + message.addExtension(repeatedCordExtensionLite, "325"); + + // ----------------------------------------------------------------- + + message.setExtension(defaultInt32ExtensionLite , 401); + message.setExtension(defaultInt64ExtensionLite , 402L); + message.setExtension(defaultUint32ExtensionLite , 403); + message.setExtension(defaultUint64ExtensionLite , 404L); + message.setExtension(defaultSint32ExtensionLite , 405); + message.setExtension(defaultSint64ExtensionLite , 406L); + message.setExtension(defaultFixed32ExtensionLite , 407); + message.setExtension(defaultFixed64ExtensionLite , 408L); + message.setExtension(defaultSfixed32ExtensionLite, 409); + message.setExtension(defaultSfixed64ExtensionLite, 410L); + message.setExtension(defaultFloatExtensionLite , 411F); + message.setExtension(defaultDoubleExtensionLite , 412D); + message.setExtension(defaultBoolExtensionLite , false); + message.setExtension(defaultStringExtensionLite , "415"); + message.setExtension(defaultBytesExtensionLite , toBytes("416")); + + message.setExtension(defaultNestedEnumExtensionLite, TestAllTypesLite.NestedEnum.FOO); + message.setExtension(defaultForeignEnumExtensionLite, ForeignEnumLite.FOREIGN_LITE_FOO); + message.setExtension(defaultImportEnumExtensionLite, ImportEnumLite.IMPORT_LITE_FOO); + + 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")); + } + + // ------------------------------------------------------------------- + + /** + * Modify the repeated extensions of {@code message} to contain the values + * expected by {@code assertRepeatedExtensionsModified()}. + */ + public static void modifyRepeatedExtensions( + TestAllExtensionsLite.Builder message) { + message.setExtension(repeatedInt32ExtensionLite , 1, 501); + message.setExtension(repeatedInt64ExtensionLite , 1, 502L); + message.setExtension(repeatedUint32ExtensionLite , 1, 503); + message.setExtension(repeatedUint64ExtensionLite , 1, 504L); + message.setExtension(repeatedSint32ExtensionLite , 1, 505); + message.setExtension(repeatedSint64ExtensionLite , 1, 506L); + message.setExtension(repeatedFixed32ExtensionLite , 1, 507); + message.setExtension(repeatedFixed64ExtensionLite , 1, 508L); + message.setExtension(repeatedSfixed32ExtensionLite, 1, 509); + message.setExtension(repeatedSfixed64ExtensionLite, 1, 510L); + message.setExtension(repeatedFloatExtensionLite , 1, 511F); + message.setExtension(repeatedDoubleExtensionLite , 1, 512D); + message.setExtension(repeatedBoolExtensionLite , 1, true); + message.setExtension(repeatedStringExtensionLite , 1, "515"); + message.setExtension(repeatedBytesExtensionLite , 1, toBytes("516")); + + message.setExtension(repeatedGroupExtensionLite, 1, + RepeatedGroup_extension_lite.newBuilder().setA(517).build()); + message.setExtension(repeatedNestedMessageExtensionLite, 1, + TestAllTypesLite.NestedMessage.newBuilder().setBb(518).build()); + message.setExtension(repeatedForeignMessageExtensionLite, 1, + ForeignMessageLite.newBuilder().setC(519).build()); + message.setExtension(repeatedImportMessageExtensionLite, 1, + ImportMessageLite.newBuilder().setD(520).build()); + message.setExtension(repeatedLazyMessageExtensionLite, 1, + TestAllTypesLite.NestedMessage.newBuilder().setBb(527).build()); + + message.setExtension(repeatedNestedEnumExtensionLite , 1, TestAllTypesLite.NestedEnum.FOO); + message.setExtension(repeatedForeignEnumExtensionLite, 1, ForeignEnumLite.FOREIGN_LITE_FOO); + message.setExtension(repeatedImportEnumExtensionLite , 1, ImportEnumLite.IMPORT_LITE_FOO); + + message.setExtension(repeatedStringPieceExtensionLite, 1, "524"); + message.setExtension(repeatedCordExtensionLite, 1, "525"); + } + + public static void setPackedExtensions(TestPackedExtensionsLite.Builder message) { + message.addExtension(packedInt32ExtensionLite , 601); + message.addExtension(packedInt64ExtensionLite , 602L); + message.addExtension(packedUint32ExtensionLite , 603); + message.addExtension(packedUint64ExtensionLite , 604L); + message.addExtension(packedSint32ExtensionLite , 605); + message.addExtension(packedSint64ExtensionLite , 606L); + message.addExtension(packedFixed32ExtensionLite , 607); + message.addExtension(packedFixed64ExtensionLite , 608L); + message.addExtension(packedSfixed32ExtensionLite, 609); + message.addExtension(packedSfixed64ExtensionLite, 610L); + message.addExtension(packedFloatExtensionLite , 611F); + message.addExtension(packedDoubleExtensionLite , 612D); + message.addExtension(packedBoolExtensionLite , true); + message.addExtension(packedEnumExtensionLite, ForeignEnumLite.FOREIGN_LITE_BAR); + // Add a second one of each field. + message.addExtension(packedInt32ExtensionLite , 701); + message.addExtension(packedInt64ExtensionLite , 702L); + message.addExtension(packedUint32ExtensionLite , 703); + message.addExtension(packedUint64ExtensionLite , 704L); + message.addExtension(packedSint32ExtensionLite , 705); + message.addExtension(packedSint64ExtensionLite , 706L); + message.addExtension(packedFixed32ExtensionLite , 707); + message.addExtension(packedFixed64ExtensionLite , 708L); + message.addExtension(packedSfixed32ExtensionLite, 709); + message.addExtension(packedSfixed64ExtensionLite, 710L); + message.addExtension(packedFloatExtensionLite , 711F); + message.addExtension(packedDoubleExtensionLite , 712D); + message.addExtension(packedBoolExtensionLite , false); + message.addExtension(packedEnumExtensionLite, ForeignEnumLite.FOREIGN_LITE_BAZ); + } +} diff --git a/java/core/src/test/java/com/google/protobuf/TextFormatParseInfoTreeTest.java b/java/core/src/test/java/com/google/protobuf/TextFormatParseInfoTreeTest.java new file mode 100644 index 00000000..e338af21 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/TextFormatParseInfoTreeTest.java @@ -0,0 +1,182 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// 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.Descriptor; +import com.google.protobuf.Descriptors.FieldDescriptor; +import protobuf_unittest.UnittestProto.TestAllTypes; + +import junit.framework.TestCase; + +/** + * Test @{link TextFormatParseInfoTree}. + */ +public class TextFormatParseInfoTreeTest extends TestCase { + + private static final Descriptor DESCRIPTOR = TestAllTypes.getDescriptor(); + private static final FieldDescriptor OPTIONAL_INT32 = + DESCRIPTOR.findFieldByName("optional_int32"); + private static final FieldDescriptor OPTIONAL_BOOLEAN = + DESCRIPTOR.findFieldByName("optional_boolean"); + private static final FieldDescriptor REPEATED_INT32 = + DESCRIPTOR.findFieldByName("repeated_int32"); + private static final FieldDescriptor OPTIONAL_NESTED_MESSAGE = + DESCRIPTOR.findFieldByName("optional_nested_message"); + private static final FieldDescriptor REPEATED_NESTED_MESSAGE = + DESCRIPTOR.findFieldByName("repeated_nested_message"); + private static final FieldDescriptor FIELD_BB = + TestAllTypes.NestedMessage.getDescriptor().findFieldByName("bb"); + + private static final TextFormatParseLocation LOC0 = TextFormatParseLocation.create(1, 2); + private static final TextFormatParseLocation LOC1 = TextFormatParseLocation.create(2, 3); + + private TextFormatParseInfoTree.Builder rootBuilder; + + @Override + public void setUp() { + rootBuilder = TextFormatParseInfoTree.builder(); + } + + public void testBuildEmptyParseTree() { + TextFormatParseInfoTree tree = rootBuilder.build(); + assertTrue(tree.getLocations(null).isEmpty()); + } + + public void testGetLocationReturnsSingleLocation() { + rootBuilder.setLocation(OPTIONAL_INT32, LOC0); + TextFormatParseInfoTree root = rootBuilder.build(); + assertEquals(LOC0, root.getLocation(OPTIONAL_INT32, 0)); + assertEquals(1, root.getLocations(OPTIONAL_INT32).size()); + } + + public void testGetLocationsReturnsNoParseLocationsForUnknownField() { + assertTrue(rootBuilder.build().getLocations(OPTIONAL_INT32).isEmpty()); + rootBuilder.setLocation(OPTIONAL_BOOLEAN, LOC0); + TextFormatParseInfoTree root = rootBuilder.build(); + assertTrue(root.getLocations(OPTIONAL_INT32).isEmpty()); + assertEquals(LOC0, root.getLocations(OPTIONAL_BOOLEAN).get(0)); + } + + public void testGetLocationThrowsIllegalArgumentExceptionForUnknownField() { + rootBuilder.setLocation(REPEATED_INT32, LOC0); + TextFormatParseInfoTree root = rootBuilder.build(); + try { + root.getNestedTree(OPTIONAL_INT32, 0); + fail("Did not detect unknown field"); + } catch (IllegalArgumentException expected) { + // pass + } + } + + public void testGetLocationThrowsIllegalArgumentExceptionForInvalidIndex() { + TextFormatParseInfoTree root = rootBuilder.setLocation(OPTIONAL_INT32, LOC0).build(); + try { + root.getLocation(OPTIONAL_INT32, 1); + fail("Invalid index not detected"); + } catch (IllegalArgumentException expected) { + // pass + } + try { + root.getLocation(OPTIONAL_INT32, -1); + fail("Negative index not detected"); + } catch (IllegalArgumentException expected) { + // pass + } + } + + public void testGetLocationsReturnsMultipleLocations() { + rootBuilder.setLocation(REPEATED_INT32, LOC0); + rootBuilder.setLocation(REPEATED_INT32, LOC1); + TextFormatParseInfoTree root = rootBuilder.build(); + assertEquals(LOC0, root.getLocation(REPEATED_INT32, 0)); + assertEquals(LOC1, root.getLocation(REPEATED_INT32, 1)); + assertEquals(2, root.getLocations(REPEATED_INT32).size()); + } + + public void testGetNestedTreeThrowsIllegalArgumentExceptionForUnknownField() { + rootBuilder.setLocation(REPEATED_INT32, LOC0); + TextFormatParseInfoTree root = rootBuilder.build(); + try { + root.getNestedTree(OPTIONAL_NESTED_MESSAGE, 0); + fail("Did not detect unknown field"); + } catch (IllegalArgumentException expected) { + // pass + } + } + + public void testGetNestedTreesReturnsNoParseInfoTreesForUnknownField() { + rootBuilder.setLocation(REPEATED_INT32, LOC0); + TextFormatParseInfoTree root = rootBuilder.build(); + assertTrue(root.getNestedTrees(OPTIONAL_NESTED_MESSAGE).isEmpty()); + } + + public void testGetNestedTreeThrowsIllegalArgumentExceptionForInvalidIndex() { + rootBuilder.setLocation(REPEATED_INT32, LOC0); + rootBuilder.getBuilderForSubMessageField(OPTIONAL_NESTED_MESSAGE); + TextFormatParseInfoTree root = rootBuilder.build(); + try { + root.getNestedTree(OPTIONAL_NESTED_MESSAGE, 1); + fail("Submessage index that is too large not detected"); + } catch (IllegalArgumentException expected) { + // pass + } + try { + rootBuilder.build().getNestedTree(OPTIONAL_NESTED_MESSAGE, -1); + fail("Invalid submessage index (-1) not detected"); + } catch (IllegalArgumentException expected) { + // pass + } + } + + public void testGetNestedTreesReturnsSingleTree() { + rootBuilder.getBuilderForSubMessageField(OPTIONAL_NESTED_MESSAGE); + TextFormatParseInfoTree root = rootBuilder.build(); + assertEquals(1, root.getNestedTrees(OPTIONAL_NESTED_MESSAGE).size()); + TextFormatParseInfoTree subtree = root.getNestedTrees(OPTIONAL_NESTED_MESSAGE).get(0); + assertNotNull(subtree); + } + + public void testGetNestedTreesReturnsMultipleTrees() { + TextFormatParseInfoTree.Builder subtree1Builder = + rootBuilder.getBuilderForSubMessageField(REPEATED_NESTED_MESSAGE); + subtree1Builder.getBuilderForSubMessageField(FIELD_BB); + subtree1Builder.getBuilderForSubMessageField(FIELD_BB); + TextFormatParseInfoTree.Builder subtree2Builder = + rootBuilder.getBuilderForSubMessageField(REPEATED_NESTED_MESSAGE); + subtree2Builder.getBuilderForSubMessageField(FIELD_BB); + TextFormatParseInfoTree root = rootBuilder.build(); + assertEquals(2, root.getNestedTrees(REPEATED_NESTED_MESSAGE).size()); + assertEquals( + 2, root.getNestedTrees(REPEATED_NESTED_MESSAGE).get(0).getNestedTrees(FIELD_BB).size()); + assertEquals( + 1, root.getNestedTrees(REPEATED_NESTED_MESSAGE).get(1).getNestedTrees(FIELD_BB).size()); + } +} diff --git a/java/core/src/test/java/com/google/protobuf/TextFormatParseLocationTest.java b/java/core/src/test/java/com/google/protobuf/TextFormatParseLocationTest.java new file mode 100644 index 00000000..c42bfa6e --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/TextFormatParseLocationTest.java @@ -0,0 +1,86 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package com.google.protobuf; + +import junit.framework.TestCase; + +/** + * Test @{link TextFormatParseLocation}. + */ +public class TextFormatParseLocationTest extends TestCase { + + public void testCreateEmpty() { + TextFormatParseLocation location = TextFormatParseLocation.create(-1, -1); + assertEquals(TextFormatParseLocation.EMPTY, location); + } + + public void testCreate() { + TextFormatParseLocation location = TextFormatParseLocation.create(2, 1); + assertEquals(2, location.getLine()); + assertEquals(1, location.getColumn()); + } + + public void testCreateThrowsIllegalArgumentExceptionForInvalidIndex() { + try { + TextFormatParseLocation.create(-1, 0); + fail("Should throw IllegalArgumentException if line is less than 0"); + } catch (IllegalArgumentException unused) { + // pass + } + try { + TextFormatParseLocation.create(0, -1); + fail("Should throw, column < 0"); + } catch (IllegalArgumentException unused) { + // pass + } + } + + public void testHashCode() { + TextFormatParseLocation loc0 = TextFormatParseLocation.create(2, 1); + TextFormatParseLocation loc1 = TextFormatParseLocation.create(2, 1); + + assertEquals(loc0.hashCode(), loc1.hashCode()); + assertEquals( + TextFormatParseLocation.EMPTY.hashCode(), TextFormatParseLocation.EMPTY.hashCode()); + } + + public void testEquals() { + TextFormatParseLocation loc0 = TextFormatParseLocation.create(2, 1); + TextFormatParseLocation loc1 = TextFormatParseLocation.create(1, 2); + TextFormatParseLocation loc2 = TextFormatParseLocation.create(2, 2); + TextFormatParseLocation loc3 = TextFormatParseLocation.create(2, 1); + + assertEquals(loc0, loc3); + assertNotSame(loc0, loc1); + assertNotSame(loc0, loc2); + assertNotSame(loc1, loc2); + } +} diff --git a/java/src/test/java/com/google/protobuf/TextFormatTest.java b/java/core/src/test/java/com/google/protobuf/TextFormatTest.java index 1df4fad7..63c17cd0 100644 --- a/java/src/test/java/com/google/protobuf/TextFormatTest.java +++ b/java/core/src/test/java/com/google/protobuf/TextFormatTest.java @@ -30,6 +30,7 @@ package com.google.protobuf; +import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.FieldDescriptor; import com.google.protobuf.TextFormat.Parser.SingularOverwritePolicy; import protobuf_unittest.UnittestMset.TestMessageSetExtension1; @@ -45,6 +46,7 @@ import proto2_wireformat_unittest.UnittestMsetWireFormat.TestMessageSet; import junit.framework.TestCase; import java.io.StringReader; +import java.util.List; /** * Test case for {@link TextFormat}. @@ -568,6 +570,16 @@ public class TextFormatTest extends TestCase { assertEquals(kEscapeTestString, TextFormat.unescapeText(kEscapeTestStringEscaped)); + // Invariant + assertEquals("hello", + TextFormat.escapeBytes(bytes("hello"))); + assertEquals("hello", + TextFormat.escapeText("hello")); + assertEquals(bytes("hello"), + TextFormat.unescapeBytes("hello")); + assertEquals("hello", + TextFormat.unescapeText("hello")); + // Unicode handling. assertEquals("\\341\\210\\264", TextFormat.escapeText("\u1234")); assertEquals("\\341\\210\\264", @@ -763,11 +775,14 @@ public class TextFormatTest extends TestCase { public void testParseBoolean() throws Exception { String goodText = "repeated_bool: t repeated_bool : 0\n" + - "repeated_bool :f repeated_bool:1"; + "repeated_bool :f repeated_bool:1\n" + + "repeated_bool: False repeated_bool: True"; String goodTextCanonical = "repeated_bool: true\n" + "repeated_bool: false\n" + "repeated_bool: false\n" + + "repeated_bool: true\n" + + "repeated_bool: false\n" + "repeated_bool: true\n"; TestAllTypes.Builder builder = TestAllTypes.newBuilder(); TextFormat.merge(goodText, builder); @@ -811,7 +826,6 @@ public class TextFormatTest extends TestCase { private void assertPrintFieldValue(String expect, Object value, String fieldName) throws Exception { - TestAllTypes.Builder builder = TestAllTypes.newBuilder(); StringBuilder sb = new StringBuilder(); TextFormat.printFieldValue( TestAllTypes.getDescriptor().findFieldByName(fieldName), @@ -1018,4 +1032,98 @@ public class TextFormatTest extends TestCase { assertFalse(oneof.hasFooString()); assertTrue(oneof.hasFooInt()); } + + // ======================================================================= + // test location information + + public void testParseInfoTreeBuilding() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + + Descriptor descriptor = TestAllTypes.getDescriptor(); + TextFormatParseInfoTree.Builder treeBuilder = TextFormatParseInfoTree.builder(); + // Set to allow unknown fields + TextFormat.Parser parser = + TextFormat.Parser.newBuilder() + .setParseInfoTreeBuilder(treeBuilder) + .build(); + + final String stringData = + "optional_int32: 1\n" + + "optional_int64: 2\n" + + " optional_double: 2.4\n" + + "repeated_int32: 5\n" + + "repeated_int32: 10\n" + + "optional_nested_message <\n" + + " bb: 78\n" + + ">\n" + + "repeated_nested_message <\n" + + " bb: 79\n" + + ">\n" + + "repeated_nested_message <\n" + + " bb: 80\n" + + ">"; + + parser.merge(stringData, builder); + TextFormatParseInfoTree tree = treeBuilder.build(); + + // Verify that the tree has the correct positions. + assertLocation(tree, descriptor, "optional_int32", 0, 0, 0); + assertLocation(tree, descriptor, "optional_int64", 0, 1, 0); + assertLocation(tree, descriptor, "optional_double", 0, 2, 2); + + assertLocation(tree, descriptor, "repeated_int32", 0, 3, 0); + assertLocation(tree, descriptor, "repeated_int32", 1, 4, 0); + + assertLocation(tree, descriptor, "optional_nested_message", 0, 5, 0); + assertLocation(tree, descriptor, "repeated_nested_message", 0, 8, 0); + assertLocation(tree, descriptor, "repeated_nested_message", 1, 11, 0); + + // Check for fields not set. For an invalid field, the location returned should be -1, -1. + assertLocation(tree, descriptor, "repeated_int64", 0, -1, -1); + assertLocation(tree, descriptor, "repeated_int32", 6, -1, -1); + + // Verify inside the nested message. + FieldDescriptor nestedField = descriptor.findFieldByName("optional_nested_message"); + + TextFormatParseInfoTree nestedTree = tree.getNestedTrees(nestedField).get(0); + assertLocation(nestedTree, nestedField.getMessageType(), "bb", 0, 6, 2); + + // Verify inside another nested message. + nestedField = descriptor.findFieldByName("repeated_nested_message"); + nestedTree = tree.getNestedTrees(nestedField).get(0); + assertLocation(nestedTree, nestedField.getMessageType(), "bb", 0, 9, 2); + + nestedTree = tree.getNestedTrees(nestedField).get(1); + assertLocation(nestedTree, nestedField.getMessageType(), "bb", 0, 12, 2); + + // Verify a NULL tree for an unknown nested field. + try { + tree.getNestedTree(nestedField, 2); + fail("unknown nested field should throw"); + } catch (IllegalArgumentException unused) { + // pass + } + } + + private void assertLocation( + TextFormatParseInfoTree tree, + final Descriptor descriptor, + final String fieldName, + int index, + int line, + int column) { + List<TextFormatParseLocation> locs = tree.getLocations(descriptor.findFieldByName(fieldName)); + if (index < locs.size()) { + TextFormatParseLocation location = locs.get(index); + TextFormatParseLocation expected = TextFormatParseLocation.create(line, column); + assertEquals(expected, location); + } else if (line != -1 && column != -1) { + fail( + String.format( + "Tree/descriptor/fieldname did not contain index %d, line %d column %d expected", + index, + line, + column)); + } + } } diff --git a/java/src/test/java/com/google/protobuf/UnknownEnumValueTest.java b/java/core/src/test/java/com/google/protobuf/UnknownEnumValueTest.java index 8f45976f..8f45976f 100644 --- a/java/src/test/java/com/google/protobuf/UnknownEnumValueTest.java +++ b/java/core/src/test/java/com/google/protobuf/UnknownEnumValueTest.java diff --git a/java/src/test/java/com/google/protobuf/UnknownFieldSetLiteTest.java b/java/core/src/test/java/com/google/protobuf/UnknownFieldSetLiteTest.java index dc987379..573cd665 100644 --- a/java/src/test/java/com/google/protobuf/UnknownFieldSetLiteTest.java +++ b/java/core/src/test/java/com/google/protobuf/UnknownFieldSetLiteTest.java @@ -47,46 +47,6 @@ import java.io.IOException; * @author dweis@google.com (Daniel Weis) */ public class UnknownFieldSetLiteTest extends TestCase { - - public void testNoDataIsDefaultInstance() { - assertSame( - UnknownFieldSetLite.getDefaultInstance(), - UnknownFieldSetLite.newBuilder() - .build()); - } - - public void testBuilderReuse() throws IOException { - UnknownFieldSetLite.Builder builder = UnknownFieldSetLite.newBuilder(); - builder.mergeVarintField(10, 2); - builder.build(); - - try { - builder.build(); - fail(); - } catch (UnsupportedOperationException e) { - // Expected. - } - - try { - builder.mergeFieldFrom(0, CodedInputStream.newInstance(new byte[0])); - fail(); - } catch (UnsupportedOperationException e) { - // Expected. - } - - try { - builder.mergeVarintField(5, 1); - fail(); - } catch (UnsupportedOperationException e) { - // Expected. - } - } - - public void testBuilderReuse_empty() { - UnknownFieldSetLite.Builder builder = UnknownFieldSetLite.newBuilder(); - builder.build(); - builder.build(); - } public void testDefaultInstance() { UnknownFieldSetLite unknownFields = UnknownFieldSetLite.getDefaultInstance(); diff --git a/java/src/test/java/com/google/protobuf/UnknownFieldSetTest.java b/java/core/src/test/java/com/google/protobuf/UnknownFieldSetTest.java index 8c9dcafe..32380f70 100644 --- a/java/src/test/java/com/google/protobuf/UnknownFieldSetTest.java +++ b/java/core/src/test/java/com/google/protobuf/UnknownFieldSetTest.java @@ -50,6 +50,7 @@ import java.util.Map; * @author kenton@google.com (Kenton Varda) */ public class UnknownFieldSetTest extends TestCase { + @Override public void setUp() throws Exception { descriptor = TestAllTypes.getDescriptor(); allFields = TestUtil.getAllSet(); diff --git a/java/src/test/java/com/google/protobuf/UnmodifiableLazyStringListTest.java b/java/core/src/test/java/com/google/protobuf/UnmodifiableLazyStringListTest.java index b1c75fc3..b1c75fc3 100644 --- a/java/src/test/java/com/google/protobuf/UnmodifiableLazyStringListTest.java +++ b/java/core/src/test/java/com/google/protobuf/UnmodifiableLazyStringListTest.java diff --git a/java/src/test/java/com/google/protobuf/WellKnownTypesTest.java b/java/core/src/test/java/com/google/protobuf/WellKnownTypesTest.java index 982e200f..982e200f 100644 --- a/java/src/test/java/com/google/protobuf/WellKnownTypesTest.java +++ b/java/core/src/test/java/com/google/protobuf/WellKnownTypesTest.java diff --git a/java/src/test/java/com/google/protobuf/WireFormatTest.java b/java/core/src/test/java/com/google/protobuf/WireFormatTest.java index 0175005d..e66b371c 100644 --- a/java/src/test/java/com/google/protobuf/WireFormatTest.java +++ b/java/core/src/test/java/com/google/protobuf/WireFormatTest.java @@ -30,12 +30,11 @@ package com.google.protobuf; -import junit.framework.TestCase; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.util.List; - +import com.google.protobuf.UnittestLite.TestAllExtensionsLite; +import com.google.protobuf.UnittestLite.TestPackedExtensionsLite; +import protobuf_unittest.UnittestMset.RawMessageSet; +import protobuf_unittest.UnittestMset.TestMessageSetExtension1; +import protobuf_unittest.UnittestMset.TestMessageSetExtension2; import protobuf_unittest.UnittestProto; import protobuf_unittest.UnittestProto.TestAllExtensions; import protobuf_unittest.UnittestProto.TestAllTypes; @@ -44,12 +43,13 @@ import protobuf_unittest.UnittestProto.TestOneof2; import protobuf_unittest.UnittestProto.TestOneofBackwardsCompatible; import protobuf_unittest.UnittestProto.TestPackedExtensions; import protobuf_unittest.UnittestProto.TestPackedTypes; -import protobuf_unittest.UnittestMset.RawMessageSet; -import protobuf_unittest.UnittestMset.TestMessageSetExtension1; -import protobuf_unittest.UnittestMset.TestMessageSetExtension2; import proto2_wireformat_unittest.UnittestMsetWireFormat.TestMessageSet; -import com.google.protobuf.UnittestLite.TestAllExtensionsLite; -import com.google.protobuf.UnittestLite.TestPackedExtensionsLite; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.util.List; /** * Tests related to parsing and serialization. @@ -132,7 +132,7 @@ public class WireFormatTest extends TestCase { // so if we serialize a TestAllExtensions then parse it as TestAllTypes // it should work. - TestAllExtensionsLite message = TestUtil.getAllLiteExtensionsSet(); + TestAllExtensionsLite message = TestUtilLite.getAllLiteExtensionsSet(); ByteString rawBytes = message.toByteString(); assertEquals(rawBytes.size(), message.getSerializedSize()); @@ -144,7 +144,7 @@ public class WireFormatTest extends TestCase { public void testSerializePackedExtensionsLite() throws Exception { // TestPackedTypes and TestPackedExtensions should have compatible wire // formats; check that they serialize to the same string. - TestPackedExtensionsLite message = TestUtil.getLitePackedExtensionsSet(); + TestPackedExtensionsLite message = TestUtilLite.getLitePackedExtensionsSet(); ByteString rawBytes = message.toByteString(); TestPackedTypes message2 = TestUtil.getPackedSet(); @@ -190,7 +190,7 @@ public class WireFormatTest extends TestCase { TestAllTypes message = TestUtil.getAllSet(); ByteString rawBytes = message.toByteString(); - ExtensionRegistryLite registry_lite = TestUtil.getExtensionRegistryLite(); + ExtensionRegistryLite registry_lite = TestUtilLite.getExtensionRegistryLite(); TestAllExtensionsLite message2 = TestAllExtensionsLite.parseFrom(rawBytes, registry_lite); @@ -208,10 +208,10 @@ public class WireFormatTest extends TestCase { public void testParsePackedExtensionsLite() throws Exception { // Ensure that packed extensions can be properly parsed. - TestPackedExtensionsLite message = TestUtil.getLitePackedExtensionsSet(); + TestPackedExtensionsLite message = TestUtilLite.getLitePackedExtensionsSet(); ByteString rawBytes = message.toByteString(); - ExtensionRegistryLite registry = TestUtil.getExtensionRegistryLite(); + ExtensionRegistryLite registry = TestUtilLite.getExtensionRegistryLite(); TestPackedExtensionsLite message2 = TestPackedExtensionsLite.parseFrom(rawBytes, registry); diff --git a/java/src/test/java/com/google/protobuf/any_test.proto b/java/core/src/test/proto/com/google/protobuf/any_test.proto index 80173d8a..80173d8a 100644 --- a/java/src/test/java/com/google/protobuf/any_test.proto +++ b/java/core/src/test/proto/com/google/protobuf/any_test.proto diff --git a/java/src/test/java/com/google/protobuf/field_presence_test.proto b/java/core/src/test/proto/com/google/protobuf/field_presence_test.proto index 8f3ca8c6..8f3ca8c6 100644 --- a/java/src/test/java/com/google/protobuf/field_presence_test.proto +++ b/java/core/src/test/proto/com/google/protobuf/field_presence_test.proto diff --git a/java/src/test/java/com/google/protobuf/lazy_fields_lite.proto b/java/core/src/test/proto/com/google/protobuf/lazy_fields_lite.proto index 5580f72d..5580f72d 100644 --- a/java/src/test/java/com/google/protobuf/lazy_fields_lite.proto +++ b/java/core/src/test/proto/com/google/protobuf/lazy_fields_lite.proto diff --git a/java/src/test/java/com/google/protobuf/lite_equals_and_hash.proto b/java/core/src/test/proto/com/google/protobuf/lite_equals_and_hash.proto index 86837250..6eef42c5 100644 --- a/java/src/test/java/com/google/protobuf/lite_equals_and_hash.proto +++ b/java/core/src/test/proto/com/google/protobuf/lite_equals_and_hash.proto @@ -34,11 +34,15 @@ syntax = "proto2"; 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 TestOneofEquals { + oneof oneof_field { + string name = 1; + int32 value = 2; + } +} + message Foo { optional int32 value = 1; repeated Bar bar = 2; @@ -70,3 +74,8 @@ extend Foo { } } +message TestRecursiveOneof { + oneof Foo { + TestRecursiveOneof r = 1; + } +} diff --git a/java/src/test/java/com/google/protobuf/map_for_proto2_lite_test.proto b/java/core/src/test/proto/com/google/protobuf/map_for_proto2_lite_test.proto index d5418f28..d5418f28 100644 --- a/java/src/test/java/com/google/protobuf/map_for_proto2_lite_test.proto +++ b/java/core/src/test/proto/com/google/protobuf/map_for_proto2_lite_test.proto diff --git a/java/src/test/java/com/google/protobuf/map_for_proto2_test.proto b/java/core/src/test/proto/com/google/protobuf/map_for_proto2_test.proto index a9be5166..a9be5166 100644 --- a/java/src/test/java/com/google/protobuf/map_for_proto2_test.proto +++ b/java/core/src/test/proto/com/google/protobuf/map_for_proto2_test.proto diff --git a/java/src/test/java/com/google/protobuf/map_initialization_order_test.proto b/java/core/src/test/proto/com/google/protobuf/map_initialization_order_test.proto index b02ac599..b02ac599 100644 --- a/java/src/test/java/com/google/protobuf/map_initialization_order_test.proto +++ b/java/core/src/test/proto/com/google/protobuf/map_initialization_order_test.proto diff --git a/java/src/test/java/com/google/protobuf/map_test.proto b/java/core/src/test/proto/com/google/protobuf/map_test.proto index 2f7709be..2280ac03 100644 --- a/java/src/test/java/com/google/protobuf/map_test.proto +++ b/java/core/src/test/proto/com/google/protobuf/map_test.proto @@ -36,7 +36,6 @@ option java_package = "map_test"; option java_outer_classname = "MapTestProto"; option java_generate_equals_and_hash = true; - message TestMap { message MessageValue { int32 value = 1; diff --git a/java/src/test/java/com/google/protobuf/multiple_files_test.proto b/java/core/src/test/proto/com/google/protobuf/multiple_files_test.proto index 92790506..92790506 100644 --- a/java/src/test/java/com/google/protobuf/multiple_files_test.proto +++ b/java/core/src/test/proto/com/google/protobuf/multiple_files_test.proto diff --git a/java/src/test/java/com/google/protobuf/nested_builders_test.proto b/java/core/src/test/proto/com/google/protobuf/nested_builders_test.proto index a5dd66d8..a5dd66d8 100644 --- a/java/src/test/java/com/google/protobuf/nested_builders_test.proto +++ b/java/core/src/test/proto/com/google/protobuf/nested_builders_test.proto diff --git a/java/src/test/java/com/google/protobuf/nested_extension.proto b/java/core/src/test/proto/com/google/protobuf/nested_extension.proto index 704e649e..704e649e 100644 --- a/java/src/test/java/com/google/protobuf/nested_extension.proto +++ b/java/core/src/test/proto/com/google/protobuf/nested_extension.proto diff --git a/java/src/test/java/com/google/protobuf/nested_extension_lite.proto b/java/core/src/test/proto/com/google/protobuf/nested_extension_lite.proto index a95c38b2..a95c38b2 100644 --- a/java/src/test/java/com/google/protobuf/nested_extension_lite.proto +++ b/java/core/src/test/proto/com/google/protobuf/nested_extension_lite.proto diff --git a/java/src/test/java/com/google/protobuf/non_nested_extension.proto b/java/core/src/test/proto/com/google/protobuf/non_nested_extension.proto index 31fac552..31fac552 100644 --- a/java/src/test/java/com/google/protobuf/non_nested_extension.proto +++ b/java/core/src/test/proto/com/google/protobuf/non_nested_extension.proto diff --git a/java/src/test/java/com/google/protobuf/non_nested_extension_lite.proto b/java/core/src/test/proto/com/google/protobuf/non_nested_extension_lite.proto index 37c369ed..37c369ed 100644 --- a/java/src/test/java/com/google/protobuf/non_nested_extension_lite.proto +++ b/java/core/src/test/proto/com/google/protobuf/non_nested_extension_lite.proto diff --git a/java/src/test/java/com/google/protobuf/outer_class_name_test.proto b/java/core/src/test/proto/com/google/protobuf/outer_class_name_test.proto index 42083681..42083681 100644 --- a/java/src/test/java/com/google/protobuf/outer_class_name_test.proto +++ b/java/core/src/test/proto/com/google/protobuf/outer_class_name_test.proto diff --git a/java/src/test/java/com/google/protobuf/outer_class_name_test2.proto b/java/core/src/test/proto/com/google/protobuf/outer_class_name_test2.proto index 3e5956b0..3e5956b0 100644 --- a/java/src/test/java/com/google/protobuf/outer_class_name_test2.proto +++ b/java/core/src/test/proto/com/google/protobuf/outer_class_name_test2.proto diff --git a/java/src/test/java/com/google/protobuf/outer_class_name_test3.proto b/java/core/src/test/proto/com/google/protobuf/outer_class_name_test3.proto index 74a8ba3c..74a8ba3c 100644 --- a/java/src/test/java/com/google/protobuf/outer_class_name_test3.proto +++ b/java/core/src/test/proto/com/google/protobuf/outer_class_name_test3.proto diff --git a/java/src/test/java/com/google/protobuf/test_bad_identifiers.proto b/java/core/src/test/proto/com/google/protobuf/test_bad_identifiers.proto index 8c37c03c..2b1f65e4 100644 --- a/java/src/test/java/com/google/protobuf/test_bad_identifiers.proto +++ b/java/core/src/test/proto/com/google/protobuf/test_bad_identifiers.proto @@ -45,7 +45,6 @@ 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; diff --git a/java/src/test/java/com/google/protobuf/test_check_utf8.proto b/java/core/src/test/proto/com/google/protobuf/test_check_utf8.proto index 119c1dcb..119c1dcb 100644 --- a/java/src/test/java/com/google/protobuf/test_check_utf8.proto +++ b/java/core/src/test/proto/com/google/protobuf/test_check_utf8.proto diff --git a/java/src/test/java/com/google/protobuf/test_check_utf8_size.proto b/java/core/src/test/proto/com/google/protobuf/test_check_utf8_size.proto index f06d76d6..f06d76d6 100644 --- a/java/src/test/java/com/google/protobuf/test_check_utf8_size.proto +++ b/java/core/src/test/proto/com/google/protobuf/test_check_utf8_size.proto diff --git a/java/src/test/java/com/google/protobuf/test_custom_options.proto b/java/core/src/test/proto/com/google/protobuf/test_custom_options.proto index f8efd455..f8efd455 100644 --- a/java/src/test/java/com/google/protobuf/test_custom_options.proto +++ b/java/core/src/test/proto/com/google/protobuf/test_custom_options.proto diff --git a/java/src/test/java/com/google/protobuf/test_extra_interfaces.proto b/java/core/src/test/proto/com/google/protobuf/test_extra_interfaces.proto index 645f57b4..645f57b4 100644 --- a/java/src/test/java/com/google/protobuf/test_extra_interfaces.proto +++ b/java/core/src/test/proto/com/google/protobuf/test_extra_interfaces.proto diff --git a/java/lite/generate-sources-build.xml b/java/lite/generate-sources-build.xml new file mode 100644 index 00000000..89c21c13 --- /dev/null +++ b/java/lite/generate-sources-build.xml @@ -0,0 +1,20 @@ +<project name="generate-sources"> + <echo message="Running protoc ..."/> + <mkdir dir="${generated.sources.lite.dir}"/> + <exec executable="${protoc}"> + <arg value="--java_out=lite:${generated.sources.lite.dir}"/> + <arg value="--proto_path=${protobuf.source.dir}"/> + <arg value="${protobuf.source.dir}/google/protobuf/any.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/api.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/descriptor.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/duration.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/empty.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/field_mask.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/source_context.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/struct.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/timestamp.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/type.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/wrappers.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/compiler/plugin.proto"/> + </exec> +</project> diff --git a/java/lite/generate-test-sources-build.xml b/java/lite/generate-test-sources-build.xml new file mode 100644 index 00000000..cdd1ee89 --- /dev/null +++ b/java/lite/generate-test-sources-build.xml @@ -0,0 +1,43 @@ +<project name="generate-test-sources"> + <mkdir dir="${generated.testsources.lite.dir}"/> + <exec executable="${protoc}"> + <arg value="--java_out=lite:${generated.testsources.lite.dir}"/> + <arg value="--proto_path=${protobuf.source.dir}"/> + <arg value="--proto_path=${test.proto.dir}"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_import.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_import_public.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_mset.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_mset_wire_format.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_optimize_for.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_custom_options.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_lite.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_import_lite.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_import_public_lite.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_lite_imports_nonlite.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_enormous_descriptor.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_no_generic_services.proto"/> + <arg value="${protobuf.source.dir}/google/protobuf/unittest_well_known_types.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/lazy_fields_lite.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/lite_equals_and_hash.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/multiple_files_test.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/nested_builders_test.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/nested_extension.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/nested_extension_lite.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/non_nested_extension.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/non_nested_extension_lite.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/outer_class_name_test.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/outer_class_name_test2.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/outer_class_name_test3.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/test_bad_identifiers.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/test_check_utf8.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/test_check_utf8_size.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/test_custom_options.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/any_test.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/field_presence_test.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/map_for_proto2_lite_test.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/map_for_proto2_test.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/map_test.proto"/> + <arg value="${test.proto.dir}/com/google/protobuf/map_initialization_order_test.proto"/> + </exec> +</project> diff --git a/java/lite/pom.xml b/java/lite/pom.xml new file mode 100644 index 00000000..c403dc09 --- /dev/null +++ b/java/lite/pom.xml @@ -0,0 +1,156 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>com.google.protobuf</groupId> + <artifactId>protobuf-parent</artifactId> + <version>3.0.0-beta-3</version> + </parent> + + <artifactId>protobuf-lite</artifactId> + <packaging>bundle</packaging> + + <name>Protocol Buffers [Lite]</name> + <description>A trimmed-down version of the Protocol Buffers library.</description> + + <dependencies> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + </dependency> + <dependency> + <groupId>org.easymock</groupId> + <artifactId>easymock</artifactId> + </dependency> + <dependency> + <groupId>org.easymock</groupId> + <artifactId>easymockclassextension</artifactId> + </dependency> + </dependencies> + + <properties> + <core.root>../core</core.root> + <test.proto.dir>${core.root}/src/test/proto</test.proto.dir> + </properties> + + <build> + <sourceDirectory>${core.root}/src/main/java</sourceDirectory> + <testSourceDirectory>${core.root}/src/test/java</testSourceDirectory> + + <plugins> + <!-- Use Antrun plugin to generate sources with protoc --> + <plugin> + <artifactId>maven-antrun-plugin</artifactId> + <executions> + <!-- Generate core protos --> + <execution> + <id>generate-sources</id> + <phase>generate-sources</phase> + <configuration> + <target> + <ant antfile="generate-sources-build.xml"/> + </target> + </configuration> + <goals> + <goal>run</goal> + </goals> + </execution> + + <!-- Generate the test protos --> + <execution> + <id>generate-test-sources</id> + <phase>generate-test-sources</phase> + <configuration> + <target> + <ant antfile="generate-test-sources-build.xml"/> + </target> + </configuration> + <goals> + <goal>run</goal> + </goals> + </execution> + </executions> + </plugin> + + <!-- Only compile a subset of the files --> + <plugin> + <artifactId>maven-compiler-plugin</artifactId> + <configuration> + <generatedSourcesDirectory>${generated.sources.lite.dir}</generatedSourcesDirectory> + <generatedTestSourcesDirectory>${generated.testsources.lite.dir}</generatedTestSourcesDirectory> + <includes> + <include>**/AbstractMessageLite.java</include> + <include>**/AbstractParser.java</include> + <include>**/AbstractProtobufList.java</include> + <include>**/BooleanArrayList.java</include> + <include>**/ByteString.java</include> + <include>**/CodedInputStream.java</include> + <include>**/CodedOutputStream.java</include> + <include>**/DoubleArrayList.java</include> + <include>**/ExtensionLite.java</include> + <include>**/ExtensionRegistryLite.java</include> + <include>**/FieldSet.java</include> + <include>**/FloatArrayList.java</include> + <include>**/GeneratedMessageLite.java</include> + <include>**/IntArrayList.java</include> + <include>**/Internal.java</include> + <include>**/InvalidProtocolBufferException.java</include> + <include>**/LazyFieldLite.java</include> + <include>**/LazyStringArrayList.java</include> + <include>**/LazyStringList.java</include> + <include>**/LongArrayList.java</include> + <include>**/MapEntryLite.java</include> + <include>**/MapFieldLite.java</include> + <include>**/MessageLite.java</include> + <include>**/MessageLiteOrBuilder.java</include> + <include>**/MessageLiteToString.java</include> + <include>**/MutabilityOracle.java</include> + <include>**/NioByteString.java</include> + <include>**/Parser.java</include> + <include>**/ProtobufArrayList.java</include> + <include>**/ProtocolStringList.java</include> + <include>**/RopeByteString.java</include> + <include>**/SmallSortedMap.java</include> + <include>**/TextFormatEscaper.java</include> + <include>**/UninitializedMessageException.java</include> + <include>**/UnknownFieldSetLite.java</include> + <include>**/UnmodifiableLazyStringList.java</include> + <include>**/UnsafeByteOperations.java</include> + <include>**/Utf8.java</include> + <include>**/WireFormat.java</include> + </includes> + <testIncludes> + <testInclude>**/*Lite.java</testInclude> + <testInclude>**/BooleanArrayListTest.java</testInclude> + <testInclude>**/DoubleArrayListTest.java</testInclude> + <testInclude>**/FloatArrayListTest.java</testInclude> + <testInclude>**/IntArrayListTest.java</testInclude> + <testInclude>**/LazyMessageLiteTest.java</testInclude> + <testInclude>**/LiteTest.java</testInclude> + <testInclude>**/LongArrayListTest.java</testInclude> + <testInclude>**/NioByteStringTest.java</testInclude> + <testInclude>**/ProtobufArrayListTest.java</testInclude> + <testInclude>**/UnknownFieldSetLiteTest.java</testInclude> + </testIncludes> + </configuration> + </plugin> + + <!-- OSGI bundle configuration --> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <extensions>true</extensions> + <configuration> + <instructions> + <Bundle-DocURL>https://developers.google.com/protocol-buffers/</Bundle-DocURL> + <Bundle-SymbolicName>com.google.protobuf</Bundle-SymbolicName> + <Export-Package>com.google.${project.artifactId};version=${project.version}</Export-Package> + </instructions> + </configuration> + </plugin> + </plugins> + </build> + +</project> diff --git a/java/pom.xml b/java/pom.xml index 8b81b645..7a1a91f8 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -8,17 +8,35 @@ <artifactId>google</artifactId> <version>1</version> </parent> + <groupId>com.google.protobuf</groupId> - <artifactId>protobuf-java</artifactId> - <version>3.0.0-beta-1</version> - <packaging>bundle</packaging> - <name>Protocol Buffer Java API</name> + <artifactId>protobuf-parent</artifactId> + <version>3.0.0-beta-3</version> + <packaging>pom</packaging> + + <name>Protocol Buffers [Parent]</name> + <inceptionYear>2008</inceptionYear> + <url>https://developers.google.com/protocol-buffers/</url> <description> Protocol Buffers are a way of encoding structured data in an efficient yet extensible format. </description> - <inceptionYear>2008</inceptionYear> - <url>https://developers.google.com/protocol-buffers/</url> + + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> + + <!-- These are relative to the submodules --> + <protobuf.basedir>${project.basedir}/../..</protobuf.basedir> + <protobuf.source.dir>${protobuf.basedir}/src</protobuf.source.dir> + <protoc>${protobuf.source.dir}/protoc</protoc> + <test.proto.dir>src/test/proto</test.proto.dir> + <generated.sources.dir>${project.build.directory}/generated-sources</generated.sources.dir> + <generated.testsources.dir>${project.build.directory}/generated-test-sources</generated.testsources.dir> + <generated.sources.lite.dir>${project.build.directory}/generated-sources-lite</generated.sources.lite.dir> + <generated.testsources.lite.dir>${project.build.directory}/generated-test-sources-lite</generated.testsources.lite.dir> + </properties> + <licenses> <license> <name>New BSD license</name> @@ -26,215 +44,116 @@ <distribution>repo</distribution> </license> </licenses> + <scm> <url>https://github.com/google/protobuf</url> - <connection> - scm:git:https://github.com/google/protobuf.git - </connection> + <connection>scm:git:https://github.com/google/protobuf.git</connection> </scm> - <dependencies> - <dependency> - <groupId>junit</groupId> - <artifactId>junit</artifactId> - <version>4.4</version> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.easymock</groupId> - <artifactId>easymock</artifactId> - <version>2.2</version> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.easymock</groupId> - <artifactId>easymockclassextension</artifactId> - <version>2.2.1</version> - <scope>test</scope> - </dependency> - </dependencies> + + <distributionManagement> + <snapshotRepository> + <id>sonatype-nexus-staging</id> + <url>https://oss.sonatype.org/content/repositories/snapshots</url> + </snapshotRepository> + <repository> + <id>sonatype-nexus-staging</id> + <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url> + </repository> + </distributionManagement> + + <dependencyManagement> + <dependencies> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>4.12</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.easymock</groupId> + <artifactId>easymock</artifactId> + <version>2.2</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.easymock</groupId> + <artifactId>easymockclassextension</artifactId> + <version>2.2.1</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + <version>18.0</version> + </dependency> + </dependencies> + </dependencyManagement> + <build> - <plugins> - <plugin> - <artifactId>maven-compiler-plugin</artifactId> - <configuration> - <source>1.5</source> - <target>1.5</target> - </configuration> - </plugin> - <plugin> - <artifactId>maven-surefire-plugin</artifactId> - <configuration> - <includes> - <include>**/*Test.java</include> - </includes> - </configuration> - </plugin> - <plugin> - <artifactId>maven-antrun-plugin</artifactId> - <executions> - <execution> - <id>generate-sources</id> - <phase>generate-sources</phase> - <configuration> - <tasks> - <mkdir dir="target/generated-sources" /> - <exec executable="../src/protoc"> - <arg value="--java_out=target/generated-sources" /> - <arg value="--proto_path=../src" /> - <arg value="../src/google/protobuf/any.proto" /> - <arg value="../src/google/protobuf/api.proto" /> - <arg value="../src/google/protobuf/descriptor.proto" /> - <arg value="../src/google/protobuf/duration.proto" /> - <arg value="../src/google/protobuf/empty.proto" /> - <arg value="../src/google/protobuf/field_mask.proto" /> - <arg value="../src/google/protobuf/source_context.proto" /> - <arg value="../src/google/protobuf/struct.proto" /> - <arg value="../src/google/protobuf/timestamp.proto" /> - <arg value="../src/google/protobuf/type.proto" /> - <arg value="../src/google/protobuf/wrappers.proto" /> - </exec> - </tasks> - <sourceRoot>target/generated-sources</sourceRoot> - </configuration> - <goals> - <goal>run</goal> - </goals> - </execution> - <execution> - <id>generate-test-sources</id> - <phase>generate-test-sources</phase> - <configuration> - <tasks> - <mkdir dir="target/generated-test-sources" /> - <exec executable="../src/protoc"> - <arg value="--java_out=target/generated-test-sources" /> - <arg value="--proto_path=../src" /> - <arg value="--proto_path=src/test/java" /> - <arg value="../src/google/protobuf/unittest.proto" /> - <arg value="../src/google/protobuf/unittest_import.proto" /> - <arg value="../src/google/protobuf/unittest_import_public.proto" /> - <arg value="../src/google/protobuf/unittest_mset.proto" /> - <arg value="../src/google/protobuf/unittest_mset_wire_format.proto" /> - <arg value="src/test/java/com/google/protobuf/lazy_fields_lite.proto" /> - <arg value="src/test/java/com/google/protobuf/lite_equals_and_hash.proto" /> - <arg - value="src/test/java/com/google/protobuf/multiple_files_test.proto" /> - <arg value="src/test/java/com/google/protobuf/nested_builders_test.proto" /> - <arg value="src/test/java/com/google/protobuf/nested_extension.proto" /> - <arg value="src/test/java/com/google/protobuf/nested_extension_lite.proto" /> - <arg value="src/test/java/com/google/protobuf/non_nested_extension.proto" /> - <arg value="src/test/java/com/google/protobuf/non_nested_extension_lite.proto" /> - <arg value="src/test/java/com/google/protobuf/outer_class_name_test.proto" /> - <arg value="src/test/java/com/google/protobuf/outer_class_name_test2.proto" /> - <arg value="src/test/java/com/google/protobuf/outer_class_name_test3.proto" /> - <arg value="src/test/java/com/google/protobuf/test_bad_identifiers.proto" /> - <arg value="src/test/java/com/google/protobuf/test_check_utf8.proto" /> - <arg value="src/test/java/com/google/protobuf/test_check_utf8_size.proto" /> - <arg value="src/test/java/com/google/protobuf/test_custom_options.proto" /> - <arg - value="../src/google/protobuf/unittest_optimize_for.proto" /> - <arg - value="../src/google/protobuf/unittest_custom_options.proto" /> - <arg value="../src/google/protobuf/unittest_lite.proto" /> - <arg value="../src/google/protobuf/unittest_import_lite.proto" /> - <arg value="../src/google/protobuf/unittest_import_public_lite.proto" /> - <arg value="../src/google/protobuf/unittest_lite_imports_nonlite.proto" /> - <arg value="../src/google/protobuf/unittest_enormous_descriptor.proto" /> - <arg value="../src/google/protobuf/unittest_no_generic_services.proto" /> - <arg value="../src/google/protobuf/unittest_well_known_types.proto" /> - <arg value="src/test/java/com/google/protobuf/any_test.proto" /> - <arg value="src/test/java/com/google/protobuf/field_presence_test.proto" /> - <arg value="src/test/java/com/google/protobuf/map_for_proto2_lite_test.proto" /> - <arg value="src/test/java/com/google/protobuf/map_for_proto2_test.proto" /> - <arg value="src/test/java/com/google/protobuf/map_test.proto" /> - <arg value="src/test/java/com/google/protobuf/map_initialization_order_test.proto" /> - </exec> - </tasks> - <testSourceRoot>target/generated-test-sources</testSourceRoot> - </configuration> - <goals> - <goal>run</goal> - </goals> - </execution> - </executions> - </plugin> - <plugin> - <groupId>org.apache.felix</groupId> - <artifactId>maven-bundle-plugin</artifactId> - <extensions>true</extensions> - <configuration> - <instructions> - <Bundle-DocURL>https://developers.google.com/protocol-buffers/</Bundle-DocURL> - <Bundle-SymbolicName>com.google.protobuf</Bundle-SymbolicName> - <Export-Package>com.google.protobuf;version=3.0.0-beta-1</Export-Package> - </instructions> - </configuration> - </plugin> - </plugins> - <resources> - <resource> - <directory>../src</directory> - <includes> - <include>google/protobuf/any.proto</include> - <include>google/protobuf/api.proto</include> - <include>google/protobuf/descriptor.proto</include> - <include>google/protobuf/duration.proto</include> - <include>google/protobuf/empty.proto</include> - <include>google/protobuf/field_mask.proto</include> - <include>google/protobuf/source_context.proto</include> - <include>google/protobuf/struct.proto</include> - <include>google/protobuf/timestamp.proto</include> - <include>google/protobuf/type.proto</include> - <include>google/protobuf/wrappers.proto</include> - </includes> - </resource> - </resources> + <pluginManagement> + <plugins> + <plugin> + <artifactId>maven-compiler-plugin</artifactId> + <version>3.3</version> + <configuration> + <source>1.6</source> + <target>1.6</target> + </configuration> + </plugin> + <plugin> + <artifactId>maven-source-plugin</artifactId> + <version>2.4</version> + <executions> + <execution> + <id>attach-sources</id> + <goals> + <goal>jar-no-fork</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <artifactId>maven-javadoc-plugin</artifactId> + <version>2.10.3</version> + <executions> + <execution> + <id>attach-javadocs</id> + <goals> + <goal>jar</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <artifactId>maven-jar-plugin</artifactId> + <version>2.6</version> + </plugin> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>build-helper-maven-plugin</artifactId> + <version>1.10</version> + </plugin> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <version>3.0.1</version> + </plugin> + <plugin> + <artifactId>maven-antrun-plugin</artifactId> + <version>1.8</version> + </plugin> + </plugins> + </pluginManagement> </build> + <profiles> <profile> <id>release</id> - <distributionManagement> - <snapshotRepository> - <id>sonatype-nexus-staging</id> - <url>https://oss.sonatype.org/content/repositories/snapshots</url> - </snapshotRepository> - <repository> - <id>sonatype-nexus-staging</id> - <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url> - </repository> - </distributionManagement> <build> <plugins> <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-source-plugin</artifactId> - <version>2.2.1</version> - <executions> - <execution> - <id>attach-sources</id> - <goals> - <goal>jar-no-fork</goal> - </goals> - </execution> - </executions> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-javadoc-plugin</artifactId> - <version>2.9.1</version> - <executions> - <execution> - <id>attach-javadocs</id> - <goals> - <goal>jar</goal> - </goals> - </execution> - </executions> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-gpg-plugin</artifactId> - <version>1.5</version> + <version>1.6</version> <executions> <execution> <id>sign-artifacts</id> @@ -248,93 +167,23 @@ <plugin> <groupId>org.sonatype.plugins</groupId> <artifactId>nexus-staging-maven-plugin</artifactId> - <version>1.6.3</version> + <version>1.6.6</version> <extensions>true</extensions> <configuration> - <serverId>sonatype-nexus-staging</serverId> - <nexusUrl>https://oss.sonatype.org/</nexusUrl> - <autoReleaseAfterClose>false</autoReleaseAfterClose> - </configuration> - </plugin> - </plugins> - </build> - </profile> - <profile> - <id>lite</id> - <build> - <plugins> - <plugin> - <artifactId>maven-compiler-plugin</artifactId> - <configuration> - <includes> - <include>**/AbstractMessageLite.java</include> - <include>**/AbstractParser.java</include> - <include>**/AbstractProtobufList.java</include> - <include>**/BoundedByteString.java</include> - <include>**/BooleanArrayList.java</include> - <include>**/ByteString.java</include> - <include>**/CodedInputStream.java</include> - <include>**/CodedOutputStream.java</include> - <include>**/DoublerrayList.java</include> - <include>**/ExtensionLite.java</include> - <include>**/ExtensionRegistryLite.java</include> - <include>**/FieldSet.java</include> - <include>**/FloatArrayList.java</include> - <include>**/GeneratedMessageLite.java</include> - <include>**/IntArrayList.java</include> - <include>**/Internal.java</include> - <include>**/InvalidProtocolBufferException.java</include> - <include>**/LazyFieldLite.java</include> - <include>**/LazyStringArrayList.java</include> - <include>**/LazyStringList.java</include> - <include>**/LiteralByteString.java</include> - <include>**/LongArrayList.java</include> - <include>**/MapEntryLite.java</include> - <include>**/MapFieldLite.java</include> - <include>**/MessageLite.java</include> - <include>**/MessageLiteOrBuilder.java</include> - <include>**/MutabilityOracle.java</include> - <include>**/Parser.java</include> - <include>**/ProtobufArrayList.java</include> - <include>**/ProtocolStringList.java</include> - <include>**/RopeByteString.java</include> - <include>**/SmallSortedMap.java</include> - <include>**/UninitializedMessageException.java</include> - <include>**/UnknownFieldSetLite.java</include> - <include>**/UnmodifiableLazyStringList.java</include> - <include>**/Utf8.java</include> - <include>**/WireFormat.java</include> - </includes> - <testIncludes> - <testInclude>**/*Lite.java</testInclude> - <testInclude>**/BooleanArrayListTest.java</testInclude> - <testInclude>**/DoubleArrayListTest.java</testInclude> - <testInclude>**/FloatArrayListTest.java</testInclude> - <testInclude>**/IntArrayListTest.java</testInclude> - <testInclude>**/LazyMessageLiteTest.java</testInclude> - <testInclude>**/LiteTest.java</testInclude> - <testInclude>**/LongArrayListTest.java</testInclude> - <testInclude>**/ProtobufArrayListTest.java</testInclude> - <testInclude>**/UnknownFieldSetLiteTest.java</testInclude> - </testIncludes> - </configuration> - </plugin> - <plugin> - <artifactId>maven-surefire-plugin</artifactId> - <configuration> - <includes> - <include>**/*Test.java</include> - </includes> - </configuration> - </plugin> - <plugin> - <artifactId>maven-jar-plugin</artifactId> - <configuration> - <classifier>lite</classifier> + <serverId>sonatype-nexus-staging</serverId> + <nexusUrl>https://oss.sonatype.org/</nexusUrl> + <autoReleaseAfterClose>false</autoReleaseAfterClose> </configuration> </plugin> </plugins> </build> </profile> </profiles> + + <modules> + <module>core</module> + <module>lite</module> + <module>util</module> + </modules> + </project> diff --git a/java/src/main/java/com/google/protobuf/BoundedByteString.java b/java/src/main/java/com/google/protobuf/BoundedByteString.java deleted file mode 100644 index b4c3fb1b..00000000 --- a/java/src/main/java/com/google/protobuf/BoundedByteString.java +++ /dev/null @@ -1,180 +0,0 @@ -// Protocol Buffers - Google's data interchange format -// Copyright 2008 Google Inc. All rights reserved. -// https://developers.google.com/protocol-buffers/ -// -// 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.io.IOException; -import java.io.InvalidObjectException; -import java.io.ObjectInputStream; -import java.util.NoSuchElementException; - -/** - * This class is used to represent the substring of a {@link ByteString} over a - * single byte array. In terms of the public API of {@link ByteString}, you end - * up here by calling {@link ByteString#copyFrom(byte[])} followed by {@link - * ByteString#substring(int, int)}. - * - * <p>This class contains most of the overhead involved in creating a substring - * from a {@link LiteralByteString}. The overhead involves some range-checking - * and two extra fields. - * - * @author carlanton@google.com (Carl Haverl) - */ -class BoundedByteString extends LiteralByteString { - - private final int bytesOffset; - private final int bytesLength; - - /** - * Creates a {@code BoundedByteString} backed by the sub-range of given array, - * without copying. - * - * @param bytes array to wrap - * @param offset index to first byte to use in bytes - * @param length number of bytes to use from bytes - * @throws IllegalArgumentException if {@code offset < 0}, {@code length < 0}, - * or if {@code offset + length > - * bytes.length}. - */ - BoundedByteString(byte[] bytes, int offset, int length) { - super(bytes); - if (offset < 0) { - throw new IllegalArgumentException("Offset too small: " + offset); - } - if (length < 0) { - throw new IllegalArgumentException("Length too small: " + offset); - } - if ((long) offset + length > bytes.length) { - throw new IllegalArgumentException( - "Offset+Length too large: " + offset + "+" + length); - } - - this.bytesOffset = offset; - this.bytesLength = length; - } - - /** - * Gets the byte at the given index. - * Throws {@link ArrayIndexOutOfBoundsException} - * for backwards-compatibility reasons although it would more properly be - * {@link IndexOutOfBoundsException}. - * - * @param index index of byte - * @return the value - * @throws ArrayIndexOutOfBoundsException {@code index} is < 0 or >= size - */ - @Override - public byte byteAt(int index) { - // We must check the index ourselves as we cannot rely on Java array index - // checking for substrings. - if (index < 0) { - throw new ArrayIndexOutOfBoundsException("Index too small: " + index); - } - if (index >= size()) { - throw new ArrayIndexOutOfBoundsException( - "Index too large: " + index + ", " + size()); - } - - return bytes[bytesOffset + index]; - } - - @Override - public int size() { - return bytesLength; - } - - @Override - protected int getOffsetIntoBytes() { - return bytesOffset; - } - - // ================================================================= - // ByteString -> byte[] - - @Override - protected void copyToInternal(byte[] target, int sourceOffset, - int targetOffset, int numberToCopy) { - System.arraycopy(bytes, getOffsetIntoBytes() + sourceOffset, target, - targetOffset, numberToCopy); - } - - // ================================================================= - // Serializable - - private static final long serialVersionUID = 1L; - - Object writeReplace() { - return new LiteralByteString(toByteArray()); - } - - private void readObject(ObjectInputStream in) throws IOException { - throw new InvalidObjectException( - "BoundedByteStream instances are not to be serialized directly"); - } - - // ================================================================= - // ByteIterator - - @Override - public ByteIterator iterator() { - return new BoundedByteIterator(); - } - - private class BoundedByteIterator implements ByteIterator { - - private int position; - private final int limit; - - private BoundedByteIterator() { - position = getOffsetIntoBytes(); - limit = position + size(); - } - - public boolean hasNext() { - return (position < limit); - } - - public Byte next() { - // Boxing calls Byte.valueOf(byte), which does not instantiate. - return nextByte(); - } - - public byte nextByte() { - if (position >= limit) { - throw new NoSuchElementException(); - } - return bytes[position++]; - } - - public void remove() { - throw new UnsupportedOperationException(); - } - } -} diff --git a/java/src/main/java/com/google/protobuf/CodedOutputStream.java b/java/src/main/java/com/google/protobuf/CodedOutputStream.java deleted file mode 100644 index 291bd20a..00000000 --- a/java/src/main/java/com/google/protobuf/CodedOutputStream.java +++ /dev/null @@ -1,1385 +0,0 @@ -// Protocol Buffers - Google's data interchange format -// Copyright 2008 Google Inc. All rights reserved. -// https://developers.google.com/protocol-buffers/ -// -// 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.Utf8.UnpairedSurrogateException; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * Encodes and writes protocol message fields. - * - * <p>This class contains two kinds of methods: methods that write specific - * protocol message constructs and field types (e.g. {@link #writeTag} and - * {@link #writeInt32}) and methods that write low-level values (e.g. - * {@link #writeRawVarint32} and {@link #writeRawBytes}). If you are - * writing encoded protocol messages, you should use the former methods, but if - * you are writing some other format of your own design, use the latter. - * - * <p>This class is totally unsynchronized. - * - * @author kneton@google.com Kenton Varda - */ -public final class CodedOutputStream { - - private static final Logger logger = Logger.getLogger(CodedOutputStream.class.getName()); - - // TODO(dweis): Consider migrating to a ByteBuffer. - private final byte[] buffer; - private final int limit; - private int position; - private int totalBytesWritten = 0; - - private final OutputStream output; - - /** - * The buffer size used in {@link #newInstance(OutputStream)}. - */ - public static final int DEFAULT_BUFFER_SIZE = 4096; - - /** - * Returns the buffer size to efficiently write dataLength bytes to this - * CodedOutputStream. Used by AbstractMessageLite. - * - * @return the buffer size to efficiently write dataLength bytes to this - * CodedOutputStream. - */ - static int computePreferredBufferSize(int dataLength) { - if (dataLength > DEFAULT_BUFFER_SIZE) return DEFAULT_BUFFER_SIZE; - return dataLength; - } - - private CodedOutputStream(final byte[] buffer, final int offset, - final int length) { - output = null; - this.buffer = buffer; - position = offset; - limit = offset + length; - } - - private CodedOutputStream(final OutputStream output, final byte[] buffer) { - this.output = output; - this.buffer = buffer; - position = 0; - limit = buffer.length; - } - - /** - * Create a new {@code CodedOutputStream} wrapping the given - * {@code OutputStream}. - */ - public static CodedOutputStream newInstance(final OutputStream output) { - return newInstance(output, DEFAULT_BUFFER_SIZE); - } - - /** - * Create a new {@code CodedOutputStream} wrapping the given - * {@code OutputStream} with a given buffer size. - */ - public static CodedOutputStream newInstance(final OutputStream output, - final int bufferSize) { - return new CodedOutputStream(output, new byte[bufferSize]); - } - - /** - * Create a new {@code CodedOutputStream} that writes directly to the given - * byte array. If more bytes are written than fit in the array, - * {@link OutOfSpaceException} will be thrown. Writing directly to a flat - * array is faster than writing to an {@code OutputStream}. See also - * {@link ByteString#newCodedBuilder}. - */ - public static CodedOutputStream newInstance(final byte[] flatArray) { - return newInstance(flatArray, 0, flatArray.length); - } - - /** - * Create a new {@code CodedOutputStream} that writes directly to the given - * byte array slice. If more bytes are written than fit in the slice, - * {@link OutOfSpaceException} will be thrown. Writing directly to a flat - * array is faster than writing to an {@code OutputStream}. See also - * {@link ByteString#newCodedBuilder}. - */ - public static CodedOutputStream newInstance(final byte[] flatArray, - final int offset, - final int length) { - 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. */ - public void writeDouble(final int fieldNumber, final double value) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED64); - writeDoubleNoTag(value); - } - - /** Write a {@code float} field, including tag, to the stream. */ - public void writeFloat(final int fieldNumber, final float value) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED32); - writeFloatNoTag(value); - } - - /** Write a {@code uint64} field, including tag, to the stream. */ - public void writeUInt64(final int fieldNumber, final long value) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); - writeUInt64NoTag(value); - } - - /** Write an {@code int64} field, including tag, to the stream. */ - public void writeInt64(final int fieldNumber, final long value) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); - writeInt64NoTag(value); - } - - /** Write an {@code int32} field, including tag, to the stream. */ - public void writeInt32(final int fieldNumber, final int value) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); - writeInt32NoTag(value); - } - - /** Write a {@code fixed64} field, including tag, to the stream. */ - public void writeFixed64(final int fieldNumber, final long value) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED64); - writeFixed64NoTag(value); - } - - /** Write a {@code fixed32} field, including tag, to the stream. */ - public void writeFixed32(final int fieldNumber, final int value) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED32); - writeFixed32NoTag(value); - } - - /** Write a {@code bool} field, including tag, to the stream. */ - public void writeBool(final int fieldNumber, final boolean value) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); - writeBoolNoTag(value); - } - - /** Write a {@code string} field, including tag, to the stream. */ - public void writeString(final int fieldNumber, final String value) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); - writeStringNoTag(value); - } - - /** Write a {@code group} field, including tag, to the stream. */ - public void writeGroup(final int fieldNumber, final MessageLite value) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_START_GROUP); - writeGroupNoTag(value); - writeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP); - } - - - /** - * Write a group represented by an {@link UnknownFieldSet}. - * - * @deprecated UnknownFieldSet now implements MessageLite, so you can just - * call {@link #writeGroup}. - */ - @Deprecated - public void writeUnknownGroup(final int fieldNumber, - final MessageLite value) - throws IOException { - writeGroup(fieldNumber, value); - } - - /** Write an embedded message field, including tag, to the stream. */ - public void writeMessage(final int fieldNumber, final MessageLite value) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); - writeMessageNoTag(value); - } - - - /** Write a {@code bytes} field, including tag, to the stream. */ - public void writeBytes(final int fieldNumber, final ByteString value) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); - 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 { - writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); - writeUInt32NoTag(value); - } - - /** - * Write an enum field, including tag, to the stream. Caller is responsible - * for converting the enum value to its numeric value. - */ - public void writeEnum(final int fieldNumber, final int value) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); - writeEnumNoTag(value); - } - - /** Write an {@code sfixed32} field, including tag, to the stream. */ - public void writeSFixed32(final int fieldNumber, final int value) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED32); - writeSFixed32NoTag(value); - } - - /** Write an {@code sfixed64} field, including tag, to the stream. */ - public void writeSFixed64(final int fieldNumber, final long value) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_FIXED64); - writeSFixed64NoTag(value); - } - - /** Write an {@code sint32} field, including tag, to the stream. */ - public void writeSInt32(final int fieldNumber, final int value) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); - writeSInt32NoTag(value); - } - - /** Write an {@code sint64} field, including tag, to the stream. */ - public void writeSInt64(final int fieldNumber, final long value) - throws IOException { - writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT); - writeSInt64NoTag(value); - } - - /** - * Write a MessageSet extension field to the stream. For historical reasons, - * the wire format differs from normal fields. - */ - public void writeMessageSetExtension(final int fieldNumber, - final MessageLite value) - throws IOException { - writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_START_GROUP); - writeUInt32(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber); - writeMessage(WireFormat.MESSAGE_SET_MESSAGE, value); - writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_END_GROUP); - } - - /** - * Write an unparsed MessageSet extension field to the stream. For - * historical reasons, the wire format differs from normal fields. - */ - public void writeRawMessageSetExtension(final int fieldNumber, - final ByteString value) - throws IOException { - writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_START_GROUP); - writeUInt32(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber); - writeBytes(WireFormat.MESSAGE_SET_MESSAGE, value); - writeTag(WireFormat.MESSAGE_SET_ITEM, WireFormat.WIRETYPE_END_GROUP); - } - - // ----------------------------------------------------------------- - - /** Write a {@code double} field to the stream. */ - public void writeDoubleNoTag(final double value) throws IOException { - writeRawLittleEndian64(Double.doubleToRawLongBits(value)); - } - - /** Write a {@code float} field to the stream. */ - public void writeFloatNoTag(final float value) throws IOException { - writeRawLittleEndian32(Float.floatToRawIntBits(value)); - } - - /** Write a {@code uint64} field to the stream. */ - public void writeUInt64NoTag(final long value) throws IOException { - writeRawVarint64(value); - } - - /** Write an {@code int64} field to the stream. */ - public void writeInt64NoTag(final long value) throws IOException { - writeRawVarint64(value); - } - - /** Write an {@code int32} field to the stream. */ - public void writeInt32NoTag(final int value) throws IOException { - if (value >= 0) { - writeRawVarint32(value); - } else { - // Must sign-extend. - writeRawVarint64(value); - } - } - - /** Write a {@code fixed64} field to the stream. */ - public void writeFixed64NoTag(final long value) throws IOException { - writeRawLittleEndian64(value); - } - - /** Write a {@code fixed32} field to the stream. */ - public void writeFixed32NoTag(final int value) throws IOException { - writeRawLittleEndian32(value); - } - - /** Write a {@code bool} field to the stream. */ - public void writeBoolNoTag(final boolean value) throws IOException { - writeRawByte(value ? 1 : 0); - } - - /** Write a {@code string} field to the stream. */ - // TODO(dweis): Document behavior on ill-formed UTF-16 input. - public void writeStringNoTag(final String value) throws IOException { - try { - efficientWriteStringNoTag(value); - } catch (UnpairedSurrogateException e) { - logger.log(Level.WARNING, - "Converting ill-formed UTF-16. Your Protocol Buffer will not round trip correctly!", e); - inefficientWriteStringNoTag(value); - } - } - - /** Write a {@code string} field to the stream. */ - private void inefficientWriteStringNoTag(final String value) throws IOException { - // Unfortunately there does not appear to be any way to tell Java to encode - // UTF-8 directly into our buffer, so we have to let it create its own byte - // array and then copy. - // TODO(dweis): Consider using nio Charset methods instead. - final byte[] bytes = value.getBytes(Internal.UTF_8); - writeRawVarint32(bytes.length); - writeRawBytes(bytes); - } - - /** - * Write a {@code string} field to the stream efficiently. If the {@code string} is malformed, - * this method rolls back its changes and throws an {@link UnpairedSurrogateException} with the - * intent that the caller will catch and retry with {@link #inefficientWriteStringNoTag(String)}. - * - * @param value the string to write to the stream - * - * @throws UnpairedSurrogateException when {@code value} is ill-formed UTF-16. - */ - private void efficientWriteStringNoTag(final String value) throws IOException { - // UTF-8 byte length of the string is at least its UTF-16 code unit length (value.length()), - // and at most 3 times of it. We take advantage of this in both branches below. - final int maxLength = value.length() * Utf8.MAX_BYTES_PER_CHAR; - final int maxLengthVarIntSize = computeRawVarint32Size(maxLength); - - // If we are streaming and the potential length is too big to fit in our buffer, we take the - // slower path. Otherwise, we're good to try the fast path. - if (output != null && maxLengthVarIntSize + maxLength > limit - position) { - // Allocate a byte[] that we know can fit the string and encode into it. String.getBytes() - // does the same internally and then does *another copy* to return a byte[] of exactly the - // right size. We can skip that copy and just writeRawBytes up to the actualLength of the - // UTF-8 encoded bytes. - final byte[] encodedBytes = new byte[maxLength]; - int actualLength = Utf8.encode(value, encodedBytes, 0, maxLength); - writeRawVarint32(actualLength); - writeRawBytes(encodedBytes, 0, actualLength); - } else { - // Optimize for the case where we know this length results in a constant varint length as this - // saves a pass for measuring the length of the string. - final int minLengthVarIntSize = computeRawVarint32Size(value.length()); - int oldPosition = position; - final int length; - try { - if (minLengthVarIntSize == maxLengthVarIntSize) { - position = oldPosition + minLengthVarIntSize; - int newPosition = Utf8.encode(value, buffer, position, limit - position); - // Since this class is stateful and tracks the position, we rewind and store the state, - // prepend the length, then reset it back to the end of the string. - position = oldPosition; - length = newPosition - oldPosition - minLengthVarIntSize; - writeRawVarint32(length); - position = newPosition; - } else { - length = Utf8.encodedLength(value); - writeRawVarint32(length); - position = Utf8.encode(value, buffer, position, limit - position); - } - } catch (UnpairedSurrogateException e) { - // Be extra careful and restore the original position for retrying the write with the less - // efficient path. - position = oldPosition; - throw e; - } catch (ArrayIndexOutOfBoundsException e) { - throw new OutOfSpaceException(e); - } - totalBytesWritten += length; - } - } - - /** Write a {@code group} field to the stream. */ - public void writeGroupNoTag(final MessageLite value) throws IOException { - value.writeTo(this); - } - - - /** - * Write a group represented by an {@link UnknownFieldSet}. - * - * @deprecated UnknownFieldSet now implements MessageLite, so you can just - * call {@link #writeGroupNoTag}. - */ - @Deprecated - public void writeUnknownGroupNoTag(final MessageLite value) - throws IOException { - writeGroupNoTag(value); - } - - /** Write an embedded message field to the stream. */ - public void writeMessageNoTag(final MessageLite value) throws IOException { - writeRawVarint32(value.getSerializedSize()); - 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); - } - - /** - * Write an enum field to the stream. Caller is responsible - * for converting the enum value to its numeric value. - */ - public void writeEnumNoTag(final int value) throws IOException { - writeInt32NoTag(value); - } - - /** Write an {@code sfixed32} field to the stream. */ - public void writeSFixed32NoTag(final int value) throws IOException { - writeRawLittleEndian32(value); - } - - /** Write an {@code sfixed64} field to the stream. */ - public void writeSFixed64NoTag(final long value) throws IOException { - writeRawLittleEndian64(value); - } - - /** Write an {@code sint32} field to the stream. */ - public void writeSInt32NoTag(final int value) throws IOException { - writeRawVarint32(encodeZigZag32(value)); - } - - /** Write an {@code sint64} field to the stream. */ - public void writeSInt64NoTag(final long value) throws IOException { - writeRawVarint64(encodeZigZag64(value)); - } - - // ================================================================= - - /** - * Compute the number of bytes that would be needed to encode a - * {@code double} field, including tag. - */ - public static int computeDoubleSize(final int fieldNumber, - final double value) { - return computeTagSize(fieldNumber) + computeDoubleSizeNoTag(value); - } - - /** - * Compute the number of bytes that would be needed to encode a - * {@code float} field, including tag. - */ - public static int computeFloatSize(final int fieldNumber, final float value) { - return computeTagSize(fieldNumber) + computeFloatSizeNoTag(value); - } - - /** - * Compute the number of bytes that would be needed to encode a - * {@code uint64} field, including tag. - */ - public static int computeUInt64Size(final int fieldNumber, final long value) { - return computeTagSize(fieldNumber) + computeUInt64SizeNoTag(value); - } - - /** - * Compute the number of bytes that would be needed to encode an - * {@code int64} field, including tag. - */ - public static int computeInt64Size(final int fieldNumber, final long value) { - return computeTagSize(fieldNumber) + computeInt64SizeNoTag(value); - } - - /** - * Compute the number of bytes that would be needed to encode an - * {@code int32} field, including tag. - */ - public static int computeInt32Size(final int fieldNumber, final int value) { - return computeTagSize(fieldNumber) + computeInt32SizeNoTag(value); - } - - /** - * Compute the number of bytes that would be needed to encode a - * {@code fixed64} field, including tag. - */ - public static int computeFixed64Size(final int fieldNumber, - final long value) { - return computeTagSize(fieldNumber) + computeFixed64SizeNoTag(value); - } - - /** - * Compute the number of bytes that would be needed to encode a - * {@code fixed32} field, including tag. - */ - public static int computeFixed32Size(final int fieldNumber, - final int value) { - return computeTagSize(fieldNumber) + computeFixed32SizeNoTag(value); - } - - /** - * Compute the number of bytes that would be needed to encode a - * {@code bool} field, including tag. - */ - public static int computeBoolSize(final int fieldNumber, - final boolean value) { - return computeTagSize(fieldNumber) + computeBoolSizeNoTag(value); - } - - /** - * Compute the number of bytes that would be needed to encode a - * {@code string} field, including tag. - */ - public static int computeStringSize(final int fieldNumber, - final String value) { - return computeTagSize(fieldNumber) + computeStringSizeNoTag(value); - } - - /** - * Compute the number of bytes that would be needed to encode a - * {@code group} field, including tag. - */ - public static int computeGroupSize(final int fieldNumber, - final MessageLite value) { - return computeTagSize(fieldNumber) * 2 + computeGroupSizeNoTag(value); - } - - /** - * Compute the number of bytes that would be needed to encode a - * {@code group} field represented by an {@code UnknownFieldSet}, including - * tag. - * - * @deprecated UnknownFieldSet now implements MessageLite, so you can just - * call {@link #computeGroupSize}. - */ - @Deprecated - public static int computeUnknownGroupSize(final int fieldNumber, - final MessageLite value) { - return computeGroupSize(fieldNumber, value); - } - - /** - * Compute the number of bytes that would be needed to encode an - * embedded message field, including tag. - */ - public static int computeMessageSize(final int fieldNumber, - final MessageLite value) { - return computeTagSize(fieldNumber) + computeMessageSizeNoTag(value); - } - - /** - * Compute the number of bytes that would be needed to encode a - * {@code bytes} field, including tag. - */ - public static int computeBytesSize(final int fieldNumber, - final ByteString value) { - 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 LazyFieldLite value) { - return computeTagSize(fieldNumber) + computeLazyFieldSizeNoTag(value); - } - - /** - * Compute the number of bytes that would be needed to encode a - * {@code uint32} field, including tag. - */ - public static int computeUInt32Size(final int fieldNumber, final int value) { - return computeTagSize(fieldNumber) + computeUInt32SizeNoTag(value); - } - - /** - * Compute the number of bytes that would be needed to encode an - * enum field, including tag. Caller is responsible for converting the - * enum value to its numeric value. - */ - public static int computeEnumSize(final int fieldNumber, final int value) { - return computeTagSize(fieldNumber) + computeEnumSizeNoTag(value); - } - - /** - * Compute the number of bytes that would be needed to encode an - * {@code sfixed32} field, including tag. - */ - public static int computeSFixed32Size(final int fieldNumber, - final int value) { - return computeTagSize(fieldNumber) + computeSFixed32SizeNoTag(value); - } - - /** - * Compute the number of bytes that would be needed to encode an - * {@code sfixed64} field, including tag. - */ - public static int computeSFixed64Size(final int fieldNumber, - final long value) { - return computeTagSize(fieldNumber) + computeSFixed64SizeNoTag(value); - } - - /** - * Compute the number of bytes that would be needed to encode an - * {@code sint32} field, including tag. - */ - public static int computeSInt32Size(final int fieldNumber, final int value) { - return computeTagSize(fieldNumber) + computeSInt32SizeNoTag(value); - } - - /** - * Compute the number of bytes that would be needed to encode an - * {@code sint64} field, including tag. - */ - public static int computeSInt64Size(final int fieldNumber, final long value) { - return computeTagSize(fieldNumber) + computeSInt64SizeNoTag(value); - } - - /** - * Compute the number of bytes that would be needed to encode a - * MessageSet extension to the stream. For historical reasons, - * the wire format differs from normal fields. - */ - public static int computeMessageSetExtensionSize( - final int fieldNumber, final MessageLite value) { - return computeTagSize(WireFormat.MESSAGE_SET_ITEM) * 2 + - computeUInt32Size(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber) + - computeMessageSize(WireFormat.MESSAGE_SET_MESSAGE, value); - } - - /** - * Compute the number of bytes that would be needed to encode an - * unparsed MessageSet extension field to the stream. For - * historical reasons, the wire format differs from normal fields. - */ - public static int computeRawMessageSetExtensionSize( - final int fieldNumber, final ByteString value) { - return computeTagSize(WireFormat.MESSAGE_SET_ITEM) * 2 + - computeUInt32Size(WireFormat.MESSAGE_SET_TYPE_ID, fieldNumber) + - computeBytesSize(WireFormat.MESSAGE_SET_MESSAGE, value); - } - - /** - * Compute the number of bytes that would be needed to encode an - * lazily parsed MessageSet extension field to the stream. For - * historical reasons, the wire format differs from normal fields. - */ - public static int computeLazyFieldMessageSetExtensionSize( - 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); - } - - // ----------------------------------------------------------------- - - /** - * Compute the number of bytes that would be needed to encode a - * {@code double} field, including tag. - */ - public static int computeDoubleSizeNoTag(final double value) { - return LITTLE_ENDIAN_64_SIZE; - } - - /** - * Compute the number of bytes that would be needed to encode a - * {@code float} field, including tag. - */ - public static int computeFloatSizeNoTag(final float value) { - return LITTLE_ENDIAN_32_SIZE; - } - - /** - * Compute the number of bytes that would be needed to encode a - * {@code uint64} field, including tag. - */ - public static int computeUInt64SizeNoTag(final long value) { - return computeRawVarint64Size(value); - } - - /** - * Compute the number of bytes that would be needed to encode an - * {@code int64} field, including tag. - */ - public static int computeInt64SizeNoTag(final long value) { - return computeRawVarint64Size(value); - } - - /** - * Compute the number of bytes that would be needed to encode an - * {@code int32} field, including tag. - */ - public static int computeInt32SizeNoTag(final int value) { - if (value >= 0) { - return computeRawVarint32Size(value); - } else { - // Must sign-extend. - return 10; - } - } - - /** - * Compute the number of bytes that would be needed to encode a - * {@code fixed64} field. - */ - public static int computeFixed64SizeNoTag(final long value) { - return LITTLE_ENDIAN_64_SIZE; - } - - /** - * Compute the number of bytes that would be needed to encode a - * {@code fixed32} field. - */ - public static int computeFixed32SizeNoTag(final int value) { - return LITTLE_ENDIAN_32_SIZE; - } - - /** - * Compute the number of bytes that would be needed to encode a - * {@code bool} field. - */ - public static int computeBoolSizeNoTag(final boolean value) { - return 1; - } - - /** - * Compute the number of bytes that would be needed to encode a - * {@code string} field. - */ - public static int computeStringSizeNoTag(final String value) { - int length; - try { - length = Utf8.encodedLength(value); - } catch (UnpairedSurrogateException e) { - // TODO(dweis): Consider using nio Charset methods instead. - final byte[] bytes = value.getBytes(Internal.UTF_8); - length = bytes.length; - } - - return computeRawVarint32Size(length) + length; - } - - /** - * Compute the number of bytes that would be needed to encode a - * {@code group} field. - */ - public static int computeGroupSizeNoTag(final MessageLite value) { - return value.getSerializedSize(); - } - - /** - * Compute the number of bytes that would be needed to encode a - * {@code group} field represented by an {@code UnknownFieldSet}, including - * tag. - * - * @deprecated UnknownFieldSet now implements MessageLite, so you can just - * call {@link #computeUnknownGroupSizeNoTag}. - */ - @Deprecated - public static int computeUnknownGroupSizeNoTag(final MessageLite value) { - return computeGroupSizeNoTag(value); - } - - /** - * Compute the number of bytes that would be needed to encode an embedded - * message field. - */ - public static int computeMessageSizeNoTag(final MessageLite value) { - final int size = value.getSerializedSize(); - return computeRawVarint32Size(size) + size; - } - - /** - * Compute the number of bytes that would be needed to encode an embedded - * message stored in lazy field. - */ - public static int computeLazyFieldSizeNoTag(final LazyFieldLite value) { - final int size = value.getSerializedSize(); - return computeRawVarint32Size(size) + size; - } - - /** - * Compute the number of bytes that would be needed to encode a - * {@code bytes} field. - */ - public static int computeBytesSizeNoTag(final ByteString value) { - return computeRawVarint32Size(value.size()) + - 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. - */ - public static int computeUInt32SizeNoTag(final int value) { - return computeRawVarint32Size(value); - } - - /** - * Compute the number of bytes that would be needed to encode an enum field. - * Caller is responsible for converting the enum value to its numeric value. - */ - public static int computeEnumSizeNoTag(final int value) { - return computeInt32SizeNoTag(value); - } - - /** - * Compute the number of bytes that would be needed to encode an - * {@code sfixed32} field. - */ - public static int computeSFixed32SizeNoTag(final int value) { - return LITTLE_ENDIAN_32_SIZE; - } - - /** - * Compute the number of bytes that would be needed to encode an - * {@code sfixed64} field. - */ - public static int computeSFixed64SizeNoTag(final long value) { - return LITTLE_ENDIAN_64_SIZE; - } - - /** - * Compute the number of bytes that would be needed to encode an - * {@code sint32} field. - */ - public static int computeSInt32SizeNoTag(final int value) { - return computeRawVarint32Size(encodeZigZag32(value)); - } - - /** - * Compute the number of bytes that would be needed to encode an - * {@code sint64} field. - */ - public static int computeSInt64SizeNoTag(final long value) { - return computeRawVarint64Size(encodeZigZag64(value)); - } - - // ================================================================= - - /** - * Internal helper that writes the current buffer to the output. The - * buffer position is reset to its initial value when this returns. - */ - private void refreshBuffer() throws IOException { - if (output == null) { - // We're writing to a single buffer. - throw new OutOfSpaceException(); - } - - // Since we have an output stream, this is our buffer - // and buffer offset == 0 - output.write(buffer, 0, position); - position = 0; - } - - /** - * Flushes the stream and forces any buffered bytes to be written. This - * does not flush the underlying OutputStream. - */ - public void flush() throws IOException { - if (output != null) { - refreshBuffer(); - } - } - - /** - * If writing to a flat array, return the space left in the array. - * Otherwise, throws {@code UnsupportedOperationException}. - */ - public int spaceLeft() { - if (output == null) { - return limit - position; - } else { - throw new UnsupportedOperationException( - "spaceLeft() can only be called on CodedOutputStreams that are " + - "writing to a flat array."); - } - } - - /** - * Verifies that {@link #spaceLeft()} returns zero. It's common to create - * a byte array that is exactly big enough to hold a message, then write to - * it with a {@code CodedOutputStream}. Calling {@code checkNoSpaceLeft()} - * after writing verifies that the message was actually as big as expected, - * which can help catch bugs. - */ - public void checkNoSpaceLeft() { - if (spaceLeft() != 0) { - throw new IllegalStateException( - "Did not write as much data as expected."); - } - } - - /** - * If you create a CodedOutputStream around a simple flat array, you must - * not attempt to write more bytes than the array has space. Otherwise, - * this exception will be thrown. - */ - public static class OutOfSpaceException extends IOException { - private static final long serialVersionUID = -6947486886997889499L; - - private static final String MESSAGE = - "CodedOutputStream was writing to a flat byte array and ran out of space."; - - OutOfSpaceException() { - super(MESSAGE); - } - - OutOfSpaceException(Throwable cause) { - super(MESSAGE, cause); - } - } - - /** - * 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) { - refreshBuffer(); - } - - buffer[position++] = value; - ++totalBytesWritten; - } - - /** Write a single byte, represented by an integer value. */ - public void writeRawByte(final int value) throws IOException { - writeRawByte((byte) value); - } - - /** Write a byte string. */ - public void writeRawBytes(final ByteString value) throws IOException { - writeRawBytes(value, 0, value.size()); - } - - /** Write an array of bytes. */ - public void writeRawBytes(final byte[] value) throws IOException { - writeRawBytes(value, 0, value.length); - } - - /** - * 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 { - if (limit - position >= length) { - // 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. - final int bytesWritten = limit - position; - System.arraycopy(value, offset, buffer, position, bytesWritten); - offset += 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 - if (length <= limit) { - // Fits in new buffer. - System.arraycopy(value, offset, buffer, 0, length); - position = length; - } else { - // Write is very big. Let's do it all at once. - output.write(value, offset, length); - } - totalBytesWritten += length; - } - } - - /** Write part of a byte string. */ - public void writeRawBytes(final ByteString value, int offset, int length) - throws IOException { - if (limit - position >= length) { - // We have room in the current buffer. - value.copyTo(buffer, offset, position, length); - position += length; - totalBytesWritten += length; - } else { - // Write extends past current buffer. Fill the rest of this buffer and - // flush. - final int bytesWritten = limit - position; - value.copyTo(buffer, offset, position, bytesWritten); - offset += bytesWritten; - length -= bytesWritten; - position = limit; - totalBytesWritten += bytesWritten; - refreshBuffer(); - - // Now deal with the rest. - // Since we have an output stream, this is our buffer - // and buffer offset == 0 - if (length <= limit) { - // Fits in new buffer. - value.copyTo(buffer, offset, 0, length); - position = length; - } else { - value.writeTo(output, offset, length); - } - totalBytesWritten += length; - } - } - - /** Encode and write a tag. */ - public void writeTag(final int fieldNumber, final int wireType) - throws IOException { - writeRawVarint32(WireFormat.makeTag(fieldNumber, wireType)); - } - - /** Compute the number of bytes that would be needed to encode a tag. */ - public static int computeTagSize(final int fieldNumber) { - return computeRawVarint32Size(WireFormat.makeTag(fieldNumber, 0)); - } - - /** - * Encode and write a varint. {@code value} is treated as - * unsigned, so it won't be sign-extended if negative. - */ - public void writeRawVarint32(int value) throws IOException { - while (true) { - if ((value & ~0x7F) == 0) { - writeRawByte(value); - return; - } else { - writeRawByte((value & 0x7F) | 0x80); - value >>>= 7; - } - } - } - - /** - * Compute the number of bytes that would be needed to encode a varint. - * {@code value} is treated as unsigned, so it won't be sign-extended if - * negative. - */ - public static int computeRawVarint32Size(final int value) { - if ((value & (0xffffffff << 7)) == 0) return 1; - if ((value & (0xffffffff << 14)) == 0) return 2; - if ((value & (0xffffffff << 21)) == 0) return 3; - if ((value & (0xffffffff << 28)) == 0) return 4; - return 5; - } - - /** Encode and write a varint. */ - public void writeRawVarint64(long value) throws IOException { - while (true) { - if ((value & ~0x7FL) == 0) { - writeRawByte((int)value); - return; - } else { - writeRawByte(((int)value & 0x7F) | 0x80); - value >>>= 7; - } - } - } - - /** Compute the number of bytes that would be needed to encode a varint. */ - public static int computeRawVarint64Size(final long value) { - if ((value & (0xffffffffffffffffL << 7)) == 0) return 1; - if ((value & (0xffffffffffffffffL << 14)) == 0) return 2; - if ((value & (0xffffffffffffffffL << 21)) == 0) return 3; - if ((value & (0xffffffffffffffffL << 28)) == 0) return 4; - if ((value & (0xffffffffffffffffL << 35)) == 0) return 5; - if ((value & (0xffffffffffffffffL << 42)) == 0) return 6; - if ((value & (0xffffffffffffffffL << 49)) == 0) return 7; - if ((value & (0xffffffffffffffffL << 56)) == 0) return 8; - if ((value & (0xffffffffffffffffL << 63)) == 0) return 9; - return 10; - } - - /** Write a little-endian 32-bit integer. */ - public void writeRawLittleEndian32(final int value) throws IOException { - writeRawByte((value ) & 0xFF); - writeRawByte((value >> 8) & 0xFF); - writeRawByte((value >> 16) & 0xFF); - writeRawByte((value >> 24) & 0xFF); - } - - public static final int LITTLE_ENDIAN_32_SIZE = 4; - - /** Write a little-endian 64-bit integer. */ - public void writeRawLittleEndian64(final long value) throws IOException { - writeRawByte((int)(value ) & 0xFF); - writeRawByte((int)(value >> 8) & 0xFF); - writeRawByte((int)(value >> 16) & 0xFF); - writeRawByte((int)(value >> 24) & 0xFF); - writeRawByte((int)(value >> 32) & 0xFF); - writeRawByte((int)(value >> 40) & 0xFF); - writeRawByte((int)(value >> 48) & 0xFF); - writeRawByte((int)(value >> 56) & 0xFF); - } - - public static final int LITTLE_ENDIAN_64_SIZE = 8; - - /** - * Encode a ZigZag-encoded 32-bit value. ZigZag encodes signed integers - * into values that can be efficiently encoded with varint. (Otherwise, - * negative values must be sign-extended to 64 bits to be varint encoded, - * thus always taking 10 bytes on the wire.) - * - * @param n A signed 32-bit integer. - * @return An unsigned 32-bit integer, stored in a signed int because - * Java has no explicit unsigned support. - */ - public static int encodeZigZag32(final int n) { - // Note: the right-shift must be arithmetic - return (n << 1) ^ (n >> 31); - } - - /** - * Encode a ZigZag-encoded 64-bit value. ZigZag encodes signed integers - * into values that can be efficiently encoded with varint. (Otherwise, - * negative values must be sign-extended to 64 bits to be varint encoded, - * thus always taking 10 bytes on the wire.) - * - * @param n A signed 64-bit integer. - * @return An unsigned 64-bit integer, stored in a signed int because - * Java has no explicit unsigned support. - */ - public static long encodeZigZag64(final long n) { - // Note: the right-shift must be arithmetic - return (n << 1) ^ (n >> 63); - } -} diff --git a/java/src/main/java/com/google/protobuf/LiteralByteString.java b/java/src/main/java/com/google/protobuf/LiteralByteString.java deleted file mode 100644 index c5a8512a..00000000 --- a/java/src/main/java/com/google/protobuf/LiteralByteString.java +++ /dev/null @@ -1,372 +0,0 @@ -// Protocol Buffers - Google's data interchange format -// Copyright 2008 Google Inc. All rights reserved. -// https://developers.google.com/protocol-buffers/ -// -// 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.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.List; -import java.util.NoSuchElementException; - -/** - * This class implements a {@link com.google.protobuf.ByteString} backed by a - * single array of bytes, contiguous in memory. It supports substring by - * pointing to only a sub-range of the underlying byte array, meaning that a - * substring will reference the full byte-array of the string it's made from, - * exactly as with {@link String}. - * - * @author carlanton@google.com (Carl Haverl) - */ -class LiteralByteString extends ByteString { - - private static final long serialVersionUID = 1L; - - protected final byte[] bytes; - - /** - * Creates a {@code LiteralByteString} backed by the given array, without - * copying. - * - * @param bytes array to wrap - */ - LiteralByteString(byte[] bytes) { - this.bytes = bytes; - } - - @Override - public byte byteAt(int index) { - // Unlike most methods in this class, this one is a direct implementation - // ignoring the potential offset because we need to do range-checking in the - // substring case anyway. - return bytes[index]; - } - - @Override - public int size() { - return bytes.length; - } - - // ================================================================= - // ByteString -> substring - - @Override - public ByteString substring(int beginIndex, int endIndex) { - if (beginIndex < 0) { - throw new IndexOutOfBoundsException( - "Beginning index: " + beginIndex + " < 0"); - } - if (endIndex > size()) { - throw new IndexOutOfBoundsException("End index: " + endIndex + " > " + - size()); - } - int substringLength = endIndex - beginIndex; - if (substringLength < 0) { - throw new IndexOutOfBoundsException( - "Beginning index larger than ending index: " + beginIndex + ", " - + endIndex); - } - - ByteString result; - if (substringLength == 0) { - result = ByteString.EMPTY; - } else { - result = new BoundedByteString(bytes, getOffsetIntoBytes() + beginIndex, - substringLength); - } - return result; - } - - // ================================================================= - // ByteString -> byte[] - - @Override - protected void copyToInternal(byte[] target, int sourceOffset, - int targetOffset, int numberToCopy) { - // Optimized form, not for subclasses, since we don't call - // getOffsetIntoBytes() or check the 'numberToCopy' parameter. - System.arraycopy(bytes, sourceOffset, target, targetOffset, numberToCopy); - } - - @Override - public void copyTo(ByteBuffer target) { - target.put(bytes, getOffsetIntoBytes(), size()); // Copies bytes - } - - @Override - public ByteBuffer asReadOnlyByteBuffer() { - ByteBuffer byteBuffer = - ByteBuffer.wrap(bytes, getOffsetIntoBytes(), size()); - return byteBuffer.asReadOnlyBuffer(); - } - - @Override - public List<ByteBuffer> asReadOnlyByteBufferList() { - // Return the ByteBuffer generated by asReadOnlyByteBuffer() as a singleton - List<ByteBuffer> result = new ArrayList<ByteBuffer>(1); - result.add(asReadOnlyByteBuffer()); - return result; - } - - @Override - public void writeTo(OutputStream outputStream) throws IOException { - outputStream.write(toByteArray()); - } - - @Override - void writeToInternal(OutputStream outputStream, int sourceOffset, - int numberToWrite) throws IOException { - outputStream.write(bytes, getOffsetIntoBytes() + sourceOffset, - numberToWrite); - } - - @Override - protected String toStringInternal(Charset charset) { - return new String(bytes, getOffsetIntoBytes(), size(), charset); - } - - // ================================================================= - // UTF-8 decoding - - @Override - public boolean isValidUtf8() { - int offset = getOffsetIntoBytes(); - return Utf8.isValidUtf8(bytes, offset, offset + size()); - } - - @Override - protected int partialIsValidUtf8(int state, int offset, int length) { - int index = getOffsetIntoBytes() + offset; - return Utf8.partialIsValidUtf8(state, bytes, index, index + length); - } - - // ================================================================= - // equals() and hashCode() - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - if (!(other instanceof ByteString)) { - return false; - } - - if (size() != ((ByteString) other).size()) { - return false; - } - if (size() == 0) { - return true; - } - - if (other instanceof LiteralByteString) { - LiteralByteString otherAsLiteral = (LiteralByteString) other; - // If we know the hash codes and they are not equal, we know the byte - // strings are not equal. - if (hash != 0 - && otherAsLiteral.hash != 0 - && hash != otherAsLiteral.hash) { - return false; - } - - return equalsRange((LiteralByteString) other, 0, size()); - } else if (other instanceof RopeByteString) { - return other.equals(this); - } else { - throw new IllegalArgumentException( - "Has a new type of ByteString been created? Found " - + other.getClass()); - } - } - - /** - * Check equality of the substring of given length of this object starting at - * zero with another {@code LiteralByteString} substring starting at offset. - * - * @param other what to compare a substring in - * @param offset offset into other - * @param length number of bytes to compare - * @return true for equality of substrings, else false. - */ - boolean equalsRange(LiteralByteString other, int offset, int length) { - if (length > other.size()) { - throw new IllegalArgumentException( - "Length too large: " + length + size()); - } - if (offset + length > other.size()) { - throw new IllegalArgumentException( - "Ran off end of other: " + offset + ", " + length + ", " + - other.size()); - } - - byte[] thisBytes = bytes; - byte[] otherBytes = other.bytes; - int thisLimit = getOffsetIntoBytes() + length; - for (int thisIndex = getOffsetIntoBytes(), otherIndex = - other.getOffsetIntoBytes() + offset; - (thisIndex < thisLimit); ++thisIndex, ++otherIndex) { - if (thisBytes[thisIndex] != otherBytes[otherIndex]) { - return false; - } - } - return true; - } - - /** - * Cached hash value. Intentionally accessed via a data race, which - * is safe because of the Java Memory Model's "no out-of-thin-air values" - * guarantees for ints. - */ - private int hash = 0; - - /** - * Compute the hashCode using the traditional algorithm from {@link - * ByteString}. - * - * @return hashCode value - */ - @Override - public int hashCode() { - int h = hash; - - if (h == 0) { - int size = size(); - h = partialHash(size, 0, size); - if (h == 0) { - h = 1; - } - hash = h; - } - return h; - } - - @Override - protected int peekCachedHashCode() { - return hash; - } - - @Override - protected int partialHash(int h, int offset, int length) { - 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 - - @Override - public InputStream newInput() { - return new ByteArrayInputStream(bytes, getOffsetIntoBytes(), - size()); // No copy - } - - @Override - public CodedInputStream newCodedInput() { - // We trust CodedInputStream not to modify the bytes, or to give anyone - // else access to them. - return CodedInputStream.newInstance(this); - } - - // ================================================================= - // ByteIterator - - @Override - public ByteIterator iterator() { - return new LiteralByteIterator(); - } - - private class LiteralByteIterator implements ByteIterator { - private int position; - private final int limit; - - private LiteralByteIterator() { - position = 0; - limit = size(); - } - - public boolean hasNext() { - return (position < limit); - } - - public Byte next() { - // Boxing calls Byte.valueOf(byte), which does not instantiate. - return nextByte(); - } - - public byte nextByte() { - try { - return bytes[position++]; - } catch (ArrayIndexOutOfBoundsException e) { - throw new NoSuchElementException(e.getMessage()); - } - } - - public void remove() { - throw new UnsupportedOperationException(); - } - } - - // ================================================================= - // Internal methods - - @Override - protected int getTreeDepth() { - return 0; - } - - @Override - protected boolean isBalanced() { - return true; - } - - /** - * Offset into {@code bytes[]} to use, non-zero for substrings. - * - * @return always 0 for this class - */ - protected int getOffsetIntoBytes() { - return 0; - } -} diff --git a/java/src/main/java/com/google/protobuf/Utf8.java b/java/src/main/java/com/google/protobuf/Utf8.java deleted file mode 100644 index 48c7e9e6..00000000 --- a/java/src/main/java/com/google/protobuf/Utf8.java +++ /dev/null @@ -1,481 +0,0 @@ -// Protocol Buffers - Google's data interchange format -// Copyright 2008 Google Inc. All rights reserved. -// https://developers.google.com/protocol-buffers/ -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -package com.google.protobuf; - -/** - * A set of low-level, high-performance static utility methods related - * to the UTF-8 character encoding. This class has no dependencies - * outside of the core JDK libraries. - * - * <p>There are several variants of UTF-8. The one implemented by - * this class is the restricted definition of UTF-8 introduced in - * Unicode 3.1, which mandates the rejection of "overlong" byte - * sequences as well as rejection of 3-byte surrogate codepoint byte - * sequences. Note that the UTF-8 decoder included in Oracle's JDK - * has been modified to also reject "overlong" byte sequences, but (as - * of 2011) still accepts 3-byte surrogate codepoint byte sequences. - * - * <p>The byte sequences considered valid by this class are exactly - * those that can be roundtrip converted to Strings and back to bytes - * using the UTF-8 charset, without loss: <pre> {@code - * Arrays.equals(bytes, new String(bytes, Internal.UTF_8).getBytes(Internal.UTF_8)) - * }</pre> - * - * <p>See the Unicode Standard,</br> - * Table 3-6. <em>UTF-8 Bit Distribution</em>,</br> - * Table 3-7. <em>Well Formed UTF-8 Byte Sequences</em>. - * - * <p>This class supports decoding of partial byte sequences, so that the - * bytes in a complete UTF-8 byte sequences can be stored in multiple - * segments. Methods typically return {@link #MALFORMED} if the partial - * byte sequence is definitely not well-formed, {@link #COMPLETE} if it is - * well-formed in the absence of additional input, or if the byte sequence - * apparently terminated in the middle of a character, an opaque integer - * "state" value containing enough information to decode the character when - * passed to a subsequent invocation of a partial decoding method. - * - * @author martinrb@google.com (Martin Buchholz) - */ -final class Utf8 { - private Utf8() {} - - /** - * Maximum number of bytes per Java UTF-16 char in UTF-8. - * @see java.nio.charset.CharsetEncoder#maxBytesPerChar() - */ - static final int MAX_BYTES_PER_CHAR = 3; - - /** - * State value indicating that the byte sequence is well-formed and - * complete (no further bytes are needed to complete a character). - */ - public static final int COMPLETE = 0; - - /** - * State value indicating that the byte sequence is definitely not - * well-formed. - */ - public static final int MALFORMED = -1; - - // Other state values include the partial bytes of the incomplete - // character to be decoded in the simplest way: we pack the bytes - // into the state int in little-endian order. For example: - // - // int state = byte1 ^ (byte2 << 8) ^ (byte3 << 16); - // - // Such a state is unpacked thus (note the ~ operation for byte2 to - // undo byte1's sign-extension bits): - // - // int byte1 = (byte) state; - // int byte2 = (byte) ~(state >> 8); - // int byte3 = (byte) (state >> 16); - // - // We cannot store a zero byte in the state because it would be - // indistinguishable from the absence of a byte. But we don't need - // to, because partial bytes must always be negative. When building - // a state, we ensure that byte1 is negative and subsequent bytes - // are valid trailing bytes. - - /** - * Returns {@code true} if the given byte array is a well-formed - * UTF-8 byte sequence. - * - * <p>This is a convenience method, equivalent to a call to {@code - * isValidUtf8(bytes, 0, bytes.length)}. - */ - public static boolean isValidUtf8(byte[] bytes) { - return isValidUtf8(bytes, 0, bytes.length); - } - - /** - * Returns {@code true} if the given byte array slice is a - * well-formed UTF-8 byte sequence. The range of bytes to be - * checked extends from index {@code index}, inclusive, to {@code - * limit}, exclusive. - * - * <p>This is a convenience method, equivalent to {@code - * partialIsValidUtf8(bytes, index, limit) == Utf8.COMPLETE}. - */ - public static boolean isValidUtf8(byte[] bytes, int index, int limit) { - return partialIsValidUtf8(bytes, index, limit) == COMPLETE; - } - - /** - * Tells whether the given byte array slice is a well-formed, - * malformed, or incomplete UTF-8 byte sequence. The range of bytes - * to be checked extends from index {@code index}, inclusive, to - * {@code limit}, exclusive. - * - * @param state either {@link Utf8#COMPLETE} (if this is the initial decoding - * operation) or the value returned from a call to a partial decoding method - * for the previous bytes - * - * @return {@link #MALFORMED} if the partial byte sequence is - * definitely not well-formed, {@link #COMPLETE} if it is well-formed - * (no additional input needed), or if the byte sequence is - * "incomplete", i.e. apparently terminated in the middle of a character, - * an opaque integer "state" value containing enough information to - * decode the character when passed to a subsequent invocation of a - * partial decoding method. - */ - public static int partialIsValidUtf8( - int state, byte[] bytes, int index, int limit) { - if (state != COMPLETE) { - // The previous decoding operation was incomplete (or malformed). - // We look for a well-formed sequence consisting of bytes from - // the previous decoding operation (stored in state) together - // with bytes from the array slice. - // - // We expect such "straddler characters" to be rare. - - if (index >= limit) { // No bytes? No progress. - return state; - } - int byte1 = (byte) state; - // byte1 is never ASCII. - if (byte1 < (byte) 0xE0) { - // two-byte form - - // Simultaneously checks for illegal trailing-byte in - // leading position and overlong 2-byte form. - if (byte1 < (byte) 0xC2 || - // byte2 trailing-byte test - bytes[index++] > (byte) 0xBF) { - return MALFORMED; - } - } else if (byte1 < (byte) 0xF0) { - // three-byte form - - // Get byte2 from saved state or array - int byte2 = (byte) ~(state >> 8); - if (byte2 == 0) { - byte2 = bytes[index++]; - if (index >= limit) { - return incompleteStateFor(byte1, byte2); - } - } - if (byte2 > (byte) 0xBF || - // overlong? 5 most significant bits must not all be zero - (byte1 == (byte) 0xE0 && byte2 < (byte) 0xA0) || - // illegal surrogate codepoint? - (byte1 == (byte) 0xED && byte2 >= (byte) 0xA0) || - // byte3 trailing-byte test - bytes[index++] > (byte) 0xBF) { - return MALFORMED; - } - } else { - // four-byte form - - // Get byte2 and byte3 from saved state or array - int byte2 = (byte) ~(state >> 8); - int byte3 = 0; - if (byte2 == 0) { - byte2 = bytes[index++]; - if (index >= limit) { - return incompleteStateFor(byte1, byte2); - } - } else { - byte3 = (byte) (state >> 16); - } - if (byte3 == 0) { - byte3 = bytes[index++]; - if (index >= limit) { - return incompleteStateFor(byte1, byte2, byte3); - } - } - - // If we were called with state == MALFORMED, then byte1 is 0xFF, - // which never occurs in well-formed UTF-8, and so we will return - // MALFORMED again below. - - if (byte2 > (byte) 0xBF || - // Check that 1 <= plane <= 16. Tricky optimized form of: - // if (byte1 > (byte) 0xF4 || - // byte1 == (byte) 0xF0 && byte2 < (byte) 0x90 || - // byte1 == (byte) 0xF4 && byte2 > (byte) 0x8F) - (((byte1 << 28) + (byte2 - (byte) 0x90)) >> 30) != 0 || - // byte3 trailing-byte test - byte3 > (byte) 0xBF || - // byte4 trailing-byte test - bytes[index++] > (byte) 0xBF) { - return MALFORMED; - } - } - } - - return partialIsValidUtf8(bytes, index, limit); - } - - /** - * Tells whether the given byte array slice is a well-formed, - * malformed, or incomplete UTF-8 byte sequence. The range of bytes - * to be checked extends from index {@code index}, inclusive, to - * {@code limit}, exclusive. - * - * <p>This is a convenience method, equivalent to a call to {@code - * partialIsValidUtf8(Utf8.COMPLETE, bytes, index, limit)}. - * - * @return {@link #MALFORMED} if the partial byte sequence is - * definitely not well-formed, {@link #COMPLETE} if it is well-formed - * (no additional input needed), or if the byte sequence is - * "incomplete", i.e. apparently terminated in the middle of a character, - * an opaque integer "state" value containing enough information to - * decode the character when passed to a subsequent invocation of a - * partial decoding method. - */ - public static int partialIsValidUtf8( - byte[] bytes, int index, int limit) { - // Optimize for 100% ASCII. - // Hotspot loves small simple top-level loops like this. - while (index < limit && bytes[index] >= 0) { - index++; - } - - return (index >= limit) ? COMPLETE : - partialIsValidUtf8NonAscii(bytes, index, limit); - } - - private static int partialIsValidUtf8NonAscii( - byte[] bytes, int index, int limit) { - for (;;) { - int byte1, byte2; - - // Optimize for interior runs of ASCII bytes. - do { - if (index >= limit) { - return COMPLETE; - } - } while ((byte1 = bytes[index++]) >= 0); - - if (byte1 < (byte) 0xE0) { - // two-byte form - - if (index >= limit) { - return byte1; - } - - // Simultaneously checks for illegal trailing-byte in - // leading position and overlong 2-byte form. - if (byte1 < (byte) 0xC2 || - bytes[index++] > (byte) 0xBF) { - return MALFORMED; - } - } else if (byte1 < (byte) 0xF0) { - // three-byte form - - if (index >= limit - 1) { // incomplete sequence - return incompleteStateFor(bytes, index, limit); - } - if ((byte2 = bytes[index++]) > (byte) 0xBF || - // overlong? 5 most significant bits must not all be zero - (byte1 == (byte) 0xE0 && byte2 < (byte) 0xA0) || - // check for illegal surrogate codepoints - (byte1 == (byte) 0xED && byte2 >= (byte) 0xA0) || - // byte3 trailing-byte test - bytes[index++] > (byte) 0xBF) { - return MALFORMED; - } - } else { - // four-byte form - - if (index >= limit - 2) { // incomplete sequence - return incompleteStateFor(bytes, index, limit); - } - if ((byte2 = bytes[index++]) > (byte) 0xBF || - // Check that 1 <= plane <= 16. Tricky optimized form of: - // if (byte1 > (byte) 0xF4 || - // byte1 == (byte) 0xF0 && byte2 < (byte) 0x90 || - // byte1 == (byte) 0xF4 && byte2 > (byte) 0x8F) - (((byte1 << 28) + (byte2 - (byte) 0x90)) >> 30) != 0 || - // byte3 trailing-byte test - bytes[index++] > (byte) 0xBF || - // byte4 trailing-byte test - bytes[index++] > (byte) 0xBF) { - return MALFORMED; - } - } - } - } - - private static int incompleteStateFor(int byte1) { - return (byte1 > (byte) 0xF4) ? - MALFORMED : byte1; - } - - private static int incompleteStateFor(int byte1, int byte2) { - return (byte1 > (byte) 0xF4 || - byte2 > (byte) 0xBF) ? - MALFORMED : byte1 ^ (byte2 << 8); - } - - private static int incompleteStateFor(int byte1, int byte2, int byte3) { - return (byte1 > (byte) 0xF4 || - byte2 > (byte) 0xBF || - byte3 > (byte) 0xBF) ? - MALFORMED : byte1 ^ (byte2 << 8) ^ (byte3 << 16); - } - - private static int incompleteStateFor(byte[] bytes, int index, int limit) { - int byte1 = bytes[index - 1]; - switch (limit - index) { - case 0: return incompleteStateFor(byte1); - case 1: return incompleteStateFor(byte1, bytes[index]); - case 2: return incompleteStateFor(byte1, bytes[index], bytes[index + 1]); - default: throw new AssertionError(); - } - } - - - // These UTF-8 handling methods are copied from Guava's Utf8 class with a modification to throw - // a protocol buffer local exception. This exception is then caught in CodedOutputStream so it can - // fallback to more lenient behavior. - - static class UnpairedSurrogateException extends IllegalArgumentException { - - private UnpairedSurrogateException(int index, int length) { - super("Unpaired surrogate at index " + index + " of " + length); - } - } - - /** - * Returns the number of bytes in the UTF-8-encoded form of {@code sequence}. For a string, - * this method is equivalent to {@code string.getBytes(UTF_8).length}, but is more efficient in - * both time and space. - * - * @throws IllegalArgumentException if {@code sequence} contains ill-formed UTF-16 (unpaired - * surrogates) - */ - static int encodedLength(CharSequence sequence) { - // Warning to maintainers: this implementation is highly optimized. - int utf16Length = sequence.length(); - int utf8Length = utf16Length; - int i = 0; - - // This loop optimizes for pure ASCII. - while (i < utf16Length && sequence.charAt(i) < 0x80) { - i++; - } - - // This loop optimizes for chars less than 0x800. - for (; i < utf16Length; i++) { - char c = sequence.charAt(i); - if (c < 0x800) { - utf8Length += ((0x7f - c) >>> 31); // branch free! - } else { - utf8Length += encodedLengthGeneral(sequence, i); - break; - } - } - - if (utf8Length < utf16Length) { - // Necessary and sufficient condition for overflow because of maximum 3x expansion - throw new IllegalArgumentException("UTF-8 length does not fit in int: " - + (utf8Length + (1L << 32))); - } - return utf8Length; - } - - private static int encodedLengthGeneral(CharSequence sequence, int start) { - int utf16Length = sequence.length(); - int utf8Length = 0; - for (int i = start; i < utf16Length; i++) { - char c = sequence.charAt(i); - if (c < 0x800) { - utf8Length += (0x7f - c) >>> 31; // branch free! - } else { - utf8Length += 2; - // jdk7+: if (Character.isSurrogate(c)) { - if (Character.MIN_SURROGATE <= c && c <= Character.MAX_SURROGATE) { - // Check that we have a well-formed surrogate pair. - int cp = Character.codePointAt(sequence, i); - if (cp < Character.MIN_SUPPLEMENTARY_CODE_POINT) { - throw new UnpairedSurrogateException(i, utf16Length); - } - i++; - } - } - } - return utf8Length; - } - - static int encode(CharSequence sequence, byte[] bytes, int offset, int length) { - int utf16Length = sequence.length(); - int j = offset; - int i = 0; - int limit = offset + length; - // Designed to take advantage of - // https://wikis.oracle.com/display/HotSpotInternals/RangeCheckElimination - for (char c; i < utf16Length && i + j < limit && (c = sequence.charAt(i)) < 0x80; i++) { - bytes[j + i] = (byte) c; - } - if (i == utf16Length) { - return j + utf16Length; - } - j += i; - for (char c; i < utf16Length; i++) { - c = sequence.charAt(i); - if (c < 0x80 && j < limit) { - bytes[j++] = (byte) c; - } else if (c < 0x800 && j <= limit - 2) { // 11 bits, two UTF-8 bytes - bytes[j++] = (byte) ((0xF << 6) | (c >>> 6)); - bytes[j++] = (byte) (0x80 | (0x3F & c)); - } else if ((c < Character.MIN_SURROGATE || Character.MAX_SURROGATE < c) && j <= limit - 3) { - // Maximum single-char code point is 0xFFFF, 16 bits, three UTF-8 bytes - bytes[j++] = (byte) ((0xF << 5) | (c >>> 12)); - bytes[j++] = (byte) (0x80 | (0x3F & (c >>> 6))); - bytes[j++] = (byte) (0x80 | (0x3F & c)); - } else if (j <= limit - 4) { - // Minimum code point represented by a surrogate pair is 0x10000, 17 bits, four UTF-8 bytes - final char low; - if (i + 1 == sequence.length() - || !Character.isSurrogatePair(c, (low = sequence.charAt(++i)))) { - throw new UnpairedSurrogateException((i - 1), utf16Length); - } - int codePoint = Character.toCodePoint(c, low); - bytes[j++] = (byte) ((0xF << 4) | (codePoint >>> 18)); - bytes[j++] = (byte) (0x80 | (0x3F & (codePoint >>> 12))); - bytes[j++] = (byte) (0x80 | (0x3F & (codePoint >>> 6))); - bytes[j++] = (byte) (0x80 | (0x3F & codePoint)); - } else { - // If we are surrogates and we're not a surrogate pair, always throw an - // IllegalArgumentException instead of an ArrayOutOfBoundsException. - if ((Character.MIN_SURROGATE <= c && c <= Character.MAX_SURROGATE) - && (i + 1 == sequence.length() - || !Character.isSurrogatePair(c, sequence.charAt(i + 1)))) { - throw new UnpairedSurrogateException(i, utf16Length); - } - throw new ArrayIndexOutOfBoundsException("Failed writing " + c + " at index " + j); - } - } - return j; - } - // End Guava UTF-8 methods. -} diff --git a/java/src/test/java/com/google/protobuf/CodedOutputStreamTest.java b/java/src/test/java/com/google/protobuf/CodedOutputStreamTest.java deleted file mode 100644 index 360e759e..00000000 --- a/java/src/test/java/com/google/protobuf/CodedOutputStreamTest.java +++ /dev/null @@ -1,509 +0,0 @@ -// Protocol Buffers - Google's data interchange format -// Copyright 2008 Google Inc. All rights reserved. -// https://developers.google.com/protocol-buffers/ -// -// 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.SparseEnumMessage; -import protobuf_unittest.UnittestProto.TestAllTypes; -import protobuf_unittest.UnittestProto.TestPackedTypes; -import protobuf_unittest.UnittestProto.TestSparseEnum; - -import junit.framework.TestCase; - -import java.io.ByteArrayOutputStream; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * Unit test for {@link CodedOutputStream}. - * - * @author kenton@google.com Kenton Varda - */ -public class CodedOutputStreamTest extends TestCase { - /** - * Helper to construct a byte array from a bunch of bytes. The inputs are - * actually ints so that I can use hex notation and not get stupid errors - * about precision. - */ - private byte[] bytes(int... bytesAsInts) { - byte[] bytes = new byte[bytesAsInts.length]; - for (int i = 0; i < bytesAsInts.length; i++) { - bytes[i] = (byte) bytesAsInts[i]; - } - return bytes; - } - - /** Arrays.asList() does not work with arrays of primitives. :( */ - private List<Byte> toList(byte[] bytes) { - List<Byte> result = new ArrayList<Byte>(); - for (byte b : bytes) { - result.add(b); - } - return result; - } - - private void assertEqualBytes(byte[] a, byte[] b) { - assertEquals(toList(a), toList(b)); - } - - /** - * Writes the given value using writeRawVarint32() and writeRawVarint64() and - * checks that the result matches the given bytes. - */ - private void assertWriteVarint(byte[] data, long value) throws Exception { - // Only do 32-bit write if the value fits in 32 bits. - if ((value >>> 32) == 0) { - ByteArrayOutputStream rawOutput = new ByteArrayOutputStream(); - CodedOutputStream output = CodedOutputStream.newInstance(rawOutput); - output.writeRawVarint32((int) value); - output.flush(); - assertEqualBytes(data, rawOutput.toByteArray()); - - // Also try computing size. - assertEquals(data.length, - CodedOutputStream.computeRawVarint32Size((int) value)); - } - - { - ByteArrayOutputStream rawOutput = new ByteArrayOutputStream(); - CodedOutputStream output = CodedOutputStream.newInstance(rawOutput); - output.writeRawVarint64(value); - output.flush(); - assertEqualBytes(data, rawOutput.toByteArray()); - - // Also try computing size. - assertEquals(data.length, - CodedOutputStream.computeRawVarint64Size(value)); - } - - // Try different block sizes. - for (int blockSize = 1; blockSize <= 16; blockSize *= 2) { - // Only do 32-bit write if the value fits in 32 bits. - if ((value >>> 32) == 0) { - ByteArrayOutputStream rawOutput = new ByteArrayOutputStream(); - CodedOutputStream output = - CodedOutputStream.newInstance(rawOutput, blockSize); - output.writeRawVarint32((int) value); - output.flush(); - assertEqualBytes(data, rawOutput.toByteArray()); - } - - { - ByteArrayOutputStream rawOutput = new ByteArrayOutputStream(); - CodedOutputStream output = - CodedOutputStream.newInstance(rawOutput, blockSize); - output.writeRawVarint64(value); - output.flush(); - assertEqualBytes(data, rawOutput.toByteArray()); - } - } - } - - /** Tests writeRawVarint32() and writeRawVarint64(). */ - public void testWriteVarint() throws Exception { - assertWriteVarint(bytes(0x00), 0); - assertWriteVarint(bytes(0x01), 1); - assertWriteVarint(bytes(0x7f), 127); - // 14882 - assertWriteVarint(bytes(0xa2, 0x74), (0x22 << 0) | (0x74 << 7)); - // 2961488830 - assertWriteVarint(bytes(0xbe, 0xf7, 0x92, 0x84, 0x0b), - (0x3e << 0) | (0x77 << 7) | (0x12 << 14) | (0x04 << 21) | - (0x0bL << 28)); - - // 64-bit - // 7256456126 - assertWriteVarint(bytes(0xbe, 0xf7, 0x92, 0x84, 0x1b), - (0x3e << 0) | (0x77 << 7) | (0x12 << 14) | (0x04 << 21) | - (0x1bL << 28)); - // 41256202580718336 - assertWriteVarint( - bytes(0x80, 0xe6, 0xeb, 0x9c, 0xc3, 0xc9, 0xa4, 0x49), - (0x00 << 0) | (0x66 << 7) | (0x6b << 14) | (0x1c << 21) | - (0x43L << 28) | (0x49L << 35) | (0x24L << 42) | (0x49L << 49)); - // 11964378330978735131 - assertWriteVarint( - bytes(0x9b, 0xa8, 0xf9, 0xc2, 0xbb, 0xd6, 0x80, 0x85, 0xa6, 0x01), - (0x1b << 0) | (0x28 << 7) | (0x79 << 14) | (0x42 << 21) | - (0x3bL << 28) | (0x56L << 35) | (0x00L << 42) | - (0x05L << 49) | (0x26L << 56) | (0x01L << 63)); - } - - /** - * Parses the given bytes using writeRawLittleEndian32() and checks - * that the result matches the given value. - */ - private void assertWriteLittleEndian32(byte[] data, int value) - throws Exception { - ByteArrayOutputStream rawOutput = new ByteArrayOutputStream(); - CodedOutputStream output = CodedOutputStream.newInstance(rawOutput); - output.writeRawLittleEndian32(value); - output.flush(); - assertEqualBytes(data, rawOutput.toByteArray()); - - // Try different block sizes. - for (int blockSize = 1; blockSize <= 16; blockSize *= 2) { - rawOutput = new ByteArrayOutputStream(); - output = CodedOutputStream.newInstance(rawOutput, blockSize); - output.writeRawLittleEndian32(value); - output.flush(); - assertEqualBytes(data, rawOutput.toByteArray()); - } - } - - /** - * Parses the given bytes using writeRawLittleEndian64() and checks - * that the result matches the given value. - */ - private void assertWriteLittleEndian64(byte[] data, long value) - throws Exception { - ByteArrayOutputStream rawOutput = new ByteArrayOutputStream(); - CodedOutputStream output = CodedOutputStream.newInstance(rawOutput); - output.writeRawLittleEndian64(value); - output.flush(); - assertEqualBytes(data, rawOutput.toByteArray()); - - // Try different block sizes. - for (int blockSize = 1; blockSize <= 16; blockSize *= 2) { - rawOutput = new ByteArrayOutputStream(); - output = CodedOutputStream.newInstance(rawOutput, blockSize); - output.writeRawLittleEndian64(value); - output.flush(); - assertEqualBytes(data, rawOutput.toByteArray()); - } - } - - /** Tests writeRawLittleEndian32() and writeRawLittleEndian64(). */ - public void testWriteLittleEndian() throws Exception { - assertWriteLittleEndian32(bytes(0x78, 0x56, 0x34, 0x12), 0x12345678); - assertWriteLittleEndian32(bytes(0xf0, 0xde, 0xbc, 0x9a), 0x9abcdef0); - - assertWriteLittleEndian64( - bytes(0xf0, 0xde, 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12), - 0x123456789abcdef0L); - assertWriteLittleEndian64( - bytes(0x78, 0x56, 0x34, 0x12, 0xf0, 0xde, 0xbc, 0x9a), - 0x9abcdef012345678L); - } - - /** Test encodeZigZag32() and encodeZigZag64(). */ - public void testEncodeZigZag() throws Exception { - assertEquals(0, CodedOutputStream.encodeZigZag32( 0)); - assertEquals(1, CodedOutputStream.encodeZigZag32(-1)); - assertEquals(2, CodedOutputStream.encodeZigZag32( 1)); - assertEquals(3, CodedOutputStream.encodeZigZag32(-2)); - assertEquals(0x7FFFFFFE, CodedOutputStream.encodeZigZag32(0x3FFFFFFF)); - assertEquals(0x7FFFFFFF, CodedOutputStream.encodeZigZag32(0xC0000000)); - assertEquals(0xFFFFFFFE, CodedOutputStream.encodeZigZag32(0x7FFFFFFF)); - assertEquals(0xFFFFFFFF, CodedOutputStream.encodeZigZag32(0x80000000)); - - assertEquals(0, CodedOutputStream.encodeZigZag64( 0)); - assertEquals(1, CodedOutputStream.encodeZigZag64(-1)); - assertEquals(2, CodedOutputStream.encodeZigZag64( 1)); - assertEquals(3, CodedOutputStream.encodeZigZag64(-2)); - assertEquals(0x000000007FFFFFFEL, - CodedOutputStream.encodeZigZag64(0x000000003FFFFFFFL)); - assertEquals(0x000000007FFFFFFFL, - CodedOutputStream.encodeZigZag64(0xFFFFFFFFC0000000L)); - assertEquals(0x00000000FFFFFFFEL, - CodedOutputStream.encodeZigZag64(0x000000007FFFFFFFL)); - assertEquals(0x00000000FFFFFFFFL, - CodedOutputStream.encodeZigZag64(0xFFFFFFFF80000000L)); - assertEquals(0xFFFFFFFFFFFFFFFEL, - CodedOutputStream.encodeZigZag64(0x7FFFFFFFFFFFFFFFL)); - assertEquals(0xFFFFFFFFFFFFFFFFL, - CodedOutputStream.encodeZigZag64(0x8000000000000000L)); - - // Some easier-to-verify round-trip tests. The inputs (other than 0, 1, -1) - // were chosen semi-randomly via keyboard bashing. - assertEquals(0, - CodedOutputStream.encodeZigZag32(CodedInputStream.decodeZigZag32(0))); - assertEquals(1, - CodedOutputStream.encodeZigZag32(CodedInputStream.decodeZigZag32(1))); - assertEquals(-1, - CodedOutputStream.encodeZigZag32(CodedInputStream.decodeZigZag32(-1))); - assertEquals(14927, - CodedOutputStream.encodeZigZag32(CodedInputStream.decodeZigZag32(14927))); - assertEquals(-3612, - CodedOutputStream.encodeZigZag32(CodedInputStream.decodeZigZag32(-3612))); - - assertEquals(0, - CodedOutputStream.encodeZigZag64(CodedInputStream.decodeZigZag64(0))); - assertEquals(1, - CodedOutputStream.encodeZigZag64(CodedInputStream.decodeZigZag64(1))); - assertEquals(-1, - CodedOutputStream.encodeZigZag64(CodedInputStream.decodeZigZag64(-1))); - assertEquals(14927, - CodedOutputStream.encodeZigZag64(CodedInputStream.decodeZigZag64(14927))); - assertEquals(-3612, - CodedOutputStream.encodeZigZag64(CodedInputStream.decodeZigZag64(-3612))); - - assertEquals(856912304801416L, - CodedOutputStream.encodeZigZag64( - CodedInputStream.decodeZigZag64( - 856912304801416L))); - assertEquals(-75123905439571256L, - CodedOutputStream.encodeZigZag64( - CodedInputStream.decodeZigZag64( - -75123905439571256L))); - } - - /** Tests writing a whole message with every field type. */ - public void testWriteWholeMessage() throws Exception { - TestAllTypes message = TestUtil.getAllSet(); - - byte[] rawBytes = message.toByteArray(); - assertEqualBytes(TestUtil.getGoldenMessage().toByteArray(), rawBytes); - - // Try different block sizes. - for (int blockSize = 1; blockSize < 256; blockSize *= 2) { - ByteArrayOutputStream rawOutput = new ByteArrayOutputStream(); - CodedOutputStream output = - CodedOutputStream.newInstance(rawOutput, blockSize); - message.writeTo(output); - output.flush(); - assertEqualBytes(rawBytes, rawOutput.toByteArray()); - } - } - - /** Tests writing a whole message with every packed field type. Ensures the - * wire format of packed fields is compatible with C++. */ - public void testWriteWholePackedFieldsMessage() throws Exception { - TestPackedTypes message = TestUtil.getPackedSet(); - - byte[] rawBytes = message.toByteArray(); - assertEqualBytes(TestUtil.getGoldenPackedFieldsMessage().toByteArray(), - rawBytes); - } - - /** Test writing a message containing a negative enum value. This used to - * fail because the size was not properly computed as a sign-extended varint. - */ - public void testWriteMessageWithNegativeEnumValue() throws Exception { - SparseEnumMessage message = SparseEnumMessage.newBuilder() - .setSparseEnum(TestSparseEnum.SPARSE_E) .build(); - assertTrue(message.getSparseEnum().getNumber() < 0); - byte[] rawBytes = message.toByteArray(); - 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(Internal.UTF_8); - for (int i = 0; i < 1024; ++i) { - codedStream.writeRawBytes(value, 0, value.length); - } - String string = - "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"; - // Ensure we take the slower fast path. - assertTrue(CodedOutputStream.computeRawVarint32Size(string.length()) - != CodedOutputStream.computeRawVarint32Size(string.length() * Utf8.MAX_BYTES_PER_CHAR)); - - codedStream.writeStringNoTag(string); - int stringSize = CodedOutputStream.computeStringSizeNoTag(string); - - // Make sure we have written more bytes than the buffer could hold. This is - // to make the test complete. - assertTrue(codedStream.getTotalBytesWritten() > BUFFER_SIZE); - - // Verify that the total bytes written is correct - assertEquals((value.length * 1024) + stringSize, codedStream.getTotalBytesWritten()); - } - - // TODO(dweis): Write a comprehensive test suite for CodedOutputStream that covers more than just - // this case. - public void testWriteStringNoTag_fastpath() throws Exception { - int bufferSize = 153; - String threeBytesPer = "\u0981"; - String string = threeBytesPer; - for (int i = 0; i < 50; i++) { - string += threeBytesPer; - } - // These checks ensure we will tickle the slower fast path. - assertEquals(1, CodedOutputStream.computeRawVarint32Size(string.length())); - assertEquals( - 2, CodedOutputStream.computeRawVarint32Size(string.length() * Utf8.MAX_BYTES_PER_CHAR)); - assertEquals(bufferSize, string.length() * Utf8.MAX_BYTES_PER_CHAR); - - CodedOutputStream output = - CodedOutputStream.newInstance(ByteBuffer.allocate(bufferSize), bufferSize); - output.writeStringNoTag(string); - } - - 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(Internal.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()); - } - - public void testSerializeInvalidUtf8() throws Exception { - String[] invalidStrings = new String[] { - newString(Character.MIN_HIGH_SURROGATE), - "foobar" + newString(Character.MIN_HIGH_SURROGATE), - newString(Character.MIN_LOW_SURROGATE), - "foobar" + newString(Character.MIN_LOW_SURROGATE), - newString(Character.MIN_HIGH_SURROGATE, Character.MIN_HIGH_SURROGATE) - }; - - CodedOutputStream outputWithStream = CodedOutputStream.newInstance(new ByteArrayOutputStream()); - CodedOutputStream outputWithArray = CodedOutputStream.newInstance(new byte[10000]); - for (String s : invalidStrings) { - // TODO(dweis): These should all fail; instead they are corrupting data. - CodedOutputStream.computeStringSizeNoTag(s); - outputWithStream.writeStringNoTag(s); - outputWithArray.writeStringNoTag(s); - } - } - - private static String newString(char... chars) { - return new String(chars); - } - - /** Regression test for https://github.com/google/protobuf/issues/292 */ - public void testCorrectExceptionThrowWhenEncodingStringsWithoutEnoughSpace() throws Exception { - String testCase = "Foooooooo"; - assertEquals(CodedOutputStream.computeRawVarint32Size(testCase.length()), - CodedOutputStream.computeRawVarint32Size(testCase.length() * 3)); - assertEquals(11, CodedOutputStream.computeStringSize(1, testCase)); - // Tag is one byte, varint describing string length is 1 byte, string length is 9 bytes. - // An array of size 1 will cause a failure when trying to write the varint. - for (int i = 0; i < 11; i++) { - CodedOutputStream output = CodedOutputStream.newInstance(new byte[i]); - try { - output.writeString(1, testCase); - fail("Should have thrown an out of space exception"); - } catch (CodedOutputStream.OutOfSpaceException expected) {} - } - } - - public void testDifferentStringLengths() throws Exception { - // Test string serialization roundtrip using strings of the following lengths, - // with ASCII and Unicode characters requiring different UTF-8 byte counts per - // char, hence causing the length delimiter varint to sometimes require more - // bytes for the Unicode strings than the ASCII string of the same length. - int[] lengths = new int[] { - 0, - 1, - (1 << 4) - 1, // 1 byte for ASCII and Unicode - (1 << 7) - 1, // 1 byte for ASCII, 2 bytes for Unicode - (1 << 11) - 1, // 2 bytes for ASCII and Unicode - (1 << 14) - 1, // 2 bytes for ASCII, 3 bytes for Unicode - (1 << 17) - 1, // 3 bytes for ASCII and Unicode - }; - for (int i : lengths) { - testEncodingOfString('q', i); // 1 byte per char - testEncodingOfString('\u07FF', i); // 2 bytes per char - testEncodingOfString('\u0981', i); // 3 bytes per char - } - } - - private void testEncodingOfString(char c, int length) throws Exception { - String fullString = fullString(c, length); - TestAllTypes testAllTypes = TestAllTypes.newBuilder() - .setOptionalString(fullString) - .build(); - assertEquals( - fullString, TestAllTypes.parseFrom(testAllTypes.toByteArray()).getOptionalString()); - } - - private String fullString(char c, int length) { - char[] result = new char[length]; - Arrays.fill(result, c); - return new String(result); - } -} diff --git a/java/util/pom.xml b/java/util/pom.xml index 44a5662d..9236f907 100644 --- a/java/util/pom.xml +++ b/java/util/pom.xml @@ -4,110 +4,73 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> - <groupId>com.google</groupId> - <artifactId>google</artifactId> - <version>1</version> + <groupId>com.google.protobuf</groupId> + <artifactId>protobuf-parent</artifactId> + <version>3.0.0-beta-3</version> </parent> - <groupId>com.google.protobuf</groupId> + <artifactId>protobuf-java-util</artifactId> - <version>3.0.0-beta-1</version> <packaging>bundle</packaging> - <name>Protocol Buffer Java API</name> - <description> - Protocol Buffers are a way of encoding structured data in an efficient yet - extensible format. - </description> - <inceptionYear>2008</inceptionYear> - <url>https://developers.google.com/protocol-buffers/</url> - <licenses> - <license> - <name>New BSD license</name> - <url>http://www.opensource.org/licenses/bsd-license.php</url> - <distribution>repo</distribution> - </license> - </licenses> - <scm> - <url>https://github.com/google/protobuf</url> - <connection> - scm:git:https://github.com/google/protobuf.git - </connection> - </scm> + + <name>Protocol Buffers [Util]</name> + <description>Utilities for Protocol Buffers</description> + <dependencies> <dependency> - <groupId>com.google.protobuf</groupId> + <groupId>${project.groupId}</groupId> <artifactId>protobuf-java</artifactId> - <version>3.0.0-beta-1</version> - <scope>compile</scope> + <version>${project.version}</version> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> - <version>18.0</version> - <scope>compile</scope> </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.3</version> - <scope>compile</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> - <version>4.4</version> - <scope>test</scope> </dependency> <dependency> <groupId>org.easymock</groupId> <artifactId>easymock</artifactId> - <version>2.2</version> - <scope>test</scope> </dependency> <dependency> <groupId>org.easymock</groupId> <artifactId>easymockclassextension</artifactId> - <version>2.2.1</version> - <scope>test</scope> </dependency> </dependencies> + + <properties> + <!-- Use the core proto dir so that we can call the core generation script --> + <test.proto.dir>../core/src/test/proto</test.proto.dir> + </properties> + <build> <plugins> <plugin> - <artifactId>maven-compiler-plugin</artifactId> - <configuration> - <source>1.5</source> - <target>1.5</target> - </configuration> - </plugin> - <plugin> - <artifactId>maven-surefire-plugin</artifactId> - <configuration> - <includes> - <include>**/*Test.java</include> - <include>../src/main/java/com/google/protobuf/TestUtil.java</include> - </includes> - </configuration> - </plugin> - <plugin> <artifactId>maven-antrun-plugin</artifactId> <executions> + <!-- Generate the test protos --> <execution> <id>generate-test-sources</id> <phase>generate-test-sources</phase> <configuration> - <tasks> - <mkdir dir="target/generated-test-sources" /> - <exec executable="../../src/protoc"> - <arg value="--java_out=target/generated-test-sources" /> - <arg value="--proto_path=../../src" /> - <arg value="--proto_path=src/test/java" /> - <arg value="../../src/google/protobuf/unittest.proto" /> - <arg value="../../src/google/protobuf/unittest_import.proto" /> - <arg value="../../src/google/protobuf/unittest_import_public.proto" /> - <arg value="src/test/java/com/google/protobuf/util/json_test.proto" /> + <target> + <!-- Generate all of the test protos from the core module --> + <ant antfile="../core/generate-test-sources-build.xml"/> + + <!-- Generate additional test protos for this module --> + <exec executable="${protoc}"> + <arg value="--java_out=${generated.testsources.dir}" /> + <arg value="--proto_path=${protobuf.source.dir}" /> + <arg value="--proto_path=src/test/proto" /> + <arg value="src/test/proto/com/google/protobuf/util/json_test.proto" /> </exec> - </tasks> - <testSourceRoot>target/generated-test-sources</testSourceRoot> + </target> </configuration> <goals> <goal>run</goal> @@ -115,6 +78,16 @@ </execution> </executions> </plugin> + + <plugin> + <artifactId>maven-compiler-plugin</artifactId> + <configuration> + <!-- Add the generated test sources to the build --> + <generatedTestSourcesDirectory>${generated.testsources.dir}</generatedTestSourcesDirectory> + </configuration> + </plugin> + + <!-- Configure the OSGI bundle --> <plugin> <groupId>org.apache.felix</groupId> <artifactId>maven-bundle-plugin</artifactId> @@ -123,80 +96,20 @@ <instructions> <Bundle-DocURL>https://developers.google.com/protocol-buffers/</Bundle-DocURL> <Bundle-SymbolicName>com.google.protobuf.util</Bundle-SymbolicName> - <Export-Package>com.google.protobuf.util;version=3.0.0-beta-1</Export-Package> + <Export-Package>com.google.protobuf.util;version=${project.version}</Export-Package> </instructions> </configuration> </plugin> + + <!-- Configure the fat jar to include all dependencies --> + <plugin> + <artifactId>maven-assembly-plugin</artifactId> + <configuration> + <descriptorRefs> + <descriptorRef>jar-with-dependencies</descriptorRef> + </descriptorRefs> + </configuration> + </plugin> </plugins> </build> - <profiles> - <profile> - <id>release</id> - <distributionManagement> - <snapshotRepository> - <id>sonatype-nexus-staging</id> - <url>https://oss.sonatype.org/content/repositories/snapshots</url> - </snapshotRepository> - <repository> - <id>sonatype-nexus-staging</id> - <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url> - </repository> - </distributionManagement> - <build> - <plugins> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-source-plugin</artifactId> - <version>2.2.1</version> - <executions> - <execution> - <id>attach-sources</id> - <goals> - <goal>jar-no-fork</goal> - </goals> - </execution> - </executions> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-javadoc-plugin</artifactId> - <version>2.9.1</version> - <executions> - <execution> - <id>attach-javadocs</id> - <goals> - <goal>jar</goal> - </goals> - </execution> - </executions> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-gpg-plugin</artifactId> - <version>1.5</version> - <executions> - <execution> - <id>sign-artifacts</id> - <phase>verify</phase> - <goals> - <goal>sign</goal> - </goals> - </execution> - </executions> - </plugin> - <plugin> - <groupId>org.sonatype.plugins</groupId> - <artifactId>nexus-staging-maven-plugin</artifactId> - <version>1.6.3</version> - <extensions>true</extensions> - <configuration> - <serverId>sonatype-nexus-staging</serverId> - <nexusUrl>https://oss.sonatype.org/</nexusUrl> - <autoReleaseAfterClose>false</autoReleaseAfterClose> - </configuration> - </plugin> - </plugins> - </build> - </profile> - </profiles> </project> diff --git a/java/util/src/main/java/com/google/protobuf/util/FieldMaskTree.java b/java/util/src/main/java/com/google/protobuf/util/FieldMaskTree.java index dc2f4b84..668d65ab 100644 --- a/java/util/src/main/java/com/google/protobuf/util/FieldMaskTree.java +++ b/java/util/src/main/java/com/google/protobuf/util/FieldMaskTree.java @@ -60,10 +60,9 @@ import java.util.logging.Logger; * FieldMask in a message tree. */ class FieldMaskTree { - private static final Logger logger = - Logger.getLogger(FieldMaskTree.class.getName()); - - private static final String FIELD_PATH_SEPARATOR_REGEX = "\\."; + private static final Logger logger = Logger.getLogger(FieldMaskTree.class.getName()); + + private static final String FIELD_PATH_SEPARATOR_REGEX = "\\."; private static class Node { public TreeMap<String, Node> children = new TreeMap<String, Node>(); @@ -73,12 +72,12 @@ class FieldMaskTree { /** Creates an empty FieldMaskTree. */ public FieldMaskTree() {} - + /** Creates a FieldMaskTree for a given FieldMask. */ public FieldMaskTree(FieldMask mask) { mergeFromFieldMask(mask); } - + @Override public String toString() { return FieldMaskUtil.toString(toFieldMask()); @@ -121,7 +120,7 @@ class FieldMaskTree { node.children.clear(); return this; } - + /** * Merges all field paths in a FieldMask into this tree. */ @@ -149,8 +148,7 @@ class FieldMaskTree { return; } for (Entry<String, Node> entry : node.children.entrySet()) { - String childPath = path.isEmpty() - ? entry.getKey() : path + "." + entry.getKey(); + String childPath = path.isEmpty() ? entry.getKey() : path + "." + entry.getKey(); getFieldPaths(entry.getValue(), childPath, paths); } } @@ -193,11 +191,10 @@ class FieldMaskTree { * Merges all fields specified by this FieldMaskTree from {@code source} to * {@code destination}. */ - public void merge(Message source, Message.Builder destination, - FieldMaskUtil.MergeOptions options) { + public void merge( + Message source, Message.Builder destination, FieldMaskUtil.MergeOptions options) { if (source.getDescriptorForType() != destination.getDescriptorForType()) { - throw new IllegalArgumentException( - "Cannot merge messages of different types."); + throw new IllegalArgumentException("Cannot merge messages of different types."); } if (root.children.isEmpty()) { return; @@ -208,30 +205,41 @@ class FieldMaskTree { /** Merges all fields specified by a sub-tree from {@code source} to * {@code destination}. */ - private void merge(Node node, String path, Message source, - Message.Builder destination, FieldMaskUtil.MergeOptions options) { + private void merge( + Node node, + String path, + Message source, + Message.Builder destination, + FieldMaskUtil.MergeOptions options) { assert source.getDescriptorForType() == destination.getDescriptorForType(); - + Descriptor descriptor = source.getDescriptorForType(); for (Entry<String, Node> entry : node.children.entrySet()) { - FieldDescriptor field = - descriptor.findFieldByName(entry.getKey()); + FieldDescriptor field = descriptor.findFieldByName(entry.getKey()); if (field == null) { - logger.warning("Cannot find field \"" + entry.getKey() - + "\" in message type " + descriptor.getFullName()); + logger.warning( + "Cannot find field \"" + + entry.getKey() + + "\" in message type " + + descriptor.getFullName()); continue; } if (!entry.getValue().children.isEmpty()) { - if (field.isRepeated() - || field.getJavaType() != FieldDescriptor.JavaType.MESSAGE) { - logger.warning("Field \"" + field.getFullName() + "\" is not a " - + "singluar message field and cannot have sub-fields."); + if (field.isRepeated() || field.getJavaType() != FieldDescriptor.JavaType.MESSAGE) { + logger.warning( + "Field \"" + + field.getFullName() + + "\" is not a " + + "singluar message field and cannot have sub-fields."); continue; } - String childPath = path.isEmpty() - ? entry.getKey() : path + "." + entry.getKey(); - merge(entry.getValue(), childPath, (Message) source.getField(field), - destination.getFieldBuilder(field), options); + String childPath = path.isEmpty() ? entry.getKey() : path + "." + entry.getKey(); + merge( + entry.getValue(), + childPath, + (Message) source.getField(field), + destination.getFieldBuilder(field), + options); continue; } if (field.isRepeated()) { @@ -245,13 +253,22 @@ class FieldMaskTree { } else { if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { if (options.replaceMessageFields()) { - destination.setField(field, source.getField(field)); + if (!source.hasField(field)) { + destination.clearField(field); + } else { + destination.setField(field, source.getField(field)); + } } else { - destination.getFieldBuilder(field).mergeFrom( - (Message) source.getField(field)); + if (source.hasField(field)) { + destination.getFieldBuilder(field).mergeFrom((Message) source.getField(field)); + } } } else { - destination.setField(field, source.getField(field)); + if (source.hasField(field) || !options.replacePrimitiveFields()) { + destination.setField(field, source.getField(field)); + } else { + destination.clearField(field); + } } } } diff --git a/java/util/src/main/java/com/google/protobuf/util/FieldMaskUtil.java b/java/util/src/main/java/com/google/protobuf/util/FieldMaskUtil.java index 7bf87858..96961521 100644 --- a/java/util/src/main/java/com/google/protobuf/util/FieldMaskUtil.java +++ b/java/util/src/main/java/com/google/protobuf/util/FieldMaskUtil.java @@ -30,6 +30,9 @@ package com.google.protobuf.util; +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.primitives.Ints; import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.FieldDescriptor; import com.google.protobuf.FieldMask; @@ -37,7 +40,6 @@ import com.google.protobuf.Internal; import com.google.protobuf.Message; import java.util.Arrays; -import java.util.List; /** * Utility helper functions to work with {@link com.google.protobuf.FieldMask}. @@ -53,6 +55,7 @@ public class FieldMaskUtil { * Converts a FieldMask to a string. */ public static String toString(FieldMask fieldMask) { + // TODO(xiaofeng): Consider using com.google.common.base.Joiner here instead. StringBuilder result = new StringBuilder(); boolean first = true; for (String value : fieldMask.getPathsList()) { @@ -74,6 +77,7 @@ public class FieldMaskUtil { * Parses from a string to a FieldMask. */ public static FieldMask fromString(String value) { + // TODO(xiaofeng): Consider using com.google.common.base.Splitter here instead. return fromStringList( null, Arrays.asList(value.split(FIELD_PATH_SEPARATOR_REGEX))); } @@ -83,8 +87,8 @@ public class FieldMaskUtil { * * @throws IllegalArgumentException if any of the field path is invalid. */ - public static FieldMask fromString(Class<? extends Message> type, String value) - throws IllegalArgumentException { + public static FieldMask fromString(Class<? extends Message> type, String value) { + // TODO(xiaofeng): Consider using com.google.common.base.Splitter here instead. return fromStringList( type, Arrays.asList(value.split(FIELD_PATH_SEPARATOR_REGEX))); } @@ -94,9 +98,9 @@ public class FieldMaskUtil { * * @throws IllegalArgumentException if any of the field path is not valid. */ + // TODO(xiaofeng): Consider renaming fromStrings() public static FieldMask fromStringList( - Class<? extends Message> type, List<String> paths) - throws IllegalArgumentException { + Class<? extends Message> type, Iterable<String> paths) { FieldMask.Builder builder = FieldMask.newBuilder(); for (String path : paths) { if (path.isEmpty()) { @@ -113,15 +117,74 @@ public class FieldMaskUtil { } /** + * Constructs a FieldMask from the passed field numbers. + * + * @throws IllegalArgumentException if any of the fields are invalid for the message. + */ + public static FieldMask fromFieldNumbers(Class<? extends Message> type, int... fieldNumbers) { + return fromFieldNumbers(type, Ints.asList(fieldNumbers)); + } + + /** + * Constructs a FieldMask from the passed field numbers. + * + * @throws IllegalArgumentException if any of the fields are invalid for the message. + */ + public static FieldMask fromFieldNumbers( + Class<? extends Message> type, Iterable<Integer> fieldNumbers) { + Descriptor descriptor = Internal.getDefaultInstance(type).getDescriptorForType(); + + FieldMask.Builder builder = FieldMask.newBuilder(); + for (Integer fieldNumber : fieldNumbers) { + FieldDescriptor field = descriptor.findFieldByNumber(fieldNumber); + checkArgument( + field != null, + String.format("%s is not a valid field number for %s.", fieldNumber, type)); + builder.addPaths(field.getName()); + } + return builder.build(); + } + + /** + * Checks whether paths in a given fields mask are valid. + */ + public static boolean isValid(Class<? extends Message> type, FieldMask fieldMask) { + Descriptor descriptor = + Internal.getDefaultInstance(type).getDescriptorForType(); + + return isValid(descriptor, fieldMask); + } + + /** + * Checks whether paths in a given fields mask are valid. + */ + public static boolean isValid(Descriptor descriptor, FieldMask fieldMask) { + for (String path : fieldMask.getPathsList()) { + if (!isValid(descriptor, path)) { + return false; + } + } + return true; + } + + /** * Checks whether a given field path is valid. */ public static boolean isValid(Class<? extends Message> type, String path) { + Descriptor descriptor = + Internal.getDefaultInstance(type).getDescriptorForType(); + + return isValid(descriptor, path); + } + + /** + * Checks whether paths in a given fields mask are valid. + */ + public static boolean isValid(Descriptor descriptor, String path) { String[] parts = path.split(FIELD_SEPARATOR_REGEX); if (parts.length == 0) { return false; } - Descriptor descriptor = - Internal.getDefaultInstance(type).getDescriptorForType(); for (String name : parts) { if (descriptor == null) { return false; @@ -148,14 +211,19 @@ public class FieldMaskUtil { public static FieldMask normalize(FieldMask mask) { return new FieldMaskTree(mask).toFieldMask(); } - + /** - * Creates an union of two FieldMasks. + * Creates a union of two or more FieldMasks. */ - public static FieldMask union(FieldMask mask1, FieldMask mask2) { - return new FieldMaskTree(mask1).mergeFromFieldMask(mask2).toFieldMask(); + public static FieldMask union( + FieldMask firstMask, FieldMask secondMask, FieldMask... otherMasks) { + FieldMaskTree maskTree = new FieldMaskTree(firstMask).mergeFromFieldMask(secondMask); + for (FieldMask mask : otherMasks) { + maskTree.mergeFromFieldMask(mask); + } + return maskTree.toFieldMask(); } - + /** * Calculates the intersection of two FieldMasks. */ @@ -171,9 +239,12 @@ public class FieldMaskUtil { /** * Options to customize merging behavior. */ - public static class MergeOptions { + public static final class MergeOptions { private boolean replaceMessageFields = false; private boolean replaceRepeatedFields = false; + // TODO(b/28277137): change the default behavior to always replace primitive fields after + // fixing all failing TAP tests. + private boolean replacePrimitiveFields = false; /** * Whether to replace message fields (i.e., discard existing content in @@ -194,7 +265,23 @@ public class FieldMaskUtil { public boolean replaceRepeatedFields() { return replaceRepeatedFields; } - + + /** + * Whether to replace primitive (non-repeated and non-message) fields in + * destination message fields with the source primitive fields (i.e., if the + * field is set in the source, the value is copied to the + * destination; if the field is unset in the source, the field is cleared + * from the destination) when merging. + * + * <p>Default behavior is to always set the value of the source primitive + * field to the destination primitive field, and if the source field is + * unset, the default value of the source field is copied to the + * destination. + */ + public boolean replacePrimitiveFields() { + return replacePrimitiveFields; + } + public void setReplaceMessageFields(boolean value) { replaceMessageFields = value; } @@ -202,10 +289,15 @@ public class FieldMaskUtil { public void setReplaceRepeatedFields(boolean value) { replaceRepeatedFields = value; } + + public void setReplacePrimitiveFields(boolean value) { + replacePrimitiveFields = value; + } } - + /** - * Merges fields specified by a FieldMask from one message to another. + * Merges fields specified by a FieldMask from one message to another with the + * specified merge options. */ public static void merge(FieldMask mask, Message source, Message.Builder destination, MergeOptions options) { diff --git a/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java b/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java index c9a39153..76f3437a 100644 --- a/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java +++ b/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java @@ -32,6 +32,7 @@ package com.google.protobuf.util; import com.google.common.io.BaseEncoding; import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonNull; @@ -78,6 +79,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TreeMap; import java.util.logging.Logger; /** @@ -99,7 +101,7 @@ public class JsonFormat { * Creates a {@link Printer} with default configurations. */ public static Printer printer() { - return new Printer(TypeRegistry.getEmptyTypeRegistry()); + return new Printer(TypeRegistry.getEmptyTypeRegistry(), false, false); } /** @@ -107,9 +109,16 @@ public class JsonFormat { */ public static class Printer { private final TypeRegistry registry; - - private Printer(TypeRegistry registry) { + private final boolean includingDefaultValueFields; + private final boolean preservingProtoFieldNames; + + private Printer( + TypeRegistry registry, + boolean includingDefaultValueFields, + boolean preservingProtoFieldNames) { this.registry = registry; + this.includingDefaultValueFields = includingDefaultValueFields; + this.preservingProtoFieldNames = preservingProtoFieldNames; } /** @@ -122,7 +131,27 @@ public class JsonFormat { if (this.registry != TypeRegistry.getEmptyTypeRegistry()) { throw new IllegalArgumentException("Only one registry is allowed."); } - return new Printer(registry); + return new Printer(registry, includingDefaultValueFields, preservingProtoFieldNames); + } + + /** + * Creates a new {@link Printer} that will also print fields set to their + * defaults. Empty repeated fields and map fields will be printed as well. + * The new Printer clones all other configurations from the current + * {@link Printer}. + */ + public Printer includingDefaultValueFields() { + return new Printer(registry, true, preservingProtoFieldNames); + } + + /** + * Creates a new {@link Printer} that is configured to use the original proto + * field names as defined in the .proto file rather than converting them to + * lowerCamelCase. The new Printer clones all other configurations from the + * current {@link Printer}. + */ + public Printer preservingProtoFieldNames() { + return new Printer(registry, includingDefaultValueFields, true); } /** @@ -136,7 +165,8 @@ public class JsonFormat { throws IOException { // TODO(xiaofeng): Investigate the allocation overhead and optimize for // mobile. - new PrinterImpl(registry, output).print(message); + new PrinterImpl(registry, includingDefaultValueFields, preservingProtoFieldNames, output) + .print(message); } /** @@ -298,7 +328,7 @@ public class JsonFormat { private void addFile(FileDescriptor file) { // Skip the file if it's already added. - if (files.contains(file.getName())) { + if (!files.add(file.getFullName())) { return; } for (FileDescriptor dependency : file.getDependencies()) { @@ -397,16 +427,24 @@ public class JsonFormat { */ private static final class PrinterImpl { private final TypeRegistry registry; + private final boolean includingDefaultValueFields; + private final boolean preservingProtoFieldNames; private final TextGenerator generator; // We use Gson to help handle string escapes. private final Gson gson; private static class GsonHolder { - private static final Gson DEFAULT_GSON = new Gson(); + private static final Gson DEFAULT_GSON = new GsonBuilder().disableHtmlEscaping().create(); } - PrinterImpl(TypeRegistry registry, Appendable jsonOutput) { + PrinterImpl( + TypeRegistry registry, + boolean includingDefaultValueFields, + boolean preservingProtoFieldNames, + Appendable jsonOutput) { this.registry = registry; + this.includingDefaultValueFields = includingDefaultValueFields; + this.preservingProtoFieldNames = preservingProtoFieldNames; this.generator = new TextGenerator(jsonOutput); this.gson = GsonHolder.DEFAULT_GSON; } @@ -647,13 +685,23 @@ public class JsonFormat { generator.print("\"@type\": " + gson.toJson(typeUrl)); printedField = true; } - for (Map.Entry<FieldDescriptor, Object> field - : message.getAllFields().entrySet()) { - // Skip unknown enum fields. - if (field.getValue() instanceof EnumValueDescriptor - && ((EnumValueDescriptor) field.getValue()).getIndex() == -1) { - continue; + Map<FieldDescriptor, Object> fieldsToPrint = null; + if (includingDefaultValueFields) { + fieldsToPrint = new TreeMap<FieldDescriptor, Object>(); + for (FieldDescriptor field : message.getDescriptorForType().getFields()) { + if (field.isOptional() + && field.getJavaType() == FieldDescriptor.JavaType.MESSAGE + && !message.hasField(field)) { + // Always skip empty optional message fields. If not we will recurse indefinitely if + // a message has itself as a sub-field. + continue; + } + fieldsToPrint.put(field, message.getField(field)); } + } else { + fieldsToPrint = message.getAllFields(); + } + for (Map.Entry<FieldDescriptor, Object> field : fieldsToPrint.entrySet()) { if (printedField) { // Add line-endings for the previous field. generator.print(",\n"); @@ -673,7 +721,11 @@ public class JsonFormat { private void printField(FieldDescriptor field, Object value) throws IOException { - generator.print("\"" + fieldNameToCamelName(field.getName()) + "\": "); + if (preservingProtoFieldNames) { + generator.print("\"" + field.getName() + "\": "); + } else { + generator.print("\"" + field.getJsonName() + "\": "); + } if (field.isMapField()) { printMapFieldValue(field, value); } else if (field.isRepeated()) { @@ -689,11 +741,6 @@ public class JsonFormat { generator.print("["); boolean printedElement = false; for (Object element : (List) value) { - // Skip unknown enum entries. - if (element instanceof EnumValueDescriptor - && ((EnumValueDescriptor) element).getIndex() == -1) { - continue; - } if (printedElement) { generator.print(", "); } else { @@ -720,11 +767,6 @@ public class JsonFormat { Message entry = (Message) element; Object entryKey = entry.getField(keyField); Object entryValue = entry.getField(valueField); - // Skip unknown enum entries. - if (entryValue instanceof EnumValueDescriptor - && ((EnumValueDescriptor) entryValue).getIndex() == -1) { - continue; - } if (printedElement) { generator.print(",\n"); } else { @@ -871,8 +913,13 @@ public class JsonFormat { generator.print("\""); } } else { - generator.print( - "\"" + ((EnumValueDescriptor) value).getName() + "\""); + if (((EnumValueDescriptor) value).getIndex() == -1) { + generator.print( + String.valueOf(((EnumValueDescriptor) value).getNumber())); + } else { + generator.print( + "\"" + ((EnumValueDescriptor) value).getName() + "\""); + } } break; @@ -905,48 +952,15 @@ public class JsonFormat { } } - private static final String TYPE_URL_PREFIX = "type.googleapis.com"; - + private static String getTypeName(String typeUrl) throws InvalidProtocolBufferException { String[] parts = typeUrl.split("/"); - if (parts.length != 2 || !parts[0].equals(TYPE_URL_PREFIX)) { + if (parts.length == 1) { throw new InvalidProtocolBufferException( "Invalid type url found: " + typeUrl); } - return parts[1]; - } - - private static String fieldNameToCamelName(String name) { - StringBuilder result = new StringBuilder(name.length()); - boolean isNextUpperCase = false; - for (int i = 0; i < name.length(); i++) { - Character ch = name.charAt(i); - if (Character.isLowerCase(ch)) { - if (isNextUpperCase) { - result.append(Character.toUpperCase(ch)); - } else { - result.append(ch); - } - isNextUpperCase = false; - } else if (Character.isUpperCase(ch)) { - if (i == 0 && !isNextUpperCase) { - // Force first letter to lower-case unless explicitly told to - // capitalize it. - result.append(Character.toLowerCase(ch)); - } else { - // Capital letters after the first are left as-is. - result.append(ch); - } - isNextUpperCase = false; - } else if (Character.isDigit(ch)) { - result.append(ch); - isNextUpperCase = true; - } else { - isNextUpperCase = true; - } - } - return result.toString(); + return parts[parts.length - 1]; } private static class ParserImpl { @@ -1052,6 +1066,15 @@ public class JsonFormat { parser.mergeStruct(json, builder); } }); + // Special-case ListValue. + parsers.put(ListValue.getDescriptor().getFullName(), + new WellKnownTypeParser() { + @Override + public void merge(ParserImpl parser, JsonElement json, + Message.Builder builder) throws InvalidProtocolBufferException { + parser.mergeListValue(json, builder); + } + }); // Special-case Value. parsers.put(Value.getDescriptor().getFullName(), new WellKnownTypeParser() { @@ -1085,7 +1108,8 @@ public class JsonFormat { Map<String, FieldDescriptor> fieldNameMap = new HashMap<String, FieldDescriptor>(); for (FieldDescriptor field : descriptor.getFields()) { - fieldNameMap.put(fieldNameToCamelName(field.getName()), field); + fieldNameMap.put(field.getName(), field); + fieldNameMap.put(field.getJsonName(), field); } fieldNameMaps.put(descriptor, fieldNameMap); return fieldNameMap; @@ -1198,6 +1222,16 @@ public class JsonFormat { } mergeMapField(field, json, builder); } + + private void mergeListValue(JsonElement json, Message.Builder builder) + throws InvalidProtocolBufferException { + Descriptor descriptor = builder.getDescriptorForType(); + FieldDescriptor field = descriptor.findFieldByName("values"); + if (field == null) { + throw new InvalidProtocolBufferException("Invalid ListValue type."); + } + mergeRepeatedField(field, json, builder); + } private void mergeValue(JsonElement json, Message.Builder builder) throws InvalidProtocolBufferException { @@ -1222,9 +1256,7 @@ public class JsonFormat { } else if (json instanceof JsonArray) { FieldDescriptor field = type.findFieldByName("list_value"); Message.Builder listBuilder = builder.newBuilderForField(field); - FieldDescriptor listField = - listBuilder.getDescriptorForType().findFieldByName("values"); - mergeRepeatedField(listField, json, listBuilder); + merge(json, listBuilder); builder.setField(field, listBuilder.build()); } else { throw new IllegalStateException("Unexpected json data: " + json); @@ -1244,7 +1276,25 @@ public class JsonFormat { private void mergeField(FieldDescriptor field, JsonElement json, Message.Builder builder) throws InvalidProtocolBufferException { - if (json instanceof JsonNull) { + if (field.isRepeated()) { + if (builder.getRepeatedFieldCount(field) > 0) { + throw new InvalidProtocolBufferException( + "Field " + field.getFullName() + " has already been set."); + } + } else { + if (builder.hasField(field)) { + throw new InvalidProtocolBufferException( + "Field " + field.getFullName() + " has already been set."); + } + if (field.getContainingOneof() != null + && builder.getOneofFieldDescriptor(field.getContainingOneof()) != null) { + FieldDescriptor other = builder.getOneofFieldDescriptor(field.getContainingOneof()); + throw new InvalidProtocolBufferException( + "Cannot set field " + field.getFullName() + " because another field " + + other.getFullName() + " belonging to the same oneof has already been set "); + } + } + if (field.isRepeated() && json instanceof JsonNull) { // We allow "null" as value for all field types and treat it as if the // field is not present. return; @@ -1282,7 +1332,8 @@ public class JsonFormat { Object value = parseFieldValue( valueField, entry.getValue(), entryBuilder); if (value == null) { - value = getDefaultValue(valueField, entryBuilder); + throw new InvalidProtocolBufferException( + "Map value cannot be null."); } entryBuilder.setField(keyField, key); entryBuilder.setField(valueField, value); @@ -1341,7 +1392,8 @@ public class JsonFormat { for (int i = 0; i < array.size(); ++i) { Object value = parseFieldValue(field, array.get(i), builder); if (value == null) { - value = getDefaultValue(field, builder); + throw new InvalidProtocolBufferException( + "Repeated field elements cannot be null"); } builder.addRepeatedField(field, value); } @@ -1352,6 +1404,15 @@ public class JsonFormat { try { return Integer.parseInt(json.getAsString()); } catch (Exception e) { + // Fall through. + } + // JSON doesn't distinguish between integer values and floating point values so "1" and + // "1.000" are treated as equal in JSON. For this reason we accept floating point values for + // integer fields as well as long as it actually is an integer (i.e., round(value) == value). + try { + BigDecimal value = new BigDecimal(json.getAsString()); + return value.intValueExact(); + } catch (Exception e) { throw new InvalidProtocolBufferException("Not an int32 value: " + json); } } @@ -1361,7 +1422,16 @@ public class JsonFormat { try { return Long.parseLong(json.getAsString()); } catch (Exception e) { - throw new InvalidProtocolBufferException("Not an int64 value: " + json); + // Fall through. + } + // JSON doesn't distinguish between integer values and floating point values so "1" and + // "1.000" are treated as equal in JSON. For this reason we accept floating point values for + // integer fields as well as long as it actually is an integer (i.e., round(value) == value). + try { + BigDecimal value = new BigDecimal(json.getAsString()); + return value.longValueExact(); + } catch (Exception e) { + throw new InvalidProtocolBufferException("Not an int32 value: " + json); } } @@ -1377,6 +1447,21 @@ public class JsonFormat { } catch (InvalidProtocolBufferException e) { throw e; } catch (Exception e) { + // Fall through. + } + // JSON doesn't distinguish between integer values and floating point values so "1" and + // "1.000" are treated as equal in JSON. For this reason we accept floating point values for + // integer fields as well as long as it actually is an integer (i.e., round(value) == value). + try { + BigDecimal decimalValue = new BigDecimal(json.getAsString()); + BigInteger value = decimalValue.toBigIntegerExact(); + if (value.signum() < 0 || value.compareTo(new BigInteger("FFFFFFFF", 16)) > 0) { + throw new InvalidProtocolBufferException("Out of range uint32 value: " + json); + } + return value.intValue(); + } catch (InvalidProtocolBufferException e) { + throw e; + } catch (Exception e) { throw new InvalidProtocolBufferException( "Not an uint32 value: " + json); } @@ -1388,7 +1473,8 @@ public class JsonFormat { private long parseUint64(JsonElement json) throws InvalidProtocolBufferException { try { - BigInteger value = new BigInteger(json.getAsString()); + BigDecimal decimalValue = new BigDecimal(json.getAsString()); + BigInteger value = decimalValue.toBigIntegerExact(); if (value.compareTo(BigInteger.ZERO) < 0 || value.compareTo(MAX_UINT64) > 0) { throw new InvalidProtocolBufferException( @@ -1488,7 +1574,12 @@ public class JsonFormat { return json.getAsString(); } - private ByteString parseBytes(JsonElement json) { + private ByteString parseBytes(JsonElement json) throws InvalidProtocolBufferException { + String encoded = json.getAsString(); + if (encoded.length() % 4 != 0) { + throw new InvalidProtocolBufferException( + "Bytes field is not encoded in standard BASE64 with paddings: " + encoded); + } return ByteString.copyFrom( BaseEncoding.base64().decode(json.getAsString())); } @@ -1498,9 +1589,25 @@ public class JsonFormat { String value = json.getAsString(); EnumValueDescriptor result = enumDescriptor.findValueByName(value); if (result == null) { - throw new InvalidProtocolBufferException( - "Invalid enum value: " + value + " for enum type: " - + enumDescriptor.getFullName()); + // Try to interpret the value as a number. + try { + int numericValue = parseInt32(json); + if (enumDescriptor.getFile().getSyntax() == FileDescriptor.Syntax.PROTO3) { + result = enumDescriptor.findValueByNumberCreatingIfUnknown(numericValue); + } else { + result = enumDescriptor.findValueByNumber(numericValue); + } + } catch (InvalidProtocolBufferException e) { + // Fall through. This exception is about invalid int32 value we get from parseInt32() but + // that's not the exception we want the user to see. Since result == null, we will throw + // an exception later. + } + + if (result == null) { + throw new InvalidProtocolBufferException( + "Invalid enum value: " + value + " for enum type: " + + enumDescriptor.getFullName()); + } } return result; } diff --git a/java/util/src/main/java/com/google/protobuf/util/TimeUtil.java b/java/util/src/main/java/com/google/protobuf/util/TimeUtil.java index 6e4b7c03..3033182a 100644 --- a/java/util/src/main/java/com/google/protobuf/util/TimeUtil.java +++ b/java/util/src/main/java/com/google/protobuf/util/TimeUtil.java @@ -58,8 +58,12 @@ public class TimeUtil { private static final long MILLIS_PER_SECOND = 1000; private static final long MICROS_PER_SECOND = 1000000; - private static final SimpleDateFormat timestampFormat = - createTimestampFormat(); + private static final ThreadLocal<SimpleDateFormat> timestampFormat = + new ThreadLocal<SimpleDateFormat>() { + protected SimpleDateFormat initialValue() { + return createTimestampFormat(); + } + }; private static SimpleDateFormat createTimestampFormat() { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); @@ -96,7 +100,7 @@ public class TimeUtil { throw new IllegalArgumentException("Timestamp is out of range."); } Date date = new Date(timestamp.getSeconds() * MILLIS_PER_SECOND); - result.append(timestampFormat.format(date)); + result.append(timestampFormat.get().format(date)); // Format the nanos part. if (timestamp.getNanos() < 0 || timestamp.getNanos() >= NANOS_PER_SECOND) { throw new IllegalArgumentException("Timestamp has invalid nanos value."); @@ -147,7 +151,7 @@ public class TimeUtil { secondValue = timeValue.substring(0, pointPosition); nanoValue = timeValue.substring(pointPosition + 1); } - Date date = timestampFormat.parse(secondValue); + Date date = timestampFormat.get().parse(secondValue); long seconds = date.getTime() / MILLIS_PER_SECOND; int nanos = nanoValue.isEmpty() ? 0 : parseNanos(nanoValue); // Parse timezone offsets. diff --git a/java/util/src/test/java/com/google/protobuf/util/FieldMaskTreeTest.java b/java/util/src/test/java/com/google/protobuf/util/FieldMaskTreeTest.java index 3391f239..3ee0fc6e 100644 --- a/java/util/src/test/java/com/google/protobuf/util/FieldMaskTreeTest.java +++ b/java/util/src/test/java/com/google/protobuf/util/FieldMaskTreeTest.java @@ -61,19 +61,16 @@ public class FieldMaskTreeTest extends TestCase { tree.addFieldPath("bar"); assertEquals("bar,foo", tree.toString()); } - + public void testMergeFromFieldMask() throws Exception { - FieldMaskTree tree = new FieldMaskTree( - FieldMaskUtil.fromString("foo,bar.baz,bar.quz")); + FieldMaskTree tree = new FieldMaskTree(FieldMaskUtil.fromString("foo,bar.baz,bar.quz")); assertEquals("bar.baz,bar.quz,foo", tree.toString()); - tree.mergeFromFieldMask( - FieldMaskUtil.fromString("foo.bar,bar")); + tree.mergeFromFieldMask(FieldMaskUtil.fromString("foo.bar,bar")); assertEquals("bar,foo", tree.toString()); } - + public void testIntersectFieldPath() throws Exception { - FieldMaskTree tree = new FieldMaskTree( - FieldMaskUtil.fromString("foo,bar.baz,bar.quz")); + FieldMaskTree tree = new FieldMaskTree(FieldMaskUtil.fromString("foo,bar.baz,bar.quz")); FieldMaskTree result = new FieldMaskTree(); // Empty path. tree.intersectFieldPath("", result); @@ -96,16 +93,18 @@ public class FieldMaskTreeTest extends TestCase { } public void testMerge() throws Exception { - TestAllTypes value = TestAllTypes.newBuilder() - .setOptionalInt32(1234) - .setOptionalNestedMessage(NestedMessage.newBuilder().setBb(5678)) - .addRepeatedInt32(4321) - .addRepeatedNestedMessage(NestedMessage.newBuilder().setBb(8765)) - .build(); - NestedTestAllTypes source = NestedTestAllTypes.newBuilder() - .setPayload(value) - .setChild(NestedTestAllTypes.newBuilder().setPayload(value)) - .build(); + TestAllTypes value = + TestAllTypes.newBuilder() + .setOptionalInt32(1234) + .setOptionalNestedMessage(NestedMessage.newBuilder().setBb(5678)) + .addRepeatedInt32(4321) + .addRepeatedNestedMessage(NestedMessage.newBuilder().setBb(8765)) + .build(); + NestedTestAllTypes source = + NestedTestAllTypes.newBuilder() + .setPayload(value) + .setChild(NestedTestAllTypes.newBuilder().setPayload(value)) + .build(); // Now we have a message source with the following structure: // [root] -+- payload -+- optional_int32 // | +- optional_nested_message @@ -116,114 +115,147 @@ public class FieldMaskTreeTest extends TestCase { // +- optional_nested_message // +- repeated_int32 // +- repeated_nested_message - + FieldMaskUtil.MergeOptions options = new FieldMaskUtil.MergeOptions(); - + // Test merging each individual field. NestedTestAllTypes.Builder builder = NestedTestAllTypes.newBuilder(); - new FieldMaskTree().addFieldPath("payload.optional_int32") - .merge(source, builder, options); + new FieldMaskTree().addFieldPath("payload.optional_int32").merge(source, builder, options); NestedTestAllTypes.Builder expected = NestedTestAllTypes.newBuilder(); expected.getPayloadBuilder().setOptionalInt32(1234); assertEquals(expected.build(), builder.build()); builder = NestedTestAllTypes.newBuilder(); - new FieldMaskTree().addFieldPath("payload.optional_nested_message") + new FieldMaskTree() + .addFieldPath("payload.optional_nested_message") .merge(source, builder, options); expected = NestedTestAllTypes.newBuilder(); - expected.getPayloadBuilder().setOptionalNestedMessage( - NestedMessage.newBuilder().setBb(5678)); + expected.getPayloadBuilder().setOptionalNestedMessage(NestedMessage.newBuilder().setBb(5678)); assertEquals(expected.build(), builder.build()); - builder = NestedTestAllTypes.newBuilder(); - new FieldMaskTree().addFieldPath("payload.repeated_int32") - .merge(source, builder, options); + new FieldMaskTree().addFieldPath("payload.repeated_int32").merge(source, builder, options); expected = NestedTestAllTypes.newBuilder(); expected.getPayloadBuilder().addRepeatedInt32(4321); assertEquals(expected.build(), builder.build()); builder = NestedTestAllTypes.newBuilder(); - new FieldMaskTree().addFieldPath("payload.repeated_nested_message") + new FieldMaskTree() + .addFieldPath("payload.repeated_nested_message") .merge(source, builder, options); expected = NestedTestAllTypes.newBuilder(); - expected.getPayloadBuilder().addRepeatedNestedMessage( - NestedMessage.newBuilder().setBb(8765)); + expected.getPayloadBuilder().addRepeatedNestedMessage(NestedMessage.newBuilder().setBb(8765)); assertEquals(expected.build(), builder.build()); builder = NestedTestAllTypes.newBuilder(); - new FieldMaskTree().addFieldPath("child.payload.optional_int32") + new FieldMaskTree() + .addFieldPath("child.payload.optional_int32") .merge(source, builder, options); expected = NestedTestAllTypes.newBuilder(); expected.getChildBuilder().getPayloadBuilder().setOptionalInt32(1234); assertEquals(expected.build(), builder.build()); builder = NestedTestAllTypes.newBuilder(); - new FieldMaskTree().addFieldPath("child.payload.optional_nested_message") + new FieldMaskTree() + .addFieldPath("child.payload.optional_nested_message") .merge(source, builder, options); expected = NestedTestAllTypes.newBuilder(); - expected.getChildBuilder().getPayloadBuilder().setOptionalNestedMessage( - NestedMessage.newBuilder().setBb(5678)); + expected + .getChildBuilder() + .getPayloadBuilder() + .setOptionalNestedMessage(NestedMessage.newBuilder().setBb(5678)); assertEquals(expected.build(), builder.build()); - builder = NestedTestAllTypes.newBuilder(); - new FieldMaskTree().addFieldPath("child.payload.repeated_int32") + new FieldMaskTree() + .addFieldPath("child.payload.repeated_int32") .merge(source, builder, options); expected = NestedTestAllTypes.newBuilder(); expected.getChildBuilder().getPayloadBuilder().addRepeatedInt32(4321); assertEquals(expected.build(), builder.build()); - builder = NestedTestAllTypes.newBuilder(); - new FieldMaskTree().addFieldPath("child.payload.repeated_nested_message") + new FieldMaskTree() + .addFieldPath("child.payload.repeated_nested_message") .merge(source, builder, options); expected = NestedTestAllTypes.newBuilder(); - expected.getChildBuilder().getPayloadBuilder().addRepeatedNestedMessage( - NestedMessage.newBuilder().setBb(8765)); + expected + .getChildBuilder() + .getPayloadBuilder() + .addRepeatedNestedMessage(NestedMessage.newBuilder().setBb(8765)); assertEquals(expected.build(), builder.build()); - + // Test merging all fields. builder = NestedTestAllTypes.newBuilder(); - new FieldMaskTree().addFieldPath("child").addFieldPath("payload") - .merge(source, builder, options); + new FieldMaskTree() + .addFieldPath("child") + .addFieldPath("payload") + .merge(source, builder, options); assertEquals(source, builder.build()); - + // Test repeated options. builder = NestedTestAllTypes.newBuilder(); builder.getPayloadBuilder().addRepeatedInt32(1000); - new FieldMaskTree().addFieldPath("payload.repeated_int32") - .merge(source, builder, options); + new FieldMaskTree().addFieldPath("payload.repeated_int32").merge(source, builder, options); // Default behavior is to append repeated fields. assertEquals(2, builder.getPayload().getRepeatedInt32Count()); assertEquals(1000, builder.getPayload().getRepeatedInt32(0)); assertEquals(4321, builder.getPayload().getRepeatedInt32(1)); // Change to replace repeated fields. options.setReplaceRepeatedFields(true); - new FieldMaskTree().addFieldPath("payload.repeated_int32") - .merge(source, builder, options); + new FieldMaskTree().addFieldPath("payload.repeated_int32").merge(source, builder, options); assertEquals(1, builder.getPayload().getRepeatedInt32Count()); assertEquals(4321, builder.getPayload().getRepeatedInt32(0)); - + // Test message options. builder = NestedTestAllTypes.newBuilder(); builder.getPayloadBuilder().setOptionalInt32(1000); builder.getPayloadBuilder().setOptionalUint32(2000); - new FieldMaskTree().addFieldPath("payload") - .merge(source, builder, options); + new FieldMaskTree().addFieldPath("payload").merge(source, builder, options); // Default behavior is to merge message fields. assertEquals(1234, builder.getPayload().getOptionalInt32()); assertEquals(2000, builder.getPayload().getOptionalUint32()); - + + // Test merging unset message fields. + NestedTestAllTypes clearedSource = source.toBuilder().clearPayload().build(); + builder = NestedTestAllTypes.newBuilder(); + new FieldMaskTree().addFieldPath("payload").merge(clearedSource, builder, options); + assertEquals(false, builder.hasPayload()); + // Change to replace message fields. options.setReplaceMessageFields(true); builder = NestedTestAllTypes.newBuilder(); builder.getPayloadBuilder().setOptionalInt32(1000); builder.getPayloadBuilder().setOptionalUint32(2000); - new FieldMaskTree().addFieldPath("payload") - .merge(source, builder, options); + new FieldMaskTree().addFieldPath("payload").merge(source, builder, options); assertEquals(1234, builder.getPayload().getOptionalInt32()); assertEquals(0, builder.getPayload().getOptionalUint32()); + + // Test merging unset message fields. + builder = NestedTestAllTypes.newBuilder(); + builder.getPayloadBuilder().setOptionalInt32(1000); + builder.getPayloadBuilder().setOptionalUint32(2000); + new FieldMaskTree().addFieldPath("payload").merge(clearedSource, builder, options); + assertEquals(false, builder.hasPayload()); + + // Test merging unset primitive fields. + builder = source.toBuilder(); + builder.getPayloadBuilder().clearOptionalInt32(); + NestedTestAllTypes sourceWithPayloadInt32Unset = builder.build(); + builder = source.toBuilder(); + new FieldMaskTree() + .addFieldPath("payload.optional_int32") + .merge(sourceWithPayloadInt32Unset, builder, options); + assertEquals(true, builder.getPayload().hasOptionalInt32()); + assertEquals(0, builder.getPayload().getOptionalInt32()); + + // Change to clear unset primitive fields. + options.setReplacePrimitiveFields(true); + builder = source.toBuilder(); + new FieldMaskTree() + .addFieldPath("payload.optional_int32") + .merge(sourceWithPayloadInt32Unset, builder, options); + assertEquals(true, builder.hasPayload()); + assertEquals(false, builder.getPayload().hasOptionalInt32()); } } - diff --git a/java/util/src/test/java/com/google/protobuf/util/FieldMaskUtilTest.java b/java/util/src/test/java/com/google/protobuf/util/FieldMaskUtilTest.java index 67fbe0b1..194f7b9c 100644 --- a/java/util/src/test/java/com/google/protobuf/util/FieldMaskUtilTest.java +++ b/java/util/src/test/java/com/google/protobuf/util/FieldMaskUtilTest.java @@ -53,6 +53,21 @@ public class FieldMaskUtilTest extends TestCase { NestedTestAllTypes.class, "payload.nonexist")); assertTrue(FieldMaskUtil.isValid( + NestedTestAllTypes.class, FieldMaskUtil.fromString("payload"))); + assertFalse(FieldMaskUtil.isValid( + NestedTestAllTypes.class, FieldMaskUtil.fromString("nonexist"))); + assertFalse(FieldMaskUtil.isValid( + NestedTestAllTypes.class, FieldMaskUtil.fromString("payload,nonexist"))); + + assertTrue(FieldMaskUtil.isValid(NestedTestAllTypes.getDescriptor(), "payload")); + assertFalse(FieldMaskUtil.isValid(NestedTestAllTypes.getDescriptor(), "nonexist")); + + assertTrue(FieldMaskUtil.isValid( + NestedTestAllTypes.getDescriptor(), FieldMaskUtil.fromString("payload"))); + assertFalse(FieldMaskUtil.isValid( + NestedTestAllTypes.getDescriptor(), FieldMaskUtil.fromString("nonexist"))); + + assertTrue(FieldMaskUtil.isValid( NestedTestAllTypes.class, "payload.optional_nested_message.bb")); // Repeated fields cannot have sub-paths. assertFalse(FieldMaskUtil.isValid( @@ -74,7 +89,7 @@ public class FieldMaskUtilTest extends TestCase { addPaths("bar").addPaths("").build(); assertEquals("foo,bar", FieldMaskUtil.toString(mask)); } - + public void testFromString() throws Exception { FieldMask mask = FieldMaskUtil.fromString(""); assertEquals(0, mask.getPathsCount()); @@ -85,16 +100,16 @@ public class FieldMaskUtilTest extends TestCase { assertEquals(2, mask.getPathsCount()); assertEquals("foo", mask.getPaths(0)); assertEquals("bar.baz", mask.getPaths(1)); - + // Empty field paths are ignore. mask = FieldMaskUtil.fromString(",foo,,bar,"); assertEquals(2, mask.getPathsCount()); assertEquals("foo", mask.getPaths(0)); assertEquals("bar", mask.getPaths(1)); - + // Check whether the field paths are valid if a class parameter is provided. mask = FieldMaskUtil.fromString(NestedTestAllTypes.class, ",payload"); - + try { mask = FieldMaskUtil.fromString( NestedTestAllTypes.class, "payload,nonexist"); @@ -103,6 +118,31 @@ public class FieldMaskUtilTest extends TestCase { // Expected. } } + + public void testFromFieldNumbers() throws Exception { + FieldMask mask = FieldMaskUtil.fromFieldNumbers(TestAllTypes.class); + assertEquals(0, mask.getPathsCount()); + mask = + FieldMaskUtil.fromFieldNumbers( + TestAllTypes.class, TestAllTypes.OPTIONAL_INT32_FIELD_NUMBER); + assertEquals(1, mask.getPathsCount()); + assertEquals("optional_int32", mask.getPaths(0)); + mask = + FieldMaskUtil.fromFieldNumbers( + TestAllTypes.class, + TestAllTypes.OPTIONAL_INT32_FIELD_NUMBER, + TestAllTypes.OPTIONAL_INT64_FIELD_NUMBER); + assertEquals(2, mask.getPathsCount()); + assertEquals("optional_int32", mask.getPaths(0)); + assertEquals("optional_int64", mask.getPaths(1)); + + try { + int invalidFieldNumber = 1000; + mask = FieldMaskUtil.fromFieldNumbers(TestAllTypes.class, invalidFieldNumber); + fail("Exception is expected."); + } catch (IllegalArgumentException expected) { + } + } public void testUnion() throws Exception { // Only test a simple case here and expect @@ -112,6 +152,15 @@ public class FieldMaskUtilTest extends TestCase { FieldMask result = FieldMaskUtil.union(mask1, mask2); assertEquals("bar,foo", FieldMaskUtil.toString(result)); } + + public void testUnion_usingVarArgs() throws Exception { + FieldMask mask1 = FieldMaskUtil.fromString("foo"); + FieldMask mask2 = FieldMaskUtil.fromString("foo.bar,bar.quz"); + FieldMask mask3 = FieldMaskUtil.fromString("bar.quz"); + FieldMask mask4 = FieldMaskUtil.fromString("bar"); + FieldMask result = FieldMaskUtil.union(mask1, mask2, mask3, mask4); + assertEquals("bar,foo", FieldMaskUtil.toString(result)); + } public void testIntersection() throws Exception { // Only test a simple case here and expect diff --git a/java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java b/java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java index ddf5ad2a..d95b626c 100644 --- a/java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java +++ b/java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java @@ -51,9 +51,11 @@ import com.google.protobuf.util.JsonTestProto.TestAllTypes; import com.google.protobuf.util.JsonTestProto.TestAllTypes.NestedEnum; import com.google.protobuf.util.JsonTestProto.TestAllTypes.NestedMessage; import com.google.protobuf.util.JsonTestProto.TestAny; +import com.google.protobuf.util.JsonTestProto.TestCustomJsonName; import com.google.protobuf.util.JsonTestProto.TestDuration; import com.google.protobuf.util.JsonTestProto.TestFieldMask; import com.google.protobuf.util.JsonTestProto.TestMap; +import com.google.protobuf.util.JsonTestProto.TestOneof; import com.google.protobuf.util.JsonTestProto.TestStruct; import com.google.protobuf.util.JsonTestProto.TestTimestamp; import com.google.protobuf.util.JsonTestProto.TestWrappers; @@ -120,11 +122,11 @@ public class JsonFormatTest extends TestCase { builder.addRepeatedNestedEnum(NestedEnum.BAZ); builder.addRepeatedNestedMessageBuilder().setValue(200); } - + private void assertRoundTripEquals(Message message) throws Exception { assertRoundTripEquals(message, TypeRegistry.getEmptyTypeRegistry()); } - + private void assertRoundTripEquals(Message message, TypeRegistry registry) throws Exception { JsonFormat.Printer printer = JsonFormat.printer().usingTypeRegistry(registry); JsonFormat.Parser parser = JsonFormat.parser().usingTypeRegistry(registry); @@ -133,72 +135,69 @@ public class JsonFormatTest extends TestCase { Message parsedMessage = builder.build(); assertEquals(message.toString(), parsedMessage.toString()); } - + private String toJsonString(Message message) throws IOException { return JsonFormat.printer().print(message); } - + private void mergeFromJson(String json, Message.Builder builder) throws IOException { JsonFormat.parser().merge(json, builder); } - + public void testAllFields() throws Exception { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); setAllFields(builder); TestAllTypes message = builder.build(); - - assertEquals( - "{\n" - + " \"optionalInt32\": 1234,\n" - + " \"optionalInt64\": \"1234567890123456789\",\n" - + " \"optionalUint32\": 5678,\n" - + " \"optionalUint64\": \"2345678901234567890\",\n" - + " \"optionalSint32\": 9012,\n" - + " \"optionalSint64\": \"3456789012345678901\",\n" - + " \"optionalFixed32\": 3456,\n" - + " \"optionalFixed64\": \"4567890123456789012\",\n" - + " \"optionalSfixed32\": 7890,\n" - + " \"optionalSfixed64\": \"5678901234567890123\",\n" - + " \"optionalFloat\": 1.5,\n" - + " \"optionalDouble\": 1.25,\n" - + " \"optionalBool\": true,\n" - + " \"optionalString\": \"Hello world!\",\n" - + " \"optionalBytes\": \"AAEC\",\n" - + " \"optionalNestedMessage\": {\n" - + " \"value\": 100\n" - + " },\n" - + " \"optionalNestedEnum\": \"BAR\",\n" - + " \"repeatedInt32\": [1234, 234],\n" - + " \"repeatedInt64\": [\"1234567890123456789\", \"234567890123456789\"],\n" - + " \"repeatedUint32\": [5678, 678],\n" - + " \"repeatedUint64\": [\"2345678901234567890\", \"345678901234567890\"],\n" - + " \"repeatedSint32\": [9012, 10],\n" - + " \"repeatedSint64\": [\"3456789012345678901\", \"456789012345678901\"],\n" - + " \"repeatedFixed32\": [3456, 456],\n" - + " \"repeatedFixed64\": [\"4567890123456789012\", \"567890123456789012\"],\n" - + " \"repeatedSfixed32\": [7890, 890],\n" - + " \"repeatedSfixed64\": [\"5678901234567890123\", \"678901234567890123\"],\n" - + " \"repeatedFloat\": [1.5, 11.5],\n" - + " \"repeatedDouble\": [1.25, 11.25],\n" - + " \"repeatedBool\": [true, true],\n" - + " \"repeatedString\": [\"Hello world!\", \"ello world!\"],\n" - + " \"repeatedBytes\": [\"AAEC\", \"AQI=\"],\n" - + " \"repeatedNestedMessage\": [{\n" - + " \"value\": 100\n" - + " }, {\n" - + " \"value\": 200\n" - + " }],\n" - + " \"repeatedNestedEnum\": [\"BAR\", \"BAZ\"]\n" - + "}", + + assertEquals( + "{\n" + + " \"optionalInt32\": 1234,\n" + + " \"optionalInt64\": \"1234567890123456789\",\n" + + " \"optionalUint32\": 5678,\n" + + " \"optionalUint64\": \"2345678901234567890\",\n" + + " \"optionalSint32\": 9012,\n" + + " \"optionalSint64\": \"3456789012345678901\",\n" + + " \"optionalFixed32\": 3456,\n" + + " \"optionalFixed64\": \"4567890123456789012\",\n" + + " \"optionalSfixed32\": 7890,\n" + + " \"optionalSfixed64\": \"5678901234567890123\",\n" + + " \"optionalFloat\": 1.5,\n" + + " \"optionalDouble\": 1.25,\n" + + " \"optionalBool\": true,\n" + + " \"optionalString\": \"Hello world!\",\n" + + " \"optionalBytes\": \"AAEC\",\n" + + " \"optionalNestedMessage\": {\n" + + " \"value\": 100\n" + + " },\n" + + " \"optionalNestedEnum\": \"BAR\",\n" + + " \"repeatedInt32\": [1234, 234],\n" + + " \"repeatedInt64\": [\"1234567890123456789\", \"234567890123456789\"],\n" + + " \"repeatedUint32\": [5678, 678],\n" + + " \"repeatedUint64\": [\"2345678901234567890\", \"345678901234567890\"],\n" + + " \"repeatedSint32\": [9012, 10],\n" + + " \"repeatedSint64\": [\"3456789012345678901\", \"456789012345678901\"],\n" + + " \"repeatedFixed32\": [3456, 456],\n" + + " \"repeatedFixed64\": [\"4567890123456789012\", \"567890123456789012\"],\n" + + " \"repeatedSfixed32\": [7890, 890],\n" + + " \"repeatedSfixed64\": [\"5678901234567890123\", \"678901234567890123\"],\n" + + " \"repeatedFloat\": [1.5, 11.5],\n" + + " \"repeatedDouble\": [1.25, 11.25],\n" + + " \"repeatedBool\": [true, true],\n" + + " \"repeatedString\": [\"Hello world!\", \"ello world!\"],\n" + + " \"repeatedBytes\": [\"AAEC\", \"AQI=\"],\n" + + " \"repeatedNestedMessage\": [{\n" + + " \"value\": 100\n" + + " }, {\n" + + " \"value\": 200\n" + + " }],\n" + + " \"repeatedNestedEnum\": [\"BAR\", \"BAZ\"]\n" + + "}", toJsonString(message)); - + assertRoundTripEquals(message); } - + public void testUnknownEnumValues() throws Exception { - // Unknown enum values will be dropped. - // TODO(xiaofeng): We may want to revisit this (whether we should omit - // unknown enum values). TestAllTypes message = TestAllTypes.newBuilder() .setOptionalNestedEnumValue(12345) .addRepeatedNestedEnumValue(12345) @@ -206,21 +205,26 @@ public class JsonFormatTest extends TestCase { .build(); assertEquals( "{\n" - + " \"repeatedNestedEnum\": [\"FOO\"]\n" + + " \"optionalNestedEnum\": 12345,\n" + + " \"repeatedNestedEnum\": [12345, \"FOO\"]\n" + "}", toJsonString(message)); - + assertRoundTripEquals(message); + TestMap.Builder mapBuilder = TestMap.newBuilder(); mapBuilder.getMutableInt32ToEnumMapValue().put(1, 0); mapBuilder.getMutableInt32ToEnumMapValue().put(2, 12345); TestMap mapMessage = mapBuilder.build(); assertEquals( - "{\n" - + " \"int32ToEnumMap\": {\n" - + " \"1\": \"FOO\"\n" - + " }\n" - + "}", toJsonString(mapMessage)); + "{\n" + + " \"int32ToEnumMap\": {\n" + + " \"1\": \"FOO\",\n" + + " \"2\": 12345\n" + + " }\n" + + "}", + toJsonString(mapMessage)); + assertRoundTripEquals(mapMessage); } - + public void testSpecialFloatValues() throws Exception { TestAllTypes message = TestAllTypes.newBuilder() .addRepeatedFloat(Float.NaN) @@ -235,10 +239,10 @@ public class JsonFormatTest extends TestCase { + " \"repeatedFloat\": [\"NaN\", \"Infinity\", \"-Infinity\"],\n" + " \"repeatedDouble\": [\"NaN\", \"Infinity\", \"-Infinity\"]\n" + "}", toJsonString(message)); - + assertRoundTripEquals(message); } - + public void testParserAcceptStringForNumbericField() throws Exception { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); mergeFromJson( @@ -262,7 +266,36 @@ public class JsonFormatTest extends TestCase { assertEquals(1.25, message.getOptionalDouble()); assertEquals(true, message.getOptionalBool()); } - + + public void testParserAcceptFloatingPointValueForIntegerField() throws Exception { + // Test that numeric values like "1.000", "1e5" will also be accepted. + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + mergeFromJson( + "{\n" + + " \"repeatedInt32\": [1.000, 1e5, \"1.000\", \"1e5\"],\n" + + " \"repeatedUint32\": [1.000, 1e5, \"1.000\", \"1e5\"],\n" + + " \"repeatedInt64\": [1.000, 1e5, \"1.000\", \"1e5\"],\n" + + " \"repeatedUint64\": [1.000, 1e5, \"1.000\", \"1e5\"]\n" + + "}", builder); + int[] expectedValues = new int[]{1, 100000, 1, 100000}; + assertEquals(4, builder.getRepeatedInt32Count()); + assertEquals(4, builder.getRepeatedUint32Count()); + assertEquals(4, builder.getRepeatedInt64Count()); + assertEquals(4, builder.getRepeatedUint64Count()); + for (int i = 0; i < 4; ++i) { + assertEquals(expectedValues[i], builder.getRepeatedInt32(i)); + assertEquals(expectedValues[i], builder.getRepeatedUint32(i)); + assertEquals(expectedValues[i], builder.getRepeatedInt64(i)); + assertEquals(expectedValues[i], builder.getRepeatedUint64(i)); + } + + // Non-integers will still be rejected. + assertRejects("optionalInt32", "1.5"); + assertRejects("optionalUint32", "1.5"); + assertRejects("optionalInt64", "1.5"); + assertRejects("optionalUint64", "1.5"); + } + private void assertRejects(String name, String value) { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); try { @@ -280,24 +313,25 @@ public class JsonFormatTest extends TestCase { // Expected. } } - + private void assertAccepts(String name, String value) throws IOException { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); // Both numeric form and string form are accepted. mergeFromJson("{\"" + name + "\":" + value + "}", builder); + builder.clear(); mergeFromJson("{\"" + name + "\":\"" + value + "\"}", builder); } - + public void testParserRejectOutOfRangeNumericValues() throws Exception { assertAccepts("optionalInt32", String.valueOf(Integer.MAX_VALUE)); assertAccepts("optionalInt32", String.valueOf(Integer.MIN_VALUE)); assertRejects("optionalInt32", String.valueOf(Integer.MAX_VALUE + 1L)); assertRejects("optionalInt32", String.valueOf(Integer.MIN_VALUE - 1L)); - + assertAccepts("optionalUint32", String.valueOf(Integer.MAX_VALUE + 1L)); assertRejects("optionalUint32", "123456789012345"); assertRejects("optionalUint32", "-1"); - + BigInteger one = new BigInteger("1"); BigInteger maxLong = new BigInteger(String.valueOf(Long.MAX_VALUE)); BigInteger minLong = new BigInteger(String.valueOf(Long.MIN_VALUE)); @@ -318,7 +352,7 @@ public class JsonFormatTest extends TestCase { assertAccepts("optionalFloat", String.valueOf(-Float.MAX_VALUE)); assertRejects("optionalFloat", String.valueOf(Double.MAX_VALUE)); assertRejects("optionalFloat", String.valueOf(-Double.MAX_VALUE)); - + BigDecimal moreThanOne = new BigDecimal("1.000001"); BigDecimal maxDouble = new BigDecimal(Double.MAX_VALUE); BigDecimal minDouble = new BigDecimal(-Double.MAX_VALUE); @@ -327,7 +361,7 @@ public class JsonFormatTest extends TestCase { assertRejects("optionalDouble", maxDouble.multiply(moreThanOne).toString()); assertRejects("optionalDouble", minDouble.multiply(moreThanOne).toString()); } - + public void testParserAcceptNull() throws Exception { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); mergeFromJson( @@ -369,87 +403,77 @@ public class JsonFormatTest extends TestCase { + "}", builder); TestAllTypes message = builder.build(); assertEquals(TestAllTypes.getDefaultInstance(), message); - - // Repeated field elements can also be null. - builder = TestAllTypes.newBuilder(); - mergeFromJson( - "{\n" - + " \"repeatedInt32\": [null, null],\n" - + " \"repeatedInt64\": [null, null],\n" - + " \"repeatedUint32\": [null, null],\n" - + " \"repeatedUint64\": [null, null],\n" - + " \"repeatedSint32\": [null, null],\n" - + " \"repeatedSint64\": [null, null],\n" - + " \"repeatedFixed32\": [null, null],\n" - + " \"repeatedFixed64\": [null, null],\n" - + " \"repeatedSfixed32\": [null, null],\n" - + " \"repeatedSfixed64\": [null, null],\n" - + " \"repeatedFloat\": [null, null],\n" - + " \"repeatedDouble\": [null, null],\n" - + " \"repeatedBool\": [null, null],\n" - + " \"repeatedString\": [null, null],\n" - + " \"repeatedBytes\": [null, null],\n" - + " \"repeatedNestedMessage\": [null, null],\n" - + " \"repeatedNestedEnum\": [null, null]\n" - + "}", builder); - message = builder.build(); - // "null" elements will be parsed to default values. - assertEquals(2, message.getRepeatedInt32Count()); - assertEquals(0, message.getRepeatedInt32(0)); - assertEquals(0, message.getRepeatedInt32(1)); - assertEquals(2, message.getRepeatedInt32Count()); - assertEquals(0, message.getRepeatedInt32(0)); - assertEquals(0, message.getRepeatedInt32(1)); - assertEquals(2, message.getRepeatedInt64Count()); - assertEquals(0, message.getRepeatedInt64(0)); - assertEquals(0, message.getRepeatedInt64(1)); - assertEquals(2, message.getRepeatedUint32Count()); - assertEquals(0, message.getRepeatedUint32(0)); - assertEquals(0, message.getRepeatedUint32(1)); - assertEquals(2, message.getRepeatedUint64Count()); - assertEquals(0, message.getRepeatedUint64(0)); - assertEquals(0, message.getRepeatedUint64(1)); - assertEquals(2, message.getRepeatedSint32Count()); - assertEquals(0, message.getRepeatedSint32(0)); - assertEquals(0, message.getRepeatedSint32(1)); - assertEquals(2, message.getRepeatedSint64Count()); - assertEquals(0, message.getRepeatedSint64(0)); - assertEquals(0, message.getRepeatedSint64(1)); - assertEquals(2, message.getRepeatedFixed32Count()); - assertEquals(0, message.getRepeatedFixed32(0)); - assertEquals(0, message.getRepeatedFixed32(1)); - assertEquals(2, message.getRepeatedFixed64Count()); - assertEquals(0, message.getRepeatedFixed64(0)); - assertEquals(0, message.getRepeatedFixed64(1)); - assertEquals(2, message.getRepeatedSfixed32Count()); - assertEquals(0, message.getRepeatedSfixed32(0)); - assertEquals(0, message.getRepeatedSfixed32(1)); - assertEquals(2, message.getRepeatedSfixed64Count()); - assertEquals(0, message.getRepeatedSfixed64(0)); - assertEquals(0, message.getRepeatedSfixed64(1)); - assertEquals(2, message.getRepeatedFloatCount()); - assertEquals(0f, message.getRepeatedFloat(0)); - assertEquals(0f, message.getRepeatedFloat(1)); - assertEquals(2, message.getRepeatedDoubleCount()); - assertEquals(0.0, message.getRepeatedDouble(0)); - assertEquals(0.0, message.getRepeatedDouble(1)); - assertEquals(2, message.getRepeatedBoolCount()); - assertFalse(message.getRepeatedBool(0)); - assertFalse(message.getRepeatedBool(1)); - assertEquals(2, message.getRepeatedStringCount()); - assertTrue(message.getRepeatedString(0).isEmpty()); - assertTrue(message.getRepeatedString(1).isEmpty()); - assertEquals(2, message.getRepeatedBytesCount()); - assertTrue(message.getRepeatedBytes(0).isEmpty()); - assertTrue(message.getRepeatedBytes(1).isEmpty()); - assertEquals(2, message.getRepeatedNestedMessageCount()); - assertEquals(NestedMessage.getDefaultInstance(), message.getRepeatedNestedMessage(0)); - assertEquals(NestedMessage.getDefaultInstance(), message.getRepeatedNestedMessage(1)); - assertEquals(2, message.getRepeatedNestedEnumCount()); - assertEquals(0, message.getRepeatedNestedEnumValue(0)); - assertEquals(0, message.getRepeatedNestedEnumValue(1)); - } - + + // Repeated field elements cannot be null. + try { + builder = TestAllTypes.newBuilder(); + mergeFromJson( + "{\n" + + " \"repeatedInt32\": [null, null],\n" + + "}", builder); + fail(); + } catch (InvalidProtocolBufferException e) { + // Exception expected. + } + + try { + builder = TestAllTypes.newBuilder(); + mergeFromJson( + "{\n" + + " \"repeatedNestedMessage\": [null, null],\n" + + "}", builder); + fail(); + } catch (InvalidProtocolBufferException e) { + // Exception expected. + } + } + + public void testParserRejectDuplicatedFields() throws Exception { + // TODO(xiaofeng): The parser we are currently using (GSON) will accept and keep the last + // one if multiple entries have the same name. This is not the desired behavior but it can + // only be fixed by using our own parser. Here we only test the cases where the names are + // different but still referring to the same field. + + // Duplicated optional fields. + try { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + mergeFromJson( + "{\n" + + " \"optionalNestedMessage\": {},\n" + + " \"optional_nested_message\": {}\n" + + "}", builder); + fail(); + } catch (InvalidProtocolBufferException e) { + // Exception expected. + } + + // Duplicated repeated fields. + try { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + mergeFromJson( + "{\n" + + " \"repeatedNestedMessage\": [null, null],\n" + + " \"repeated_nested_message\": [null, null]\n" + + "}", builder); + fail(); + } catch (InvalidProtocolBufferException e) { + // Exception expected. + } + + // Duplicated oneof fields. + try { + TestOneof.Builder builder = TestOneof.newBuilder(); + mergeFromJson( + "{\n" + + " \"oneofInt32\": 1,\n" + + " \"oneof_int32\": 2\n" + + "}", builder); + fail(); + } catch (InvalidProtocolBufferException e) { + // Exception expected. + } + } + public void testMapFields() throws Exception { TestMap.Builder builder = TestMap.newBuilder(); builder.getMutableInt32ToInt32Map().put(1, 10); @@ -484,7 +508,7 @@ public class JsonFormatTest extends TestCase { 8, NestedMessage.newBuilder().setValue(1234).build()); builder.getMutableInt32ToEnumMap().put(9, NestedEnum.BAR); TestMap message = builder.build(); - + assertEquals( "{\n" + " \"int32ToInt32Map\": {\n" @@ -575,13 +599,13 @@ public class JsonFormatTest extends TestCase { + " }\n" + "}", toJsonString(message)); assertRoundTripEquals(message); - + // Test multiple entries. builder = TestMap.newBuilder(); builder.getMutableInt32ToInt32Map().put(1, 2); builder.getMutableInt32ToInt32Map().put(3, 4); message = builder.build(); - + assertEquals( "{\n" + " \"int32ToInt32Map\": {\n" @@ -591,21 +615,33 @@ public class JsonFormatTest extends TestCase { + "}", toJsonString(message)); assertRoundTripEquals(message); } - - public void testMapNullValueIsDefault() throws Exception { - TestMap.Builder builder = TestMap.newBuilder(); - mergeFromJson( - "{\n" - + " \"int32ToInt32Map\": {\"1\": null},\n" - + " \"int32ToMessageMap\": {\"2\": null}\n" - + "}", builder); - TestMap message = builder.build(); - assertTrue(message.getInt32ToInt32Map().containsKey(1)); - assertEquals(0, message.getInt32ToInt32Map().get(1).intValue()); - assertTrue(message.getInt32ToMessageMap().containsKey(2)); - assertEquals(0, message.getInt32ToMessageMap().get(2).getValue()); + + public void testMapNullValueIsRejected() throws Exception { + try { + TestMap.Builder builder = TestMap.newBuilder(); + mergeFromJson( + "{\n" + + " \"int32ToInt32Map\": {null: 1},\n" + + " \"int32ToMessageMap\": {null: 2}\n" + + "}", builder); + fail(); + } catch (InvalidProtocolBufferException e) { + // Exception expected. + } + + try { + TestMap.Builder builder = TestMap.newBuilder(); + mergeFromJson( + "{\n" + + " \"int32ToInt32Map\": {\"1\": null},\n" + + " \"int32ToMessageMap\": {\"2\": null}\n" + + "}", builder); + fail(); + } catch (InvalidProtocolBufferException e) { + // Exception expected. + } } - + public void testParserAcceptNonQuotedObjectKey() throws Exception { TestMap.Builder builder = TestMap.newBuilder(); mergeFromJson( @@ -617,7 +653,7 @@ public class JsonFormatTest extends TestCase { assertEquals(2, message.getInt32ToInt32Map().get(1).intValue()); assertEquals(3, message.getStringToInt32Map().get("hello").intValue()); } - + public void testWrappers() throws Exception { TestWrappers.Builder builder = TestWrappers.newBuilder(); builder.getBoolValueBuilder().setValue(false); @@ -630,7 +666,7 @@ public class JsonFormatTest extends TestCase { builder.getStringValueBuilder().setValue(""); builder.getBytesValueBuilder().setValue(ByteString.EMPTY); TestWrappers message = builder.build(); - + assertEquals( "{\n" + " \"int32Value\": 0,\n" @@ -656,7 +692,7 @@ public class JsonFormatTest extends TestCase { builder.getStringValueBuilder().setValue("7"); builder.getBytesValueBuilder().setValue(ByteString.copyFrom(new byte[]{8})); message = builder.build(); - + assertEquals( "{\n" + " \"int32Value\": 1,\n" @@ -671,43 +707,43 @@ public class JsonFormatTest extends TestCase { + "}", toJsonString(message)); assertRoundTripEquals(message); } - + public void testTimestamp() throws Exception { TestTimestamp message = TestTimestamp.newBuilder() .setTimestampValue(TimeUtil.parseTimestamp("1970-01-01T00:00:00Z")) .build(); - + assertEquals( "{\n" + " \"timestampValue\": \"1970-01-01T00:00:00Z\"\n" + "}", toJsonString(message)); assertRoundTripEquals(message); } - + public void testDuration() throws Exception { TestDuration message = TestDuration.newBuilder() .setDurationValue(TimeUtil.parseDuration("12345s")) .build(); - + assertEquals( "{\n" + " \"durationValue\": \"12345s\"\n" + "}", toJsonString(message)); assertRoundTripEquals(message); } - + public void testFieldMask() throws Exception { TestFieldMask message = TestFieldMask.newBuilder() .setFieldMaskValue(FieldMaskUtil.fromString("foo.bar,baz")) .build(); - + assertEquals( "{\n" + " \"fieldMaskValue\": \"foo.bar,baz\"\n" + "}", toJsonString(message)); assertRoundTripEquals(message); } - + public void testStruct() throws Exception { // Build a struct with all possible values. TestStruct.Builder builder = TestStruct.newBuilder(); @@ -729,7 +765,7 @@ public class JsonFormatTest extends TestCase { structBuilder.getMutableFields().put( "list_value", Value.newBuilder().setListValue(listBuilder.build()).build()); TestStruct message = builder.build(); - + assertEquals( "{\n" + " \"structValue\": {\n" @@ -743,12 +779,32 @@ public class JsonFormatTest extends TestCase { + " }\n" + "}", toJsonString(message)); assertRoundTripEquals(message); + + builder = TestStruct.newBuilder(); + builder.setValue(Value.newBuilder().setNullValueValue(0).build()); + message = builder.build(); + assertEquals( + "{\n" + + " \"value\": null\n" + + "}", toJsonString(message)); + assertRoundTripEquals(message); + + builder = TestStruct.newBuilder(); + listBuilder = builder.getListValueBuilder(); + listBuilder.addValues(Value.newBuilder().setNumberValue(31831.125).build()); + listBuilder.addValues(Value.newBuilder().setNullValueValue(0).build()); + message = builder.build(); + assertEquals( + "{\n" + + " \"listValue\": [31831.125, null]\n" + + "}", toJsonString(message)); + assertRoundTripEquals(message); } - + public void testAnyFields() throws Exception { TestAllTypes content = TestAllTypes.newBuilder().setOptionalInt32(1234).build(); TestAny message = TestAny.newBuilder().setAnyValue(Any.pack(content)).build(); - + // A TypeRegistry must be provided in order to convert Any types. try { toJsonString(message); @@ -756,11 +812,11 @@ public class JsonFormatTest extends TestCase { } catch (IOException e) { // Expected. } - + JsonFormat.TypeRegistry registry = JsonFormat.TypeRegistry.newBuilder() .add(TestAllTypes.getDescriptor()).build(); JsonFormat.Printer printer = JsonFormat.printer().usingTypeRegistry(registry); - + assertEquals( "{\n" + " \"anyValue\": {\n" @@ -769,8 +825,8 @@ public class JsonFormatTest extends TestCase { + " }\n" + "}" , printer.print(message)); assertRoundTripEquals(message, registry); - - + + // Well-known types have a special formatting when embedded in Any. // // 1. Any in Any. @@ -784,7 +840,7 @@ public class JsonFormatTest extends TestCase { + " }\n" + "}", printer.print(anyMessage)); assertRoundTripEquals(anyMessage, registry); - + // 2. Wrappers in Any. anyMessage = Any.pack(Int32Value.newBuilder().setValue(12345).build()); assertEquals( @@ -850,7 +906,7 @@ public class JsonFormatTest extends TestCase { + " \"value\": \"AQI=\"\n" + "}", printer.print(anyMessage)); assertRoundTripEquals(anyMessage, registry); - + // 3. Timestamp in Any. anyMessage = Any.pack(TimeUtil.parseTimestamp("1969-12-31T23:59:59Z")); assertEquals( @@ -859,7 +915,7 @@ public class JsonFormatTest extends TestCase { + " \"value\": \"1969-12-31T23:59:59Z\"\n" + "}", printer.print(anyMessage)); assertRoundTripEquals(anyMessage, registry); - + // 4. Duration in Any anyMessage = Any.pack(TimeUtil.parseDuration("12345.10s")); assertEquals( @@ -891,8 +947,17 @@ public class JsonFormatTest extends TestCase { + " }\n" + "}", printer.print(anyMessage)); assertRoundTripEquals(anyMessage, registry); + Value.Builder valueBuilder = Value.newBuilder(); + valueBuilder.setNumberValue(1); + anyMessage = Any.pack(valueBuilder.build()); + assertEquals( + "{\n" + + " \"@type\": \"type.googleapis.com/google.protobuf.Value\",\n" + + " \"value\": 1.0\n" + + "}", printer.print(anyMessage)); + assertRoundTripEquals(anyMessage, registry); } - + public void testParserMissingTypeUrl() throws Exception { try { Any.Builder builder = Any.newBuilder(); @@ -905,7 +970,7 @@ public class JsonFormatTest extends TestCase { // Expected. } } - + public void testParserUnexpectedTypeUrl() throws Exception { try { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); @@ -917,9 +982,9 @@ public class JsonFormatTest extends TestCase { fail("Exception is expected."); } catch (IOException e) { // Expected. - } + } } - + public void testParserRejectTrailingComma() throws Exception { try { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); @@ -947,20 +1012,13 @@ public class JsonFormatTest extends TestCase { // // Expected. // } } - + public void testParserRejectInvalidBase64() throws Exception { - try { - TestAllTypes.Builder builder = TestAllTypes.newBuilder(); - mergeFromJson( - "{\n" - + " \"optionalBytes\": \"!@#$\"\n" - + "}", builder); - fail("Exception is expected."); - } catch (InvalidProtocolBufferException e) { - // Expected. - } + assertRejects("optionalBytes", "!@#$"); + // We use standard BASE64 with paddings. + assertRejects("optionalBytes", "AQI"); } - + public void testParserRejectInvalidEnumValue() throws Exception { try { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); @@ -971,6 +1029,146 @@ public class JsonFormatTest extends TestCase { fail("Exception is expected."); } catch (InvalidProtocolBufferException e) { // Expected. - } + } + } + + public void testCustomJsonName() throws Exception { + TestCustomJsonName message = TestCustomJsonName.newBuilder().setValue(12345).build(); + assertEquals("{\n" + " \"@value\": 12345\n" + "}", JsonFormat.printer().print(message)); + assertRoundTripEquals(message); + } + + public void testDefaultGsonDoesNotHtmlEscape() throws Exception { + TestAllTypes message = TestAllTypes.newBuilder().setOptionalString("=").build(); + assertEquals( + "{\n" + " \"optionalString\": \"=\"" + "\n}", JsonFormat.printer().print(message)); + } + + public void testIncludingDefaultValueFields() throws Exception { + TestAllTypes message = TestAllTypes.getDefaultInstance(); + assertEquals("{\n}", JsonFormat.printer().print(message)); + assertEquals( + "{\n" + + " \"optionalInt32\": 0,\n" + + " \"optionalInt64\": \"0\",\n" + + " \"optionalUint32\": 0,\n" + + " \"optionalUint64\": \"0\",\n" + + " \"optionalSint32\": 0,\n" + + " \"optionalSint64\": \"0\",\n" + + " \"optionalFixed32\": 0,\n" + + " \"optionalFixed64\": \"0\",\n" + + " \"optionalSfixed32\": 0,\n" + + " \"optionalSfixed64\": \"0\",\n" + + " \"optionalFloat\": 0.0,\n" + + " \"optionalDouble\": 0.0,\n" + + " \"optionalBool\": false,\n" + + " \"optionalString\": \"\",\n" + + " \"optionalBytes\": \"\",\n" + + " \"optionalNestedEnum\": \"FOO\",\n" + + " \"repeatedInt32\": [],\n" + + " \"repeatedInt64\": [],\n" + + " \"repeatedUint32\": [],\n" + + " \"repeatedUint64\": [],\n" + + " \"repeatedSint32\": [],\n" + + " \"repeatedSint64\": [],\n" + + " \"repeatedFixed32\": [],\n" + + " \"repeatedFixed64\": [],\n" + + " \"repeatedSfixed32\": [],\n" + + " \"repeatedSfixed64\": [],\n" + + " \"repeatedFloat\": [],\n" + + " \"repeatedDouble\": [],\n" + + " \"repeatedBool\": [],\n" + + " \"repeatedString\": [],\n" + + " \"repeatedBytes\": [],\n" + + " \"repeatedNestedMessage\": [],\n" + + " \"repeatedNestedEnum\": []\n" + + "}", + JsonFormat.printer().includingDefaultValueFields().print(message)); + + TestMap mapMessage = TestMap.getDefaultInstance(); + assertEquals("{\n}", JsonFormat.printer().print(mapMessage)); + assertEquals( + "{\n" + + " \"int32ToInt32Map\": {\n" + + " },\n" + + " \"int64ToInt32Map\": {\n" + + " },\n" + + " \"uint32ToInt32Map\": {\n" + + " },\n" + + " \"uint64ToInt32Map\": {\n" + + " },\n" + + " \"sint32ToInt32Map\": {\n" + + " },\n" + + " \"sint64ToInt32Map\": {\n" + + " },\n" + + " \"fixed32ToInt32Map\": {\n" + + " },\n" + + " \"fixed64ToInt32Map\": {\n" + + " },\n" + + " \"sfixed32ToInt32Map\": {\n" + + " },\n" + + " \"sfixed64ToInt32Map\": {\n" + + " },\n" + + " \"boolToInt32Map\": {\n" + + " },\n" + + " \"stringToInt32Map\": {\n" + + " },\n" + + " \"int32ToInt64Map\": {\n" + + " },\n" + + " \"int32ToUint32Map\": {\n" + + " },\n" + + " \"int32ToUint64Map\": {\n" + + " },\n" + + " \"int32ToSint32Map\": {\n" + + " },\n" + + " \"int32ToSint64Map\": {\n" + + " },\n" + + " \"int32ToFixed32Map\": {\n" + + " },\n" + + " \"int32ToFixed64Map\": {\n" + + " },\n" + + " \"int32ToSfixed32Map\": {\n" + + " },\n" + + " \"int32ToSfixed64Map\": {\n" + + " },\n" + + " \"int32ToFloatMap\": {\n" + + " },\n" + + " \"int32ToDoubleMap\": {\n" + + " },\n" + + " \"int32ToBoolMap\": {\n" + + " },\n" + + " \"int32ToStringMap\": {\n" + + " },\n" + + " \"int32ToBytesMap\": {\n" + + " },\n" + + " \"int32ToMessageMap\": {\n" + + " },\n" + + " \"int32ToEnumMap\": {\n" + + " }\n" + + "}", + JsonFormat.printer().includingDefaultValueFields().print(mapMessage)); + } + + public void testPreservingProtoFieldNames() throws Exception { + TestAllTypes message = TestAllTypes.newBuilder().setOptionalInt32(12345).build(); + assertEquals("{\n" + " \"optionalInt32\": 12345\n" + "}", JsonFormat.printer().print(message)); + assertEquals( + "{\n" + " \"optional_int32\": 12345\n" + "}", + JsonFormat.printer().preservingProtoFieldNames().print(message)); + + // The json_name field option is ignored when configured to use original proto field names. + TestCustomJsonName messageWithCustomJsonName = + TestCustomJsonName.newBuilder().setValue(12345).build(); + assertEquals( + "{\n" + " \"value\": 12345\n" + "}", + JsonFormat.printer().preservingProtoFieldNames().print(messageWithCustomJsonName)); + + // Parsers accept both original proto field names and lowerCamelCase names. + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + JsonFormat.parser().merge("{\"optionalInt32\": 12345}", builder); + assertEquals(12345, builder.getOptionalInt32()); + builder.clear(); + JsonFormat.parser().merge("{\"optional_int32\": 54321}", builder); + assertEquals(54321, builder.getOptionalInt32()); } } diff --git a/java/util/src/test/java/com/google/protobuf/util/TimeUtilTest.java b/java/util/src/test/java/com/google/protobuf/util/TimeUtilTest.java index fe5617e1..4c31b2b3 100644 --- a/java/util/src/test/java/com/google/protobuf/util/TimeUtilTest.java +++ b/java/util/src/test/java/com/google/protobuf/util/TimeUtilTest.java @@ -38,6 +38,8 @@ import junit.framework.TestCase; import org.junit.Assert; import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; /** Unit tests for {@link TimeUtil}. */ public class TimeUtilTest extends TestCase { @@ -76,6 +78,71 @@ public class TimeUtilTest extends TestCase { assertEquals("1970-01-01T08:00:00.010Z", TimeUtil.toString(value)); } + private volatile boolean stopParsingThreads = false; + private volatile String errorMessage = ""; + + private class ParseTimestampThread extends Thread { + private final String[] strings; + private final Timestamp[] values; + public ParseTimestampThread(String[] strings, Timestamp[] values) { + this.strings = strings; + this.values = values; + } + + @Override + public void run() { + int index = 0; + while (!stopParsingThreads) { + Timestamp result; + try { + result = TimeUtil.parseTimestamp(strings[index]); + } catch (ParseException e) { + errorMessage = "Failed to parse timestamp: " + strings[index]; + break; + } + if (result.getSeconds() != values[index].getSeconds() + || result.getNanos() != values[index].getNanos()) { + errorMessage = "Actual result: " + result.toString() + ", expected: " + + values[index].toString(); + break; + } + index = (index + 1) % strings.length; + } + } + } + + public void testTimestampConcurrentParsing() throws Exception { + String[] timestampStrings = new String[]{ + "0001-01-01T00:00:00Z", + "9999-12-31T23:59:59.999999999Z", + "1970-01-01T00:00:00Z", + "1969-12-31T23:59:59.999Z", + }; + Timestamp[] timestampValues = new Timestamp[timestampStrings.length]; + for (int i = 0; i < timestampStrings.length; i++) { + timestampValues[i] = TimeUtil.parseTimestamp(timestampStrings[i]); + } + + final int THREAD_COUNT = 16; + final int RUNNING_TIME = 5000; // in milliseconds. + final List<Thread> threads = new ArrayList<Thread>(); + + stopParsingThreads = false; + errorMessage = ""; + for (int i = 0; i < THREAD_COUNT; i++) { + Thread thread = new ParseTimestampThread( + timestampStrings, timestampValues); + thread.start(); + threads.add(thread); + } + Thread.sleep(RUNNING_TIME); + stopParsingThreads = true; + for (Thread thread : threads) { + thread.join(); + } + Assert.assertEquals("", errorMessage); + } + public void testTimetampInvalidFormat() throws Exception { try { // Value too small. diff --git a/java/util/src/test/java/com/google/protobuf/util/json_test.proto b/java/util/src/test/proto/com/google/protobuf/util/json_test.proto index b2753af6..686edc42 100644 --- a/java/util/src/test/java/com/google/protobuf/util/json_test.proto +++ b/java/util/src/test/proto/com/google/protobuf/util/json_test.proto @@ -28,6 +28,36 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// 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. + syntax = "proto3"; package json_test; @@ -90,6 +120,13 @@ message TestAllTypes { repeated NestedEnum repeated_nested_enum = 51; } +message TestOneof { + oneof oneof_field { + int32 oneof_int32 = 1; + TestAllTypes.NestedMessage oneof_nested_message = 2; + } +} + message TestMap { // Instead of testing all combinations (too many), we only make sure all // valid types have been used at least in one field as key and in one @@ -151,8 +188,14 @@ message TestFieldMask { message TestStruct { google.protobuf.Struct struct_value = 1; + google.protobuf.Value value = 2; + google.protobuf.ListValue list_value = 3; } message TestAny { google.protobuf.Any any_value = 1; } + +message TestCustomJsonName { + int32 value = 1 [json_name = "@value"]; +} |