diff options
Diffstat (limited to 'java/core/src')
4 files changed, 98 insertions, 1 deletions
diff --git a/java/core/src/main/java/com/google/protobuf/CodedOutputStream.java b/java/core/src/main/java/com/google/protobuf/CodedOutputStream.java index 576a350f..4f5a9b77 100644 --- a/java/core/src/main/java/com/google/protobuf/CodedOutputStream.java +++ b/java/core/src/main/java/com/google/protobuf/CodedOutputStream.java @@ -145,6 +145,44 @@ public abstract class CodedOutputStream extends ByteOutput { } /** + * Configures serialization to be deterministic. + * + * <p>The deterministic serialization guarantees that for a given binary, equal (defined by the + * {@code equals()} methods in protos) messages will always be serialized to the same bytes. This + * implies: + * + * <ul> + * <li>repeated serialization of a message will return the same bytes + * <li>different processes of the same binary (which may be executing on different machines) will + * serialize equal messages to the same bytes. + * </ul> + * + * <p>Note the deterministic serialization is NOT canonical across languages; it is also unstable + * across different builds with schema changes due to unknown fields. Users who need canonical + * serialization, e.g. persistent storage in a canonical form, fingerprinting, etc, should define + * their own canonicalization specification and implement the serializer using reflection APIs + * rather than relying on this API. + * + * <p> Once set, the serializer will: (Note this is an implementation detail and may subject to + * change in the future) + * + * <ul> + * <li> sort map entries by keys in lexicographical order or numerical order. Note: For string + * keys, the order is based on comparing the Unicode value of each character in the strings. + * The order may be different from the deterministic serialization in other languages where + * maps are sorted on the lexicographical order of the UTF8 encoded keys. + * </ul> + */ + public final void useDeterministicSerialization() { + serializationDeterministic = true; + } + + boolean isSerializationDeterministic() { + return serializationDeterministic; + } + private boolean serializationDeterministic; + + /** * 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 diff --git a/java/core/src/main/java/com/google/protobuf/DynamicMessage.java b/java/core/src/main/java/com/google/protobuf/DynamicMessage.java index 859a9e8f..c54da67f 100644 --- a/java/core/src/main/java/com/google/protobuf/DynamicMessage.java +++ b/java/core/src/main/java/com/google/protobuf/DynamicMessage.java @@ -526,6 +526,14 @@ public final class DynamicMessage extends AbstractMessage { fields.clearField(oldField); } oneofCases[index] = field; + } else if (field.getFile().getSyntax() == Descriptors.FileDescriptor.Syntax.PROTO3) { + if (!field.isRepeated() + && field.getJavaType() != FieldDescriptor.JavaType.MESSAGE + && value.equals(field.getDefaultValue())) { + // In proto3, setting a field to its default value is equivalent to clearing the field. + fields.clearField(field); + return this; + } } fields.setField(field, value); return this; diff --git a/java/core/src/test/java/com/google/protobuf/FieldPresenceTest.java b/java/core/src/test/java/com/google/protobuf/FieldPresenceTest.java index 82f4216b..4a42c897 100644 --- a/java/core/src/test/java/com/google/protobuf/FieldPresenceTest.java +++ b/java/core/src/test/java/com/google/protobuf/FieldPresenceTest.java @@ -31,6 +31,8 @@ package com.google.protobuf; import com.google.protobuf.Descriptors.Descriptor; +import com.google.protobuf.Descriptors.EnumDescriptor; +import com.google.protobuf.Descriptors.EnumValueDescriptor; import com.google.protobuf.Descriptors.FieldDescriptor; import com.google.protobuf.FieldPresenceTestProto.TestAllTypes; import com.google.protobuf.FieldPresenceTestProto.TestOptionalFieldsOnly; @@ -253,6 +255,54 @@ public class FieldPresenceTest extends TestCase { assertEquals(4, message.getAllFields().size()); } + public void testFieldPresenceDynamicMessage() { + Descriptor descriptor = TestAllTypes.getDescriptor(); + FieldDescriptor optionalInt32Field = descriptor.findFieldByName("optional_int32"); + FieldDescriptor optionalStringField = descriptor.findFieldByName("optional_string"); + FieldDescriptor optionalBytesField = descriptor.findFieldByName("optional_bytes"); + FieldDescriptor optionalNestedEnumField = descriptor.findFieldByName("optional_nested_enum"); + EnumDescriptor enumDescriptor = optionalNestedEnumField.getEnumType(); + EnumValueDescriptor defaultEnumValueDescriptor = enumDescriptor.getValues().get(0); + EnumValueDescriptor nonDefaultEnumValueDescriptor = enumDescriptor.getValues().get(1); + + DynamicMessage defaultInstance = DynamicMessage.getDefaultInstance(descriptor); + // Field not present. + DynamicMessage message = defaultInstance.newBuilderForType().build(); + assertFalse(message.hasField(optionalInt32Field)); + assertFalse(message.hasField(optionalStringField)); + assertFalse(message.hasField(optionalBytesField)); + assertFalse(message.hasField(optionalNestedEnumField)); + assertEquals(0, message.getAllFields().size()); + + // Field set to non-default value is seen as present. + message = + defaultInstance + .newBuilderForType() + .setField(optionalInt32Field, 1) + .setField(optionalStringField, "x") + .setField(optionalBytesField, ByteString.copyFromUtf8("y")) + .setField(optionalNestedEnumField, nonDefaultEnumValueDescriptor) + .build(); + assertTrue(message.hasField(optionalInt32Field)); + assertTrue(message.hasField(optionalStringField)); + assertTrue(message.hasField(optionalBytesField)); + assertTrue(message.hasField(optionalNestedEnumField)); + assertEquals(4, message.getAllFields().size()); + + // Field set to default value is seen as not present. + message = message.toBuilder() + .setField(optionalInt32Field, 0) + .setField(optionalStringField, "") + .setField(optionalBytesField, ByteString.EMPTY) + .setField(optionalNestedEnumField, defaultEnumValueDescriptor) + .build(); + assertFalse(message.hasField(optionalInt32Field)); + assertFalse(message.hasField(optionalStringField)); + assertFalse(message.hasField(optionalBytesField)); + assertFalse(message.hasField(optionalNestedEnumField)); + assertEquals(0, message.getAllFields().size()); + } + public void testMessageField() { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); assertFalse(builder.hasOptionalNestedMessage()); diff --git a/java/core/src/test/java/com/google/protobuf/LazyStringArrayListTest.java b/java/core/src/test/java/com/google/protobuf/LazyStringArrayListTest.java index 0f42ac50..497c4df2 100644 --- a/java/core/src/test/java/com/google/protobuf/LazyStringArrayListTest.java +++ b/java/core/src/test/java/com/google/protobuf/LazyStringArrayListTest.java @@ -35,6 +35,7 @@ import static java.util.Arrays.asList; import junit.framework.TestCase; import java.util.ArrayList; +import java.util.Collections; import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.List; @@ -233,7 +234,7 @@ public class LazyStringArrayListTest extends TestCase { } try { - list.addAllByteArray(asList(BYTE_STRING_A.toByteArray())); + list.addAllByteArray(Collections.singletonList(BYTE_STRING_A.toByteArray())); fail(); } catch (UnsupportedOperationException e) { // expected |