From 92a7e778e7394386f413cec28d67a07630f784b1 Mon Sep 17 00:00:00 2001 From: Adam Cozzette Date: Fri, 1 Dec 2017 10:05:10 -0800 Subject: Integrated internal changes from Google --- .../src/main/java/com/google/protobuf/Android.java | 57 ++++++++++++++ .../main/java/com/google/protobuf/ByteString.java | 10 +-- .../main/java/com/google/protobuf/FieldSet.java | 5 ++ .../com/google/protobuf/GeneratedMessageLite.java | 42 ++++++----- .../main/java/com/google/protobuf/TextFormat.java | 35 +++++++++ .../main/java/com/google/protobuf/UnsafeUtil.java | 9 --- .../com/google/protobuf/AbstractMessageTest.java | 12 ++- .../test/java/com/google/protobuf/TestUtil.java | 6 ++ .../java/com/google/protobuf/TextFormatTest.java | 88 +++++++++++++++++++--- .../java/com/google/protobuf/util/JsonFormat.java | 57 +++++++++++--- .../java/com/google/protobuf/util/Timestamps.java | 11 ++- .../com/google/protobuf/util/JsonFormatTest.java | 15 ++++ 12 files changed, 282 insertions(+), 65 deletions(-) create mode 100644 java/core/src/main/java/com/google/protobuf/Android.java (limited to 'java') diff --git a/java/core/src/main/java/com/google/protobuf/Android.java b/java/core/src/main/java/com/google/protobuf/Android.java new file mode 100644 index 00000000..cad54783 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/Android.java @@ -0,0 +1,57 @@ +// 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; + +final class Android { + + private static final Class MEMORY_CLASS = getClassForName("libcore.io.Memory"); + private static final boolean IS_ROBOLECTRIC = + getClassForName("org.robolectric.Robolectric") != null; + + /** Returns {@code true} if running on an Android device. */ + static boolean isOnAndroidDevice() { + return MEMORY_CLASS != null && !IS_ROBOLECTRIC; + } + + /** Returns the memory class or {@code null} if not on Android device. */ + static Class getMemoryClass() { + return MEMORY_CLASS; + } + + @SuppressWarnings("unchecked") + private static Class getClassForName(String name) { + try { + return (Class) Class.forName(name); + } catch (Throwable e) { + return null; + } + } +} diff --git a/java/core/src/main/java/com/google/protobuf/ByteString.java b/java/core/src/main/java/com/google/protobuf/ByteString.java index 99a31209..d67bb54a 100644 --- a/java/core/src/main/java/com/google/protobuf/ByteString.java +++ b/java/core/src/main/java/com/google/protobuf/ByteString.java @@ -124,14 +124,8 @@ public abstract class ByteString implements Iterable, Serializable { 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(); + byteArrayCopier = + Android.isOnAndroidDevice() ? new SystemByteArrayCopier() : new ArraysByteArrayCopier(); } /** diff --git a/java/core/src/main/java/com/google/protobuf/FieldSet.java b/java/core/src/main/java/com/google/protobuf/FieldSet.java index 8a9239ed..c09daa32 100644 --- a/java/core/src/main/java/com/google/protobuf/FieldSet.java +++ b/java/core/src/main/java/com/google/protobuf/FieldSet.java @@ -102,6 +102,11 @@ final class FieldSet implements ExtendableMessageOrBuilder { - /** - * Represents the set of extensions on this message. For use by generated - * code only. - */ - protected FieldSet extensions = FieldSet.newFieldSet(); + /** Represents the set of extensions on this message. For use by generated code only. */ + protected FieldSet extensions = FieldSet.emptySet(); @SuppressWarnings("unchecked") protected final void mergeExtensionFields(final MessageType other) { @@ -578,7 +575,11 @@ public abstract class GeneratedMessageLite< if (unknown) { // Unknown field or wrong wire type. Skip. return parseUnknownField(tag, input); } - + + if (extensions.isImmutable()) { + extensions = extensions.clone(); + } + if (packed) { int length = input.readRawVarint32(); int limit = input.pushLimit(length); @@ -942,12 +943,6 @@ public abstract class GeneratedMessageLite< implements ExtendableMessageOrBuilder { protected ExtendableBuilder(MessageType defaultInstance) { super(defaultInstance); - - // TODO(dweis): This is kind of an unnecessary clone since we construct a - // new instance in the parent constructor which makes the extensions - // immutable. This extra allocation shouldn't matter in practice - // though. - instance.extensions = instance.extensions.clone(); } // For immutable message conversion. @@ -966,6 +961,15 @@ public abstract class GeneratedMessageLite< instance.extensions = instance.extensions.clone(); } + private FieldSet ensureExtensionsAreMutable() { + FieldSet extensions = instance.extensions; + if (extensions.isImmutable()) { + extensions = extensions.clone(); + instance.extensions = extensions; + } + return extensions; + } + @Override public final MessageType buildPartial() { if (isBuilt) { @@ -1024,7 +1028,8 @@ public abstract class GeneratedMessageLite< verifyExtensionContainingType(extensionLite); copyOnWrite(); - instance.extensions.setField(extensionLite.descriptor, extensionLite.toFieldSetType(value)); + ensureExtensionsAreMutable() + .setField(extensionLite.descriptor, extensionLite.toFieldSetType(value)); return (BuilderType) this; } @@ -1037,8 +1042,9 @@ public abstract class GeneratedMessageLite< verifyExtensionContainingType(extensionLite); copyOnWrite(); - instance.extensions.setRepeatedField( - extensionLite.descriptor, index, extensionLite.singularToFieldSetType(value)); + ensureExtensionsAreMutable() + .setRepeatedField( + extensionLite.descriptor, index, extensionLite.singularToFieldSetType(value)); return (BuilderType) this; } @@ -1051,8 +1057,8 @@ public abstract class GeneratedMessageLite< verifyExtensionContainingType(extensionLite); copyOnWrite(); - instance.extensions.addRepeatedField( - extensionLite.descriptor, extensionLite.singularToFieldSetType(value)); + ensureExtensionsAreMutable() + .addRepeatedField(extensionLite.descriptor, extensionLite.singularToFieldSetType(value)); return (BuilderType) this; } @@ -1063,7 +1069,7 @@ public abstract class GeneratedMessageLite< verifyExtensionContainingType(extensionLite); copyOnWrite(); - instance.extensions.clearField(extensionLite.descriptor); + ensureExtensionsAreMutable().clearField(extensionLite.descriptor); return (BuilderType) this; } } diff --git a/java/core/src/main/java/com/google/protobuf/TextFormat.java b/java/core/src/main/java/com/google/protobuf/TextFormat.java index ab9acf2f..0c8b898c 100644 --- a/java/core/src/main/java/com/google/protobuf/TextFormat.java +++ b/java/core/src/main/java/com/google/protobuf/TextFormat.java @@ -1223,6 +1223,22 @@ public final class TextFormat { PARSER.merge(input, builder); } + /** + * Parse a text-format message from {@code input}. + * + * @return the parsed message, guaranteed initialized + */ + public static T parse(final CharSequence input, + final Class protoClass) + throws ParseException { + Message.Builder builder = + Internal.getDefaultInstance(protoClass).newBuilderForType(); + merge(input, builder); + @SuppressWarnings("unchecked") + T output = (T) builder.build(); + return output; + } + /** * Parse a text-format message from {@code input} and merge the contents * into {@code builder}. Extensions will be recognized if they are @@ -1248,6 +1264,25 @@ public final class TextFormat { PARSER.merge(input, extensionRegistry, builder); } + /** + * Parse a text-format message from {@code input}. Extensions will be + * recognized if they are registered in {@code extensionRegistry}. + * + * @return the parsed message, guaranteed initialized + */ + public static T parse( + final CharSequence input, + final ExtensionRegistry extensionRegistry, + final Class protoClass) + throws ParseException { + Message.Builder builder = + Internal.getDefaultInstance(protoClass).newBuilderForType(); + merge(input, extensionRegistry, builder); + @SuppressWarnings("unchecked") + T output = (T) builder.build(); + return output; + } + /** * Parser for text-format proto2 instances. This class is thread-safe. diff --git a/java/core/src/main/java/com/google/protobuf/UnsafeUtil.java b/java/core/src/main/java/com/google/protobuf/UnsafeUtil.java index 88315cb6..98c93b90 100644 --- a/java/core/src/main/java/com/google/protobuf/UnsafeUtil.java +++ b/java/core/src/main/java/com/google/protobuf/UnsafeUtil.java @@ -364,15 +364,6 @@ final class UnsafeUtil { } - @SuppressWarnings("unchecked") - private static Class getClassForName(String name) { - try { - return (Class) Class.forName(name); - } catch (Throwable e) { - return null; - } - } - /** Finds the address field within a direct {@link Buffer}. */ private static Field bufferAddressField() { return field(Buffer.class, "address"); diff --git a/java/core/src/test/java/com/google/protobuf/AbstractMessageTest.java b/java/core/src/test/java/com/google/protobuf/AbstractMessageTest.java index 622e36a4..cb2d34eb 100644 --- a/java/core/src/test/java/com/google/protobuf/AbstractMessageTest.java +++ b/java/core/src/test/java/com/google/protobuf/AbstractMessageTest.java @@ -30,6 +30,9 @@ package com.google.protobuf; +import static com.google.protobuf.TestUtil.TEST_REQUIRED_INITIALIZED; +import static com.google.protobuf.TestUtil.TEST_REQUIRED_UNINITIALIZED; + import com.google.protobuf.Descriptors.FieldDescriptor; import protobuf_unittest.UnittestOptimizeFor.TestOptimizedForSize; import protobuf_unittest.UnittestProto; @@ -346,11 +349,6 @@ public class AbstractMessageTest extends TestCase { // ----------------------------------------------------------------- // Tests for isInitialized(). - private static final TestRequired TEST_REQUIRED_UNINITIALIZED = - TestRequired.getDefaultInstance(); - private static final TestRequired TEST_REQUIRED_INITIALIZED = - TestRequired.newBuilder().setA(1).setB(2).setC(3).build(); - public void testIsInitialized() throws Exception { TestRequired.Builder builder = TestRequired.newBuilder(); AbstractMessageWrapper.Builder abstractBuilder = @@ -380,7 +378,7 @@ public class AbstractMessageTest extends TestCase { builder.setOptionalMessage(TEST_REQUIRED_UNINITIALIZED); assertFalse(abstractBuilder.isInitialized()); assertEquals( - "optional_message.a, optional_message.b, optional_message.c", + "optional_message.b, optional_message.c", abstractBuilder.getInitializationErrorString()); builder.setOptionalMessage(TEST_REQUIRED_INITIALIZED); @@ -390,7 +388,7 @@ public class AbstractMessageTest extends TestCase { builder.addRepeatedMessage(TEST_REQUIRED_UNINITIALIZED); assertFalse(abstractBuilder.isInitialized()); assertEquals( - "repeated_message[0].a, repeated_message[0].b, repeated_message[0].c", + "repeated_message[0].b, repeated_message[0].c", abstractBuilder.getInitializationErrorString()); builder.setRepeatedMessage(0, TEST_REQUIRED_INITIALIZED); diff --git a/java/core/src/test/java/com/google/protobuf/TestUtil.java b/java/core/src/test/java/com/google/protobuf/TestUtil.java index e96adf07..b4bc3a3d 100644 --- a/java/core/src/test/java/com/google/protobuf/TestUtil.java +++ b/java/core/src/test/java/com/google/protobuf/TestUtil.java @@ -231,6 +231,7 @@ import protobuf_unittest.UnittestProto.TestAllTypesOrBuilder; import protobuf_unittest.UnittestProto.TestOneof2; import protobuf_unittest.UnittestProto.TestPackedExtensions; import protobuf_unittest.UnittestProto.TestPackedTypes; +import protobuf_unittest.UnittestProto.TestRequired; import protobuf_unittest.UnittestProto.TestUnpackedTypes; import java.io.File; import java.io.IOException; @@ -252,6 +253,11 @@ import junit.framework.Assert; public final class TestUtil { private TestUtil() {} + public static final TestRequired TEST_REQUIRED_UNINITIALIZED = + TestRequired.newBuilder().setA(1).buildPartial(); + public static final TestRequired TEST_REQUIRED_INITIALIZED = + TestRequired.newBuilder().setA(1).setB(2).setC(3).build(); + /** Helper to convert a String to ByteString. */ static ByteString toBytes(String str) { return ByteString.copyFrom(str.getBytes(Internal.UTF_8)); diff --git a/java/core/src/test/java/com/google/protobuf/TextFormatTest.java b/java/core/src/test/java/com/google/protobuf/TextFormatTest.java index 28c4fdea..5d38ca51 100644 --- a/java/core/src/test/java/com/google/protobuf/TextFormatTest.java +++ b/java/core/src/test/java/com/google/protobuf/TextFormatTest.java @@ -30,6 +30,9 @@ package com.google.protobuf; +import static com.google.protobuf.TestUtil.TEST_REQUIRED_INITIALIZED; +import static com.google.protobuf.TestUtil.TEST_REQUIRED_UNINITIALIZED; + import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.FieldDescriptor; import com.google.protobuf.TextFormat.Parser.SingularOverwritePolicy; @@ -42,6 +45,7 @@ import protobuf_unittest.UnittestProto.TestAllTypes; import protobuf_unittest.UnittestProto.TestAllTypes.NestedMessage; import protobuf_unittest.UnittestProto.TestEmptyMessage; import protobuf_unittest.UnittestProto.TestOneof2; +import protobuf_unittest.UnittestProto.TestRequired; import proto2_wireformat_unittest.UnittestMsetWireFormat.TestMessageSet; import java.io.StringReader; import java.util.List; @@ -326,19 +330,58 @@ public class TextFormatTest extends TestCase { // ================================================================= - public void testParse() throws Exception { + public void testMerge() throws Exception { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); TextFormat.merge(allFieldsSetText, builder); TestUtil.assertAllFieldsSet(builder.build()); } - public void testParseReader() throws Exception { + public void testParse() throws Exception { + TestUtil.assertAllFieldsSet( + TextFormat.parse(allFieldsSetText, TestAllTypes.class)); + } + + public void testMergeInitialized() throws Exception { + TestRequired.Builder builder = TestRequired.newBuilder(); + TextFormat.merge(TEST_REQUIRED_INITIALIZED.toString(), builder); + assertEquals(TEST_REQUIRED_INITIALIZED.toString(), + builder.buildPartial().toString()); + assertTrue(builder.isInitialized()); + } + + public void testParseInitialized() throws Exception { + TestRequired parsed = + TextFormat.parse(TEST_REQUIRED_INITIALIZED.toString(), + TestRequired.class); + assertEquals(TEST_REQUIRED_INITIALIZED.toString(), parsed.toString()); + assertTrue(parsed.isInitialized()); + } + + public void testMergeUninitialized() throws Exception { + TestRequired.Builder builder = TestRequired.newBuilder(); + TextFormat.merge(TEST_REQUIRED_UNINITIALIZED.toString(), builder); + assertEquals(TEST_REQUIRED_UNINITIALIZED.toString(), + builder.buildPartial().toString()); + assertFalse(builder.isInitialized()); + } + + public void testParseUninitialized() throws Exception { + try { + TextFormat.parse(TEST_REQUIRED_UNINITIALIZED.toString(), + TestRequired.class); + fail("Expected UninitializedMessageException."); + } catch (UninitializedMessageException e) { + assertEquals("Message missing required fields: b, c", e.getMessage()); + } + } + + public void testMergeReader() throws Exception { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); TextFormat.merge(new StringReader(allFieldsSetText), builder); TestUtil.assertAllFieldsSet(builder.build()); } - public void testParseExtensions() throws Exception { + public void testMergeExtensions() throws Exception { TestAllExtensions.Builder builder = TestAllExtensions.newBuilder(); TextFormat.merge(allExtensionsSetText, TestUtil.getExtensionRegistry(), @@ -346,7 +389,14 @@ public class TextFormatTest extends TestCase { TestUtil.assertAllExtensionsSet(builder.build()); } - public void testParseCompatibility() throws Exception { + public void testParseExtensions() throws Exception { + TestUtil.assertAllExtensionsSet( + TextFormat.parse(allExtensionsSetText, + TestUtil.getExtensionRegistry(), + TestAllExtensions.class)); + } + + public void testMergeAndParseCompatibility() throws Exception { String original = "repeated_float: inf\n" + "repeated_float: -inf\n" + "repeated_float: nan\n" + @@ -371,21 +421,29 @@ public class TextFormatTest extends TestCase { "repeated_double: Infinity\n" + "repeated_double: -Infinity\n" + "repeated_double: NaN\n"; + + // Test merge(). TestAllTypes.Builder builder = TestAllTypes.newBuilder(); TextFormat.merge(original, builder); assertEquals(canonical, builder.build().toString()); + + // Test parse(). + assertEquals(canonical, + TextFormat.parse(original, TestAllTypes.class).toString()); } - public void testParseExotic() throws Exception { + public void testMergeAndParseExotic() throws Exception { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); TextFormat.merge(exoticText, builder); // Too lazy to check things individually. Don't try to debug this // if testPrintExotic() is failing. assertEquals(canonicalExoticText, builder.build().toString()); + assertEquals(canonicalExoticText, + TextFormat.parse(exoticText, TestAllTypes.class).toString()); } - public void testParseMessageSet() throws Exception { + public void testMergeMessageSet() throws Exception { ExtensionRegistry extensionRegistry = ExtensionRegistry.newInstance(); extensionRegistry.add(TestMessageSetExtension1.messageSetExtension); extensionRegistry.add(TestMessageSetExtension2.messageSetExtension); @@ -411,7 +469,7 @@ public class TextFormatTest extends TestCase { TestMessageSetExtension1.messageSetExtension).getI()); } - public void testParseMessageSetWithOverwriteForbidden() throws Exception { + public void testMergeMessageSetWithOverwriteForbidden() throws Exception { ExtensionRegistry extensionRegistry = ExtensionRegistry.newInstance(); extensionRegistry.add(TestMessageSetExtension1.messageSetExtension); extensionRegistry.add(TestMessageSetExtension2.messageSetExtension); @@ -438,20 +496,20 @@ public class TextFormatTest extends TestCase { } } - public void testParseNumericEnum() throws Exception { + public void testMergeNumericEnum() throws Exception { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); TextFormat.merge("optional_nested_enum: 2", builder); assertEquals(TestAllTypes.NestedEnum.BAR, builder.getOptionalNestedEnum()); } - public void testParseAngleBrackets() throws Exception { + public void testMergeAngleBrackets() throws Exception { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); TextFormat.merge("OptionalGroup: < a: 1 >", builder); assertTrue(builder.hasOptionalGroup()); assertEquals(1, builder.getOptionalGroup().getA()); } - public void testParseComment() throws Exception { + public void testMergeComment() throws Exception { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); TextFormat.merge( "# this is a comment\n" + @@ -463,6 +521,7 @@ public class TextFormatTest extends TestCase { } private void assertParseError(String error, String text) { + // Test merge(). TestAllTypes.Builder builder = TestAllTypes.newBuilder(); try { TextFormat.merge(text, TestUtil.getExtensionRegistry(), builder); @@ -470,6 +529,15 @@ public class TextFormatTest extends TestCase { } catch (TextFormat.ParseException e) { assertEquals(error, e.getMessage()); } + + // Test parse(). + try { + TextFormat.parse( + text, TestUtil.getExtensionRegistry(), TestAllTypes.class); + fail("Expected parse exception."); + } catch (TextFormat.ParseException e) { + assertEquals(error, e.getMessage()); + } } 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 a26dbc2d..7f69ee68 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 @@ -105,7 +105,7 @@ public class JsonFormat { public static Printer printer() { return new Printer( TypeRegistry.getEmptyTypeRegistry(), false, Collections.emptySet(), - false, false); + false, false, false); } /** @@ -125,18 +125,21 @@ public class JsonFormat { private Set includingDefaultValueFields; private final boolean preservingProtoFieldNames; private final boolean omittingInsignificantWhitespace; + private final boolean printingEnumsAsInts; private Printer( TypeRegistry registry, boolean alwaysOutputDefaultValueFields, Set includingDefaultValueFields, boolean preservingProtoFieldNames, - boolean omittingInsignificantWhitespace) { + boolean omittingInsignificantWhitespace, + boolean printingEnumsAsInts) { this.registry = registry; this.alwaysOutputDefaultValueFields = alwaysOutputDefaultValueFields; this.includingDefaultValueFields = includingDefaultValueFields; this.preservingProtoFieldNames = preservingProtoFieldNames; this.omittingInsignificantWhitespace = omittingInsignificantWhitespace; + this.printingEnumsAsInts = printingEnumsAsInts; } /** @@ -154,7 +157,8 @@ public class JsonFormat { alwaysOutputDefaultValueFields, includingDefaultValueFields, preservingProtoFieldNames, - omittingInsignificantWhitespace); + omittingInsignificantWhitespace, + printingEnumsAsInts); } /** @@ -170,7 +174,31 @@ public class JsonFormat { true, Collections.emptySet(), preservingProtoFieldNames, - omittingInsignificantWhitespace); + omittingInsignificantWhitespace, + printingEnumsAsInts); + } + + /** + * Creates a new {@link Printer} that will print enum field values as integers instead of as + * string. + * The new Printer clones all other configurations from the current + * {@link Printer}. + */ + public Printer printingEnumsAsInts() { + checkUnsetPrintingEnumsAsInts(); + return new Printer( + registry, + alwaysOutputDefaultValueFields, + Collections.emptySet(), + preservingProtoFieldNames, + omittingInsignificantWhitespace, + true); + } + + private void checkUnsetPrintingEnumsAsInts() { + if (printingEnumsAsInts) { + throw new IllegalStateException("JsonFormat printingEnumsAsInts has already been set."); + } } /** @@ -191,7 +219,8 @@ public class JsonFormat { false, fieldsToAlwaysOutput, preservingProtoFieldNames, - omittingInsignificantWhitespace); + omittingInsignificantWhitespace, + printingEnumsAsInts); } private void checkUnsetIncludingDefaultValueFields() { @@ -213,7 +242,8 @@ public class JsonFormat { alwaysOutputDefaultValueFields, includingDefaultValueFields, true, - omittingInsignificantWhitespace); + omittingInsignificantWhitespace, + printingEnumsAsInts); } @@ -240,7 +270,8 @@ public class JsonFormat { alwaysOutputDefaultValueFields, includingDefaultValueFields, preservingProtoFieldNames, - true); + true, + printingEnumsAsInts); } /** @@ -259,7 +290,8 @@ public class JsonFormat { includingDefaultValueFields, preservingProtoFieldNames, output, - omittingInsignificantWhitespace) + omittingInsignificantWhitespace, + printingEnumsAsInts) .print(message); } @@ -416,7 +448,7 @@ public class JsonFormat { */ public Builder add(Iterable messageTypes) { if (types == null) { - throw new IllegalStateException("A TypeRegistry.Builer can only be used once."); + throw new IllegalStateException("A TypeRegistry.Builder can only be used once."); } for (Descriptor type : messageTypes) { addFile(type.getFile()); @@ -570,6 +602,7 @@ public class JsonFormat { private final boolean alwaysOutputDefaultValueFields; private final Set includingDefaultValueFields; private final boolean preservingProtoFieldNames; + private final boolean printingEnumsAsInts; private final TextGenerator generator; // We use Gson to help handle string escapes. private final Gson gson; @@ -586,11 +619,13 @@ public class JsonFormat { Set includingDefaultValueFields, boolean preservingProtoFieldNames, Appendable jsonOutput, - boolean omittingInsignificantWhitespace) { + boolean omittingInsignificantWhitespace, + boolean printingEnumsAsInts) { this.registry = registry; this.alwaysOutputDefaultValueFields = alwaysOutputDefaultValueFields; this.includingDefaultValueFields = includingDefaultValueFields; this.preservingProtoFieldNames = preservingProtoFieldNames; + this.printingEnumsAsInts = printingEnumsAsInts; this.gson = GsonHolder.DEFAULT_GSON; // json format related properties, determined by printerType if (omittingInsignificantWhitespace) { @@ -1069,7 +1104,7 @@ public class JsonFormat { generator.print("\""); } } else { - if (((EnumValueDescriptor) value).getIndex() == -1) { + if (printingEnumsAsInts || ((EnumValueDescriptor) value).getIndex() == -1) { generator.print(String.valueOf(((EnumValueDescriptor) value).getNumber())); } else { generator.print("\"" + ((EnumValueDescriptor) value).getName() + "\""); diff --git a/java/util/src/main/java/com/google/protobuf/util/Timestamps.java b/java/util/src/main/java/com/google/protobuf/util/Timestamps.java index 7a1f2014..9e528d4a 100644 --- a/java/util/src/main/java/com/google/protobuf/util/Timestamps.java +++ b/java/util/src/main/java/com/google/protobuf/util/Timestamps.java @@ -74,6 +74,11 @@ public final class Timestamps { public static final Timestamp MAX_VALUE = Timestamp.newBuilder().setSeconds(TIMESTAMP_SECONDS_MAX).setNanos(999999999).build(); + /** + * A constant holding the {@link Timestamp} of epoch time, {@code 1970-01-01T00:00:00.000000000Z}. + */ + public static final Timestamp EPOCH = Timestamp.newBuilder().setSeconds(0).setNanos(0).build(); + private static final ThreadLocal timestampFormat = new ThreadLocal() { @Override @@ -358,10 +363,12 @@ public final class Timestamps { static Timestamp normalizedTimestamp(long seconds, int nanos) { if (nanos <= -NANOS_PER_SECOND || nanos >= NANOS_PER_SECOND) { seconds = checkedAdd(seconds, nanos / NANOS_PER_SECOND); - nanos %= NANOS_PER_SECOND; + nanos = (int) (nanos % NANOS_PER_SECOND); } if (nanos < 0) { - nanos += NANOS_PER_SECOND; // no overflow since nanos is negative (and we're adding) + nanos = + (int) + (nanos + NANOS_PER_SECOND); // no overflow since nanos is negative (and we're adding) seconds = checkedSubtract(seconds, 1); } Timestamp timestamp = Timestamp.newBuilder().setSeconds(seconds).setNanos(nanos).build(); 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 e4c5387a..6ef08508 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 @@ -1174,6 +1174,14 @@ public class JsonFormatTest extends TestCase { JsonFormat.parser().ignoringUnknownFields().merge(json, builder); } + public void testParserIntegerEnumValue() throws Exception { + TestAllTypes.Builder actualBuilder = TestAllTypes.newBuilder(); + mergeFromJson("{\n" + " \"optionalNestedEnum\": 2\n" + "}", actualBuilder); + + TestAllTypes expected = TestAllTypes.newBuilder().setOptionalNestedEnum(NestedEnum.BAZ).build(); + assertEquals(expected, actualBuilder.build()); + } + public void testCustomJsonName() throws Exception { TestCustomJsonName message = TestCustomJsonName.newBuilder().setValue(12345).build(); assertEquals("{\n" + " \"@value\": 12345\n" + "}", JsonFormat.printer().print(message)); @@ -1441,6 +1449,13 @@ public class JsonFormatTest extends TestCase { assertEquals(54321, builder.getOptionalInt32()); } + public void testPrintingEnumsAsInts() throws Exception { + TestAllTypes message = TestAllTypes.newBuilder().setOptionalNestedEnum(NestedEnum.BAR).build(); + assertEquals( + "{\n" + " \"optionalNestedEnum\": 1\n" + "}", + JsonFormat.printer().printingEnumsAsInts().print(message)); + } + public void testOmittingInsignificantWhiteSpace() throws Exception { TestAllTypes message = TestAllTypes.newBuilder().setOptionalInt32(12345).build(); assertEquals( -- cgit v1.2.3