From d64a2d9941c36a7bc2a7959ea10ab8363192ac14 Mon Sep 17 00:00:00 2001 From: Adam Cozzette Date: Wed, 29 Jun 2016 15:23:27 -0700 Subject: Integrated internal changes from Google This includes all internal changes from around May 20 to now. --- .../java/com/google/protobuf/util/Durations.java | 256 +++++++ .../com/google/protobuf/util/FieldMaskTree.java | 48 +- .../com/google/protobuf/util/FieldMaskUtil.java | 78 ++- .../java/com/google/protobuf/util/JsonFormat.java | 715 +++++++++----------- .../java/com/google/protobuf/util/TimeUtil.java | 381 ++++------- .../java/com/google/protobuf/util/Timestamps.java | 349 ++++++++++ .../google/protobuf/util/FieldMaskUtilTest.java | 113 ++-- .../com/google/protobuf/util/JsonFormatTest.java | 733 ++++++++++----------- .../com/google/protobuf/util/TimeUtilTest.java | 77 +-- .../proto/com/google/protobuf/util/json_test.proto | 1 + 10 files changed, 1602 insertions(+), 1149 deletions(-) create mode 100644 java/util/src/main/java/com/google/protobuf/util/Durations.java create mode 100644 java/util/src/main/java/com/google/protobuf/util/Timestamps.java (limited to 'java/util/src') diff --git a/java/util/src/main/java/com/google/protobuf/util/Durations.java b/java/util/src/main/java/com/google/protobuf/util/Durations.java new file mode 100644 index 00000000..5fe6ebca --- /dev/null +++ b/java/util/src/main/java/com/google/protobuf/util/Durations.java @@ -0,0 +1,256 @@ +// 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.util; + +import static com.google.protobuf.util.Timestamps.MICROS_PER_SECOND; +import static com.google.protobuf.util.Timestamps.MILLIS_PER_SECOND; +import static com.google.protobuf.util.Timestamps.NANOS_PER_MICROSECOND; +import static com.google.protobuf.util.Timestamps.NANOS_PER_MILLISECOND; +import static com.google.protobuf.util.Timestamps.NANOS_PER_SECOND; + +import com.google.protobuf.Duration; + +import java.text.ParseException; + +/** + * Utilities to help create/manipulate {@code protobuf/duration.proto}. + */ +public final class Durations { + static final long DURATION_SECONDS_MIN = -315576000000L; + static final long DURATION_SECONDS_MAX = 315576000000L; + + // TODO(kak): Do we want to expose Duration constants for MAX/MIN? + + private Durations() {} + + /** + * Returns true if the given {@link Duration} is valid. The {@code seconds} value must be in the + * range [-315,576,000,000, +315,576,000,000]. The {@code nanos} value must be in the range + * [-999,999,999, +999,999,999]. + * + *

Note: Durations less than one second are represented with a 0 {@code seconds} field and a + * positive or negative {@code nanos} field. For durations of one second or more, a non-zero value + * for the {@code nanos} field must be of the same sign as the {@code seconds} field. + */ + public static boolean isValid(Duration duration) { + return isValid(duration.getSeconds(), duration.getNanos()); + } + + /** + * Returns true if the given number of seconds and nanos is a valid {@link Duration}. The + * {@code seconds} value must be in the range [-315,576,000,000, +315,576,000,000]. The + * {@code nanos} value must be in the range [-999,999,999, +999,999,999]. + * + *

Note: Durations less than one second are represented with a 0 {@code seconds} field and a + * positive or negative {@code nanos} field. For durations of one second or more, a non-zero value + * for the {@code nanos} field must be of the same sign as the {@code seconds} field. + */ + public static boolean isValid(long seconds, long nanos) { + if (seconds < DURATION_SECONDS_MIN || seconds > DURATION_SECONDS_MAX) { + return false; + } + if (nanos < -999999999L || nanos >= NANOS_PER_SECOND) { + return false; + } + if (seconds < 0 || nanos < 0) { + if (seconds > 0 || nanos > 0) { + return false; + } + } + return true; + } + + /** + * Throws an {@link IllegalArgumentException} if the given seconds/nanos are not + * a valid {@link Duration}. + */ + private static void checkValid(long seconds, int nanos) { + if (!isValid(seconds, nanos)) { + throw new IllegalArgumentException(String.format( + "Duration is not valid. See proto definition for valid values. " + + "Seconds (%s) must be in range [-315,576,000,000, +315,576,000,000]." + + "Nanos (%s) must be in range [-999,999,999, +999,999,999]. " + + "Nanos must have the same sign as seconds", seconds, nanos)); + } + } + + /** + * Convert Duration to string format. The string format will contains 3, 6, + * or 9 fractional digits depending on the precision required to represent + * the exact Duration value. For example: "1s", "1.010s", "1.000000100s", + * "-3.100s" The range that can be represented by Duration is from + * -315,576,000,000 to +315,576,000,000 inclusive (in seconds). + * + * @return The string representation of the given duration. + * @throws IllegalArgumentException if the given duration is not in the valid + * range. + */ + public static String toString(Duration duration) { + long seconds = duration.getSeconds(); + int nanos = duration.getNanos(); + checkValid(seconds, nanos); + + StringBuilder result = new StringBuilder(); + if (seconds < 0 || nanos < 0) { + result.append("-"); + seconds = -seconds; + nanos = -nanos; + } + result.append(seconds); + if (nanos != 0) { + result.append("."); + result.append(Timestamps.formatNanos(nanos)); + } + result.append("s"); + return result.toString(); + } + + /** + * Parse from a string to produce a duration. + * + * @return A Duration parsed from the string. + * @throws ParseException if parsing fails. + */ + public static Duration parse(String value) throws ParseException { + // Must ended with "s". + if (value.isEmpty() || value.charAt(value.length() - 1) != 's') { + throw new ParseException("Invalid duration string: " + value, 0); + } + boolean negative = false; + if (value.charAt(0) == '-') { + negative = true; + value = value.substring(1); + } + String secondValue = value.substring(0, value.length() - 1); + String nanoValue = ""; + int pointPosition = secondValue.indexOf('.'); + if (pointPosition != -1) { + nanoValue = secondValue.substring(pointPosition + 1); + secondValue = secondValue.substring(0, pointPosition); + } + long seconds = Long.parseLong(secondValue); + int nanos = nanoValue.isEmpty() ? 0 : Timestamps.parseNanos(nanoValue); + if (seconds < 0) { + throw new ParseException("Invalid duration string: " + value, 0); + } + if (negative) { + seconds = -seconds; + nanos = -nanos; + } + try { + return normalizedDuration(seconds, nanos); + } catch (IllegalArgumentException e) { + throw new ParseException("Duration value is out of range.", 0); + } + } + + /** + * Create a Duration from the number of milliseconds. + */ + public static Duration fromMillis(long milliseconds) { + return normalizedDuration( + milliseconds / MILLIS_PER_SECOND, + (int) (milliseconds % MILLIS_PER_SECOND * NANOS_PER_MILLISECOND)); + } + + /** + * Convert a Duration to the number of milliseconds.The result will be + * rounded towards 0 to the nearest millisecond. E.g., if the duration + * represents -1 nanosecond, it will be rounded to 0. + */ + public static long toMillis(Duration duration) { + return duration.getSeconds() * MILLIS_PER_SECOND + duration.getNanos() / NANOS_PER_MILLISECOND; + } + + /** + * Create a Duration from the number of microseconds. + */ + public static Duration fromMicros(long microseconds) { + return normalizedDuration( + microseconds / MICROS_PER_SECOND, + (int) (microseconds % MICROS_PER_SECOND * NANOS_PER_MICROSECOND)); + } + + /** + * Convert a Duration to the number of microseconds.The result will be + * rounded towards 0 to the nearest microseconds. E.g., if the duration + * represents -1 nanosecond, it will be rounded to 0. + */ + public static long toMicros(Duration duration) { + return duration.getSeconds() * MICROS_PER_SECOND + duration.getNanos() / NANOS_PER_MICROSECOND; + } + + /** + * Create a Duration from the number of nanoseconds. + */ + public static Duration fromNanos(long nanoseconds) { + return normalizedDuration( + nanoseconds / NANOS_PER_SECOND, (int) (nanoseconds % NANOS_PER_SECOND)); + } + + /** + * Convert a Duration to the number of nanoseconds. + */ + public static long toNanos(Duration duration) { + return duration.getSeconds() * NANOS_PER_SECOND + duration.getNanos(); + } + + /** + * Add two durations. + */ + public static Duration add(Duration d1, Duration d2) { + return normalizedDuration(d1.getSeconds() + d2.getSeconds(), d1.getNanos() + d2.getNanos()); + } + + /** + * Subtract a duration from another. + */ + public static Duration subtract(Duration d1, Duration d2) { + return normalizedDuration(d1.getSeconds() - d2.getSeconds(), d1.getNanos() - d2.getNanos()); + } + + static Duration normalizedDuration(long seconds, int nanos) { + if (nanos <= -NANOS_PER_SECOND || nanos >= NANOS_PER_SECOND) { + seconds += nanos / NANOS_PER_SECOND; + nanos %= NANOS_PER_SECOND; + } + if (seconds > 0 && nanos < 0) { + nanos += NANOS_PER_SECOND; + seconds -= 1; + } + if (seconds < 0 && nanos > 0) { + nanos -= NANOS_PER_SECOND; + seconds += 1; + } + checkValid(seconds, nanos); + return Duration.newBuilder().setSeconds(seconds).setNanos(nanos).build(); + } +} 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 668d65ab..b577495d 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 @@ -38,6 +38,7 @@ import com.google.protobuf.Message; import java.util.ArrayList; import java.util.List; import java.util.Map.Entry; +import java.util.SortedMap; import java.util.TreeMap; import java.util.logging.Logger; @@ -59,22 +60,26 @@ import java.util.logging.Logger; * intersection to two FieldMasks and traverse all fields specified by the * FieldMask in a message tree. */ -class FieldMaskTree { +final class FieldMaskTree { private static final Logger logger = Logger.getLogger(FieldMaskTree.class.getName()); private static final String FIELD_PATH_SEPARATOR_REGEX = "\\."; - private static class Node { - public TreeMap children = new TreeMap(); + private static final class Node { + final SortedMap children = new TreeMap(); } private final Node root = new Node(); - /** Creates an empty FieldMaskTree. */ - public FieldMaskTree() {} + /** + * Creates an empty FieldMaskTree. + */ + FieldMaskTree() {} - /** Creates a FieldMaskTree for a given FieldMask. */ - public FieldMaskTree(FieldMask mask) { + /** + * Creates a FieldMaskTree for a given FieldMask. + */ + FieldMaskTree(FieldMask mask) { mergeFromFieldMask(mask); } @@ -93,7 +98,7 @@ class FieldMaskTree { * Likewise, if the field path to add is a sub-path of an existing leaf node, * nothing will be changed in the tree. */ - public FieldMaskTree addFieldPath(String path) { + FieldMaskTree addFieldPath(String path) { String[] parts = path.split(FIELD_PATH_SEPARATOR_REGEX); if (parts.length == 0) { return this; @@ -124,15 +129,17 @@ class FieldMaskTree { /** * Merges all field paths in a FieldMask into this tree. */ - public FieldMaskTree mergeFromFieldMask(FieldMask mask) { + FieldMaskTree mergeFromFieldMask(FieldMask mask) { for (String path : mask.getPathsList()) { addFieldPath(path); } return this; } - /** Converts this tree to a FieldMask. */ - public FieldMask toFieldMask() { + /** + * Converts this tree to a FieldMask. + */ + FieldMask toFieldMask() { if (root.children.isEmpty()) { return FieldMask.getDefaultInstance(); } @@ -141,7 +148,9 @@ class FieldMaskTree { return FieldMask.newBuilder().addAllPaths(paths).build(); } - /** Gathers all field paths in a sub-tree. */ + /** + * Gathers all field paths in a sub-tree. + */ private void getFieldPaths(Node node, String path, List paths) { if (node.children.isEmpty()) { paths.add(path); @@ -154,10 +163,9 @@ class FieldMaskTree { } /** - * Adds the intersection of this tree with the given {@code path} to - * {@code output}. + * Adds the intersection of this tree with the given {@code path} to {@code output}. */ - public void intersectFieldPath(String path, FieldMaskTree output) { + void intersectFieldPath(String path, FieldMaskTree output) { if (root.children.isEmpty()) { return; } @@ -188,11 +196,9 @@ class FieldMaskTree { } /** - * Merges all fields specified by this FieldMaskTree from {@code source} to - * {@code destination}. + * Merges all fields specified by this FieldMaskTree from {@code source} to {@code destination}. */ - public void merge( - Message source, Message.Builder destination, FieldMaskUtil.MergeOptions options) { + void merge(Message source, Message.Builder destination, FieldMaskUtil.MergeOptions options) { if (source.getDescriptorForType() != destination.getDescriptorForType()) { throw new IllegalArgumentException("Cannot merge messages of different types."); } @@ -202,8 +208,8 @@ class FieldMaskTree { merge(root, "", source, destination, options); } - /** Merges all fields specified by a sub-tree from {@code source} to - * {@code destination}. + /** + * Merges all fields specified by a sub-tree from {@code source} to {@code destination}. */ private void merge( Node node, 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 96961521..21d11b2c 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 @@ -32,6 +32,9 @@ package com.google.protobuf.util; import static com.google.common.base.Preconditions.checkArgument; +import com.google.common.base.CaseFormat; +import com.google.common.base.Joiner; +import com.google.common.base.Splitter; import com.google.common.primitives.Ints; import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.FieldDescriptor; @@ -39,7 +42,9 @@ import com.google.protobuf.FieldMask; import com.google.protobuf.Internal; import com.google.protobuf.Message; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; /** * Utility helper functions to work with {@link com.google.protobuf.FieldMask}. @@ -48,7 +53,7 @@ public class FieldMaskUtil { private static final String FIELD_PATH_SEPARATOR = ","; private static final String FIELD_PATH_SEPARATOR_REGEX = ","; private static final String FIELD_SEPARATOR_REGEX = "\\."; - + private FieldMaskUtil() {} /** @@ -78,19 +83,17 @@ public class FieldMaskUtil { */ 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))); + return fromStringList(null, Arrays.asList(value.split(FIELD_PATH_SEPARATOR_REGEX))); } /** * Parses from a string to a FieldMask and validates all field paths. - * + * * @throws IllegalArgumentException if any of the field path is invalid. */ public static FieldMask fromString(Class 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))); + return fromStringList(type, Arrays.asList(value.split(FIELD_PATH_SEPARATOR_REGEX))); } /** @@ -99,8 +102,7 @@ public class FieldMaskUtil { * @throws IllegalArgumentException if any of the field path is not valid. */ // TODO(xiaofeng): Consider renaming fromStrings() - public static FieldMask fromStringList( - Class type, Iterable paths) { + public static FieldMask fromStringList(Class type, Iterable paths) { FieldMask.Builder builder = FieldMask.newBuilder(); for (String path : paths) { if (path.isEmpty()) { @@ -108,8 +110,7 @@ public class FieldMaskUtil { continue; } if (type != null && !isValid(type, path)) { - throw new IllegalArgumentException( - path + " is not a valid path for " + type); + throw new IllegalArgumentException(path + " is not a valid path for " + type); } builder.addPaths(path); } @@ -145,16 +146,46 @@ public class FieldMaskUtil { return builder.build(); } + /** + * Converts a field mask to a Proto3 JSON string, that is converting from snake case to camel + * case and joining all paths into one string with commas. + */ + public static String toJsonString(FieldMask fieldMask) { + List paths = new ArrayList(fieldMask.getPathsCount()); + for (String path : fieldMask.getPathsList()) { + if (path.isEmpty()) { + continue; + } + paths.add(CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, path)); + } + return Joiner.on(FIELD_PATH_SEPARATOR).join(paths); + } + + /** + * Converts a field mask from a Proto3 JSON string, that is splitting the paths along commas and + * converting from camel case to snake case. + */ + public static FieldMask fromJsonString(String value) { + Iterable paths = Splitter.on(FIELD_PATH_SEPARATOR).split(value); + FieldMask.Builder builder = FieldMask.newBuilder(); + for (String path : paths) { + if (path.isEmpty()) { + continue; + } + builder.addPaths(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, path)); + } + return builder.build(); + } + /** * Checks whether paths in a given fields mask are valid. */ public static boolean isValid(Class type, FieldMask fieldMask) { - Descriptor descriptor = - Internal.getDefaultInstance(type).getDescriptorForType(); - + Descriptor descriptor = Internal.getDefaultInstance(type).getDescriptorForType(); + return isValid(descriptor, fieldMask); } - + /** * Checks whether paths in a given fields mask are valid. */ @@ -171,9 +202,8 @@ public class FieldMaskUtil { * Checks whether a given field path is valid. */ public static boolean isValid(Class type, String path) { - Descriptor descriptor = - Internal.getDefaultInstance(type).getDescriptorForType(); - + Descriptor descriptor = Internal.getDefaultInstance(type).getDescriptorForType(); + return isValid(descriptor, path); } @@ -193,8 +223,7 @@ public class FieldMaskUtil { if (field == null) { return false; } - if (!field.isRepeated() - && field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + if (!field.isRepeated() && field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { descriptor = field.getMessageType(); } else { descriptor = null; @@ -202,7 +231,7 @@ public class FieldMaskUtil { } return true; } - + /** * Converts a FieldMask to its canonical form. In the canonical form of a * FieldMask, all field paths are sorted alphabetically and redundant field @@ -251,7 +280,7 @@ public class FieldMaskUtil { * destination message fields) when merging. * Default behavior is to merge the source message field into the * destination message field. - */ + */ public boolean replaceMessageFields() { return replaceMessageFields; } @@ -299,16 +328,15 @@ public class FieldMaskUtil { * 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) { + public static void merge( + FieldMask mask, Message source, Message.Builder destination, MergeOptions options) { new FieldMaskTree(mask).merge(source, destination, options); } /** * Merges fields specified by a FieldMask from one message to another. */ - public static void merge(FieldMask mask, Message source, - Message.Builder destination) { + public static void merge(FieldMask mask, Message source, Message.Builder destination) { merge(mask, source, destination, new MergeOptions()); } } 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 76f3437a..bf70834a 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 @@ -60,6 +60,7 @@ import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.ListValue; import com.google.protobuf.Message; import com.google.protobuf.MessageOrBuilder; +import com.google.protobuf.NullValue; import com.google.protobuf.StringValue; import com.google.protobuf.Struct; import com.google.protobuf.Timestamp; @@ -92,18 +93,17 @@ import java.util.logging.Logger; * as well. */ public class JsonFormat { - private static final Logger logger = - Logger.getLogger(JsonFormat.class.getName()); + private static final Logger logger = Logger.getLogger(JsonFormat.class.getName()); private JsonFormat() {} - + /** * Creates a {@link Printer} with default configurations. */ public static Printer printer() { return new Printer(TypeRegistry.getEmptyTypeRegistry(), false, false); } - + /** * A Printer converts protobuf message to JSON format. */ @@ -120,11 +120,11 @@ public class JsonFormat { this.includingDefaultValueFields = includingDefaultValueFields; this.preservingProtoFieldNames = preservingProtoFieldNames; } - + /** * Creates a new {@link Printer} using the given registry. The new Printer * clones all other configurations from the current {@link Printer}. - * + * * @throws IllegalArgumentException if a registry is already set. */ public Printer usingTypeRegistry(TypeRegistry registry) { @@ -153,16 +153,15 @@ public class JsonFormat { public Printer preservingProtoFieldNames() { return new Printer(registry, includingDefaultValueFields, true); } - + /** * Converts a protobuf message to JSON format. - * + * * @throws InvalidProtocolBufferException if the message contains Any types * that can't be resolved. * @throws IOException if writing to the output fails. */ - public void appendTo(MessageOrBuilder message, Appendable output) - throws IOException { + public void appendTo(MessageOrBuilder message, Appendable output) throws IOException { // TODO(xiaofeng): Investigate the allocation overhead and optimize for // mobile. new PrinterImpl(registry, includingDefaultValueFields, preservingProtoFieldNames, output) @@ -171,10 +170,9 @@ public class JsonFormat { /** * Converts a protobuf message to JSON format. Throws exceptions if there - * are unknown Any types in the message. + * are unknown Any types in the message. */ - public String print(MessageOrBuilder message) - throws InvalidProtocolBufferException { + public String print(MessageOrBuilder message) throws InvalidProtocolBufferException { try { StringBuilder builder = new StringBuilder(); appendTo(message, builder); @@ -194,21 +192,21 @@ public class JsonFormat { public static Parser parser() { return new Parser(TypeRegistry.getEmptyTypeRegistry()); } - + /** * A Parser parses JSON to protobuf message. */ public static class Parser { private final TypeRegistry registry; - + private Parser(TypeRegistry registry) { - this.registry = registry; + this.registry = registry; } - + /** * Creates a new {@link Parser} using the given registry. The new Parser * clones all other configurations from this Parser. - * + * * @throws IllegalArgumentException if a registry is already set. */ public Parser usingTypeRegistry(TypeRegistry registry) { @@ -217,29 +215,27 @@ public class JsonFormat { } return new Parser(registry); } - + /** * Parses from JSON into a protobuf message. - * + * * @throws InvalidProtocolBufferException if the input is not valid JSON * format or there are unknown fields in the input. */ - public void merge(String json, Message.Builder builder) - throws InvalidProtocolBufferException { + public void merge(String json, Message.Builder builder) throws InvalidProtocolBufferException { // TODO(xiaofeng): Investigate the allocation overhead and optimize for // mobile. new ParserImpl(registry).merge(json, builder); } - + /** * Parses from JSON into a protobuf message. - * + * * @throws InvalidProtocolBufferException if the input is not valid JSON * format or there are unknown fields in the input. * @throws IOException if reading from the input throws. */ - public void merge(Reader json, Message.Builder builder) - throws IOException { + public void merge(Reader json, Message.Builder builder) throws IOException { // TODO(xiaofeng): Investigate the allocation overhead and optimize for // mobile. new ParserImpl(registry).merge(json, builder); @@ -255,8 +251,8 @@ public class JsonFormat { */ public static class TypeRegistry { private static class EmptyTypeRegistryHolder { - private static final TypeRegistry EMPTY = new TypeRegistry( - Collections.emptyMap()); + private static final TypeRegistry EMPTY = + new TypeRegistry(Collections.emptyMap()); } public static TypeRegistry getEmptyTypeRegistry() { @@ -293,8 +289,7 @@ public class JsonFormat { */ public Builder add(Descriptor messageType) { if (types == null) { - throw new IllegalStateException( - "A TypeRegistry.Builer can only be used once."); + throw new IllegalStateException("A TypeRegistry.Builer can only be used once."); } addFile(messageType.getFile()); return this; @@ -306,8 +301,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.Builer can only be used once."); } for (Descriptor type : messageTypes) { addFile(type.getFile()); @@ -345,8 +339,7 @@ public class JsonFormat { } if (types.containsKey(message.getFullName())) { - logger.warning("Type " + message.getFullName() - + " is added multiple times."); + logger.warning("Type " + message.getFullName() + " is added multiple times."); return; } @@ -354,8 +347,7 @@ public class JsonFormat { } private final Set files = new HashSet(); - private Map types = - new HashMap(); + private Map types = new HashMap(); } } @@ -387,8 +379,7 @@ public class JsonFormat { public void outdent() { final int length = indent.length(); if (length < 2) { - throw new IllegalArgumentException( - " Outdent() without matching Indent()."); + throw new IllegalArgumentException(" Outdent() without matching Indent()."); } indent.delete(length - 2, length); } @@ -450,45 +441,41 @@ public class JsonFormat { } void print(MessageOrBuilder message) throws IOException { - WellKnownTypePrinter specialPrinter = wellKnownTypePrinters.get( - message.getDescriptorForType().getFullName()); + WellKnownTypePrinter specialPrinter = + wellKnownTypePrinters.get(message.getDescriptorForType().getFullName()); if (specialPrinter != null) { specialPrinter.print(this, message); return; } print(message, null); } - + private interface WellKnownTypePrinter { - void print(PrinterImpl printer, MessageOrBuilder message) - throws IOException; - } - - private static final Map - wellKnownTypePrinters = buildWellKnownTypePrinters(); - - private static Map - buildWellKnownTypePrinters() { - Map printers = - new HashMap(); + void print(PrinterImpl printer, MessageOrBuilder message) throws IOException; + } + + private static final Map wellKnownTypePrinters = + buildWellKnownTypePrinters(); + + private static Map buildWellKnownTypePrinters() { + Map printers = new HashMap(); // Special-case Any. - printers.put(Any.getDescriptor().getFullName(), + printers.put( + Any.getDescriptor().getFullName(), new WellKnownTypePrinter() { - @Override - public void print(PrinterImpl printer, MessageOrBuilder message) - throws IOException { - printer.printAny(message); - } - }); + @Override + public void print(PrinterImpl printer, MessageOrBuilder message) throws IOException { + printer.printAny(message); + } + }); // Special-case wrapper types. - WellKnownTypePrinter wrappersPrinter = new WellKnownTypePrinter() { - @Override - public void print(PrinterImpl printer, MessageOrBuilder message) - throws IOException { - printer.printWrapper(message); - - } - }; + WellKnownTypePrinter wrappersPrinter = + new WellKnownTypePrinter() { + @Override + public void print(PrinterImpl printer, MessageOrBuilder message) throws IOException { + printer.printWrapper(message); + } + }; printers.put(BoolValue.getDescriptor().getFullName(), wrappersPrinter); printers.put(Int32Value.getDescriptor().getFullName(), wrappersPrinter); printers.put(UInt32Value.getDescriptor().getFullName(), wrappersPrinter); @@ -499,70 +486,71 @@ public class JsonFormat { printers.put(FloatValue.getDescriptor().getFullName(), wrappersPrinter); printers.put(DoubleValue.getDescriptor().getFullName(), wrappersPrinter); // Special-case Timestamp. - printers.put(Timestamp.getDescriptor().getFullName(), + printers.put( + Timestamp.getDescriptor().getFullName(), new WellKnownTypePrinter() { - @Override - public void print(PrinterImpl printer, MessageOrBuilder message) - throws IOException { - printer.printTimestamp(message); - } - }); + @Override + public void print(PrinterImpl printer, MessageOrBuilder message) throws IOException { + printer.printTimestamp(message); + } + }); // Special-case Duration. - printers.put(Duration.getDescriptor().getFullName(), + printers.put( + Duration.getDescriptor().getFullName(), new WellKnownTypePrinter() { - @Override - public void print(PrinterImpl printer, MessageOrBuilder message) - throws IOException { - printer.printDuration(message); - } - }); + @Override + public void print(PrinterImpl printer, MessageOrBuilder message) throws IOException { + printer.printDuration(message); + } + }); // Special-case FieldMask. - printers.put(FieldMask.getDescriptor().getFullName(), + printers.put( + FieldMask.getDescriptor().getFullName(), new WellKnownTypePrinter() { - @Override - public void print(PrinterImpl printer, MessageOrBuilder message) - throws IOException { - printer.printFieldMask(message); - } - }); + @Override + public void print(PrinterImpl printer, MessageOrBuilder message) throws IOException { + printer.printFieldMask(message); + } + }); // Special-case Struct. - printers.put(Struct.getDescriptor().getFullName(), + printers.put( + Struct.getDescriptor().getFullName(), new WellKnownTypePrinter() { - @Override - public void print(PrinterImpl printer, MessageOrBuilder message) - throws IOException { - printer.printStruct(message); - } - }); + @Override + public void print(PrinterImpl printer, MessageOrBuilder message) throws IOException { + printer.printStruct(message); + } + }); // Special-case Value. - printers.put(Value.getDescriptor().getFullName(), + printers.put( + Value.getDescriptor().getFullName(), new WellKnownTypePrinter() { - @Override - public void print(PrinterImpl printer, MessageOrBuilder message) - throws IOException { - printer.printValue(message); - } - }); + @Override + public void print(PrinterImpl printer, MessageOrBuilder message) throws IOException { + printer.printValue(message); + } + }); // Special-case ListValue. - printers.put(ListValue.getDescriptor().getFullName(), + printers.put( + ListValue.getDescriptor().getFullName(), new WellKnownTypePrinter() { - @Override - public void print(PrinterImpl printer, MessageOrBuilder message) - throws IOException { - printer.printListValue(message); - } - }); + @Override + public void print(PrinterImpl printer, MessageOrBuilder message) throws IOException { + printer.printListValue(message); + } + }); return printers; } - + /** Prints google.protobuf.Any */ private void printAny(MessageOrBuilder message) throws IOException { Descriptor descriptor = message.getDescriptorForType(); FieldDescriptor typeUrlField = descriptor.findFieldByName("type_url"); FieldDescriptor valueField = descriptor.findFieldByName("value"); // Validates type of the message. Note that we can't just cast the message - // to com.google.protobuf.Any because it might be a DynamicMessage. - if (typeUrlField == null || valueField == null + // to com.google.protobuf.Any because it might be a DynamicMessage. + if (typeUrlField == null + || valueField == null || typeUrlField.getType() != FieldDescriptor.Type.STRING || valueField.getType() != FieldDescriptor.Type.BYTES) { throw new InvalidProtocolBufferException("Invalid Any type."); @@ -571,12 +559,11 @@ public class JsonFormat { String typeName = getTypeName(typeUrl); Descriptor type = registry.find(typeName); if (type == null) { - throw new InvalidProtocolBufferException( - "Cannot find type for url: " + typeUrl); + throw new InvalidProtocolBufferException("Cannot find type for url: " + typeUrl); } ByteString content = (ByteString) message.getField(valueField); - Message contentMessage = DynamicMessage.getDefaultInstance(type) - .getParserForType().parseFrom(content); + Message contentMessage = + DynamicMessage.getDefaultInstance(type).getParserForType().parseFrom(content); WellKnownTypePrinter printer = wellKnownTypePrinters.get(typeName); if (printer != null) { // If the type is one of the well-known types, we use a special @@ -594,7 +581,7 @@ public class JsonFormat { print(contentMessage, typeUrl); } } - + /** Prints wrapper types (e.g., google.protobuf.Int32Value) */ private void printWrapper(MessageOrBuilder message) throws IOException { Descriptor descriptor = message.getDescriptorForType(); @@ -606,7 +593,7 @@ public class JsonFormat { // the whole message. printSingleFieldValue(valueField, message.getField(valueField)); } - + private ByteString toByteString(MessageOrBuilder message) { if (message instanceof Message) { return ((Message) message).toByteString(); @@ -614,26 +601,25 @@ public class JsonFormat { return ((Message.Builder) message).build().toByteString(); } } - + /** Prints google.protobuf.Timestamp */ private void printTimestamp(MessageOrBuilder message) throws IOException { Timestamp value = Timestamp.parseFrom(toByteString(message)); - generator.print("\"" + TimeUtil.toString(value) + "\""); + generator.print("\"" + Timestamps.toString(value) + "\""); } - + /** Prints google.protobuf.Duration */ private void printDuration(MessageOrBuilder message) throws IOException { Duration value = Duration.parseFrom(toByteString(message)); - generator.print("\"" + TimeUtil.toString(value) + "\""); - + generator.print("\"" + Durations.toString(value) + "\""); } - + /** Prints google.protobuf.FieldMask */ private void printFieldMask(MessageOrBuilder message) throws IOException { FieldMask value = FieldMask.parseFrom(toByteString(message)); - generator.print("\"" + FieldMaskUtil.toString(value) + "\""); + generator.print("\"" + FieldMaskUtil.toJsonString(value) + "\""); } - + /** Prints google.protobuf.Struct */ private void printStruct(MessageOrBuilder message) throws IOException { Descriptor descriptor = message.getDescriptorForType(); @@ -644,7 +630,7 @@ public class JsonFormat { // Struct is formatted as a map object. printMapFieldValue(field, message.getField(field)); } - + /** Prints google.protobuf.Value */ private void printValue(MessageOrBuilder message) throws IOException { // For a Value message, only the value of the field is formatted. @@ -663,7 +649,7 @@ public class JsonFormat { printSingleFieldValue(entry.getKey(), entry.getValue()); } } - + /** Prints google.protobuf.ListValue */ private void printListValue(MessageOrBuilder message) throws IOException { Descriptor descriptor = message.getDescriptorForType(); @@ -675,8 +661,7 @@ public class JsonFormat { } /** Prints a regular message with an optional type URL. */ - private void print(MessageOrBuilder message, String typeUrl) - throws IOException { + private void print(MessageOrBuilder message, String typeUrl) throws IOException { generator.print("{\n"); generator.indent(); @@ -710,7 +695,7 @@ public class JsonFormat { } printField(field.getKey(), field.getValue()); } - + // Add line-endings for the last field. if (printedField) { generator.print("\n"); @@ -719,8 +704,7 @@ public class JsonFormat { generator.print("}"); } - private void printField(FieldDescriptor field, Object value) - throws IOException { + private void printField(FieldDescriptor field, Object value) throws IOException { if (preservingProtoFieldNames) { generator.print("\"" + field.getName() + "\": "); } else { @@ -734,10 +718,9 @@ public class JsonFormat { printSingleFieldValue(field, value); } } - + @SuppressWarnings("rawtypes") - private void printRepeatedFieldValue(FieldDescriptor field, Object value) - throws IOException { + private void printRepeatedFieldValue(FieldDescriptor field, Object value) throws IOException { generator.print("["); boolean printedElement = false; for (Object element : (List) value) { @@ -750,10 +733,9 @@ public class JsonFormat { } generator.print("]"); } - + @SuppressWarnings("rawtypes") - private void printMapFieldValue(FieldDescriptor field, Object value) - throws IOException { + private void printMapFieldValue(FieldDescriptor field, Object value) throws IOException { Descriptor type = field.getMessageType(); FieldDescriptor keyField = type.findFieldByName("key"); FieldDescriptor valueField = type.findFieldByName("value"); @@ -783,21 +765,20 @@ public class JsonFormat { generator.outdent(); generator.print("}"); } - - private void printSingleFieldValue(FieldDescriptor field, Object value) - throws IOException { + + private void printSingleFieldValue(FieldDescriptor field, Object value) throws IOException { printSingleFieldValue(field, value, false); } /** * Prints a field's value in JSON format. - * + * * @param alwaysWithQuotes whether to always add double-quotes to primitive * types. */ private void printSingleFieldValue( - final FieldDescriptor field, final Object value, - boolean alwaysWithQuotes) throws IOException { + final FieldDescriptor field, final Object value, boolean alwaysWithQuotes) + throws IOException { switch (field.getType()) { case INT32: case SINT32: @@ -851,7 +832,7 @@ public class JsonFormat { } } break; - + case DOUBLE: Double doubleValue = (Double) value; if (doubleValue.isNaN()) { @@ -895,15 +876,13 @@ public class JsonFormat { case BYTES: generator.print("\""); - generator.print( - BaseEncoding.base64().encode(((ByteString) value).toByteArray())); + generator.print(BaseEncoding.base64().encode(((ByteString) value).toByteArray())); generator.print("\""); break; case ENUM: // Special-case google.protobuf.NullValue (it's an Enum). - if (field.getEnumType().getFullName().equals( - "google.protobuf.NullValue")) { + if (field.getEnumType().getFullName().equals("google.protobuf.NullValue")) { // No matter what value it contains, we always print it as "null". if (alwaysWithQuotes) { generator.print("\""); @@ -914,11 +893,9 @@ public class JsonFormat { } } else { if (((EnumValueDescriptor) value).getIndex() == -1) { - generator.print( - String.valueOf(((EnumValueDescriptor) value).getNumber())); + generator.print(String.valueOf(((EnumValueDescriptor) value).getNumber())); } else { - generator.print( - "\"" + ((EnumValueDescriptor) value).getName() + "\""); + generator.print("\"" + ((EnumValueDescriptor) value).getName() + "\""); } } break; @@ -947,40 +924,34 @@ public class JsonFormat { } else { // Pull off the most-significant bit so that BigInteger doesn't think // the number is negative, then set it again using setBit(). - return BigInteger.valueOf(value & Long.MAX_VALUE) - .setBit(Long.SIZE - 1).toString(); + return BigInteger.valueOf(value & Long.MAX_VALUE).setBit(Long.SIZE - 1).toString(); } } - - private static String getTypeName(String typeUrl) - throws InvalidProtocolBufferException { + private static String getTypeName(String typeUrl) throws InvalidProtocolBufferException { String[] parts = typeUrl.split("/"); if (parts.length == 1) { - throw new InvalidProtocolBufferException( - "Invalid type url found: " + typeUrl); + throw new InvalidProtocolBufferException("Invalid type url found: " + typeUrl); } return parts[parts.length - 1]; } - + private static class ParserImpl { private final TypeRegistry registry; private final JsonParser jsonParser; - + ParserImpl(TypeRegistry registry) { this.registry = registry; this.jsonParser = new JsonParser(); } - - void merge(Reader json, Message.Builder builder) - throws IOException { + + void merge(Reader json, Message.Builder builder) throws IOException { JsonReader reader = new JsonReader(json); reader.setLenient(false); merge(jsonParser.parse(reader), builder); } - - void merge(String json, Message.Builder builder) - throws InvalidProtocolBufferException { + + void merge(String json, Message.Builder builder) throws InvalidProtocolBufferException { try { JsonReader reader = new JsonReader(new StringReader(json)); reader.setLenient(false); @@ -992,35 +963,36 @@ public class JsonFormat { throw new InvalidProtocolBufferException(e.getMessage()); } } - + private interface WellKnownTypeParser { void merge(ParserImpl parser, JsonElement json, Message.Builder builder) throws InvalidProtocolBufferException; } - + private static final Map wellKnownTypeParsers = buildWellKnownTypeParsers(); - - private static Map - buildWellKnownTypeParsers() { - Map parsers = - new HashMap(); + + private static Map buildWellKnownTypeParsers() { + Map parsers = new HashMap(); // Special-case Any. - parsers.put(Any.getDescriptor().getFullName(), new WellKnownTypeParser() { - @Override - public void merge(ParserImpl parser, JsonElement json, - Message.Builder builder) throws InvalidProtocolBufferException { - parser.mergeAny(json, builder); - } - }); + parsers.put( + Any.getDescriptor().getFullName(), + new WellKnownTypeParser() { + @Override + public void merge(ParserImpl parser, JsonElement json, Message.Builder builder) + throws InvalidProtocolBufferException { + parser.mergeAny(json, builder); + } + }); // Special-case wrapper types. - WellKnownTypeParser wrappersPrinter = new WellKnownTypeParser() { - @Override - public void merge(ParserImpl parser, JsonElement json, - Message.Builder builder) throws InvalidProtocolBufferException { - parser.mergeWrapper(json, builder); - } - }; + WellKnownTypeParser wrappersPrinter = + new WellKnownTypeParser() { + @Override + public void merge(ParserImpl parser, JsonElement json, Message.Builder builder) + throws InvalidProtocolBufferException { + parser.mergeWrapper(json, builder); + } + }; parsers.put(BoolValue.getDescriptor().getFullName(), wrappersPrinter); parsers.put(Int32Value.getDescriptor().getFullName(), wrappersPrinter); parsers.put(UInt32Value.getDescriptor().getFullName(), wrappersPrinter); @@ -1031,82 +1003,86 @@ public class JsonFormat { parsers.put(FloatValue.getDescriptor().getFullName(), wrappersPrinter); parsers.put(DoubleValue.getDescriptor().getFullName(), wrappersPrinter); // Special-case Timestamp. - parsers.put(Timestamp.getDescriptor().getFullName(), + parsers.put( + Timestamp.getDescriptor().getFullName(), new WellKnownTypeParser() { - @Override - public void merge(ParserImpl parser, JsonElement json, - Message.Builder builder) throws InvalidProtocolBufferException { - parser.mergeTimestamp(json, builder); - } - }); + @Override + public void merge(ParserImpl parser, JsonElement json, Message.Builder builder) + throws InvalidProtocolBufferException { + parser.mergeTimestamp(json, builder); + } + }); // Special-case Duration. - parsers.put(Duration.getDescriptor().getFullName(), + parsers.put( + Duration.getDescriptor().getFullName(), new WellKnownTypeParser() { - @Override - public void merge(ParserImpl parser, JsonElement json, - Message.Builder builder) throws InvalidProtocolBufferException { - parser.mergeDuration(json, builder); - } - }); + @Override + public void merge(ParserImpl parser, JsonElement json, Message.Builder builder) + throws InvalidProtocolBufferException { + parser.mergeDuration(json, builder); + } + }); // Special-case FieldMask. - parsers.put(FieldMask.getDescriptor().getFullName(), + parsers.put( + FieldMask.getDescriptor().getFullName(), new WellKnownTypeParser() { - @Override - public void merge(ParserImpl parser, JsonElement json, - Message.Builder builder) throws InvalidProtocolBufferException { - parser.mergeFieldMask(json, builder); - } - }); + @Override + public void merge(ParserImpl parser, JsonElement json, Message.Builder builder) + throws InvalidProtocolBufferException { + parser.mergeFieldMask(json, builder); + } + }); // Special-case Struct. - parsers.put(Struct.getDescriptor().getFullName(), + parsers.put( + Struct.getDescriptor().getFullName(), new WellKnownTypeParser() { - @Override - public void merge(ParserImpl parser, JsonElement json, - Message.Builder builder) throws InvalidProtocolBufferException { - parser.mergeStruct(json, builder); - } - }); + @Override + public void merge(ParserImpl parser, JsonElement json, Message.Builder builder) + throws InvalidProtocolBufferException { + parser.mergeStruct(json, builder); + } + }); // Special-case ListValue. - parsers.put(ListValue.getDescriptor().getFullName(), + parsers.put( + ListValue.getDescriptor().getFullName(), new WellKnownTypeParser() { - @Override - public void merge(ParserImpl parser, JsonElement json, - Message.Builder builder) throws InvalidProtocolBufferException { - parser.mergeListValue(json, builder); - } - }); + @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(), + parsers.put( + Value.getDescriptor().getFullName(), new WellKnownTypeParser() { - @Override - public void merge(ParserImpl parser, JsonElement json, - Message.Builder builder) throws InvalidProtocolBufferException { - parser.mergeValue(json, builder); - } - }); + @Override + public void merge(ParserImpl parser, JsonElement json, Message.Builder builder) + throws InvalidProtocolBufferException { + parser.mergeValue(json, builder); + } + }); return parsers; } - + private void merge(JsonElement json, Message.Builder builder) throws InvalidProtocolBufferException { - WellKnownTypeParser specialParser = wellKnownTypeParsers.get( - builder.getDescriptorForType().getFullName()); + WellKnownTypeParser specialParser = + wellKnownTypeParsers.get(builder.getDescriptorForType().getFullName()); if (specialParser != null) { specialParser.merge(this, json, builder); return; } mergeMessage(json, builder, false); } - + // Maps from camel-case field names to FieldDescriptor. private final Map> fieldNameMaps = new HashMap>(); - - private Map getFieldNameMap( - Descriptor descriptor) { + + private Map getFieldNameMap(Descriptor descriptor) { if (!fieldNameMaps.containsKey(descriptor)) { - Map fieldNameMap = - new HashMap(); + Map fieldNameMap = new HashMap(); for (FieldDescriptor field : descriptor.getFields()) { fieldNameMap.put(field.getName(), field); fieldNameMap.put(field.getJsonName(), field); @@ -1116,16 +1092,14 @@ public class JsonFormat { } return fieldNameMaps.get(descriptor); } - - private void mergeMessage(JsonElement json, Message.Builder builder, - boolean skipTypeUrl) throws InvalidProtocolBufferException { + + private void mergeMessage(JsonElement json, Message.Builder builder, boolean skipTypeUrl) + throws InvalidProtocolBufferException { if (!(json instanceof JsonObject)) { - throw new InvalidProtocolBufferException( - "Expect message object but got: " + json); + throw new InvalidProtocolBufferException("Expect message object but got: " + json); } JsonObject object = (JsonObject) json; - Map fieldNameMap = - getFieldNameMap(builder.getDescriptorForType()); + Map fieldNameMap = getFieldNameMap(builder.getDescriptorForType()); for (Map.Entry entry : object.entrySet()) { if (skipTypeUrl && entry.getKey().equals("@type")) { continue; @@ -1133,47 +1107,46 @@ public class JsonFormat { FieldDescriptor field = fieldNameMap.get(entry.getKey()); if (field == null) { throw new InvalidProtocolBufferException( - "Cannot find field: " + entry.getKey() + " in message " - + builder.getDescriptorForType().getFullName()); + "Cannot find field: " + + entry.getKey() + + " in message " + + builder.getDescriptorForType().getFullName()); } mergeField(field, entry.getValue(), builder); } } - + private void mergeAny(JsonElement json, Message.Builder builder) throws InvalidProtocolBufferException { Descriptor descriptor = builder.getDescriptorForType(); FieldDescriptor typeUrlField = descriptor.findFieldByName("type_url"); FieldDescriptor valueField = descriptor.findFieldByName("value"); // Validates type of the message. Note that we can't just cast the message - // to com.google.protobuf.Any because it might be a DynamicMessage. - if (typeUrlField == null || valueField == null + // to com.google.protobuf.Any because it might be a DynamicMessage. + if (typeUrlField == null + || valueField == null || typeUrlField.getType() != FieldDescriptor.Type.STRING || valueField.getType() != FieldDescriptor.Type.BYTES) { throw new InvalidProtocolBufferException("Invalid Any type."); } - + if (!(json instanceof JsonObject)) { - throw new InvalidProtocolBufferException( - "Expect message object but got: " + json); + throw new InvalidProtocolBufferException("Expect message object but got: " + json); } JsonObject object = (JsonObject) json; JsonElement typeUrlElement = object.get("@type"); if (typeUrlElement == null) { - throw new InvalidProtocolBufferException( - "Missing type url when parsing: " + json); + throw new InvalidProtocolBufferException("Missing type url when parsing: " + json); } String typeUrl = typeUrlElement.getAsString(); Descriptor contentType = registry.find(getTypeName(typeUrl)); if (contentType == null) { - throw new InvalidProtocolBufferException( - "Cannot resolve type: " + typeUrl); + throw new InvalidProtocolBufferException("Cannot resolve type: " + typeUrl); } builder.setField(typeUrlField, typeUrl); Message.Builder contentBuilder = DynamicMessage.getDefaultInstance(contentType).newBuilderForType(); - WellKnownTypeParser specialParser = - wellKnownTypeParsers.get(contentType.getFullName()); + WellKnownTypeParser specialParser = wellKnownTypeParsers.get(contentType.getFullName()); if (specialParser != null) { JsonElement value = object.get("value"); if (value != null) { @@ -1184,35 +1157,33 @@ public class JsonFormat { } builder.setField(valueField, contentBuilder.build().toByteString()); } - + private void mergeFieldMask(JsonElement json, Message.Builder builder) throws InvalidProtocolBufferException { - FieldMask value = FieldMaskUtil.fromString(json.getAsString()); + FieldMask value = FieldMaskUtil.fromJsonString(json.getAsString()); builder.mergeFrom(value.toByteString()); } - + private void mergeTimestamp(JsonElement json, Message.Builder builder) throws InvalidProtocolBufferException { try { - Timestamp value = TimeUtil.parseTimestamp(json.getAsString()); + Timestamp value = Timestamps.parse(json.getAsString()); builder.mergeFrom(value.toByteString()); } catch (ParseException e) { - throw new InvalidProtocolBufferException( - "Failed to parse timestamp: " + json); + throw new InvalidProtocolBufferException("Failed to parse timestamp: " + json); } } - + private void mergeDuration(JsonElement json, Message.Builder builder) throws InvalidProtocolBufferException { try { - Duration value = TimeUtil.parseDuration(json.getAsString()); + Duration value = Durations.parse(json.getAsString()); builder.mergeFrom(value.toByteString()); } catch (ParseException e) { - throw new InvalidProtocolBufferException( - "Failed to parse duration: " + json); + throw new InvalidProtocolBufferException("Failed to parse duration: " + json); } } - + private void mergeStruct(JsonElement json, Message.Builder builder) throws InvalidProtocolBufferException { Descriptor descriptor = builder.getDescriptorForType(); @@ -1232,21 +1203,18 @@ public class JsonFormat { } mergeRepeatedField(field, json, builder); } - + private void mergeValue(JsonElement json, Message.Builder builder) throws InvalidProtocolBufferException { Descriptor type = builder.getDescriptorForType(); if (json instanceof JsonPrimitive) { JsonPrimitive primitive = (JsonPrimitive) json; if (primitive.isBoolean()) { - builder.setField(type.findFieldByName("bool_value"), - primitive.getAsBoolean()); + builder.setField(type.findFieldByName("bool_value"), primitive.getAsBoolean()); } else if (primitive.isNumber()) { - builder.setField(type.findFieldByName("number_value"), - primitive.getAsDouble()); + builder.setField(type.findFieldByName("number_value"), primitive.getAsDouble()); } else { - builder.setField(type.findFieldByName("string_value"), - primitive.getAsString()); + builder.setField(type.findFieldByName("string_value"), primitive.getAsString()); } } else if (json instanceof JsonObject) { FieldDescriptor field = type.findFieldByName("struct_value"); @@ -1262,20 +1230,19 @@ public class JsonFormat { throw new IllegalStateException("Unexpected json data: " + json); } } - + private void mergeWrapper(JsonElement json, Message.Builder builder) throws InvalidProtocolBufferException { Descriptor type = builder.getDescriptorForType(); FieldDescriptor field = type.findFieldByName("value"); if (field == null) { - throw new InvalidProtocolBufferException( - "Invalid wrapper type: " + type.getFullName()); + throw new InvalidProtocolBufferException("Invalid wrapper type: " + type.getFullName()); } builder.setField(field, parseFieldValue(field, json, builder)); } - - private void mergeField(FieldDescriptor field, JsonElement json, - Message.Builder builder) throws InvalidProtocolBufferException { + + private void mergeField(FieldDescriptor field, JsonElement json, Message.Builder builder) + throws InvalidProtocolBufferException { if (field.isRepeated()) { if (builder.getRepeatedFieldCount(field) > 0) { throw new InvalidProtocolBufferException( @@ -1290,8 +1257,11 @@ public class JsonFormat { && 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 "); + "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) { @@ -1310,44 +1280,38 @@ public class JsonFormat { } } } - - private void mergeMapField(FieldDescriptor field, JsonElement json, - Message.Builder builder) throws InvalidProtocolBufferException { + + private void mergeMapField(FieldDescriptor field, JsonElement json, Message.Builder builder) + throws InvalidProtocolBufferException { if (!(json instanceof JsonObject)) { - throw new InvalidProtocolBufferException( - "Expect a map object but found: " + json); + throw new InvalidProtocolBufferException("Expect a map object but found: " + json); } Descriptor type = field.getMessageType(); FieldDescriptor keyField = type.findFieldByName("key"); FieldDescriptor valueField = type.findFieldByName("value"); if (keyField == null || valueField == null) { - throw new InvalidProtocolBufferException( - "Invalid map field: " + field.getFullName()); + throw new InvalidProtocolBufferException("Invalid map field: " + field.getFullName()); } JsonObject object = (JsonObject) json; for (Map.Entry entry : object.entrySet()) { Message.Builder entryBuilder = builder.newBuilderForField(field); - Object key = parseFieldValue( - keyField, new JsonPrimitive(entry.getKey()), entryBuilder); - Object value = parseFieldValue( - valueField, entry.getValue(), entryBuilder); + Object key = parseFieldValue(keyField, new JsonPrimitive(entry.getKey()), entryBuilder); + Object value = parseFieldValue(valueField, entry.getValue(), entryBuilder); if (value == null) { - throw new InvalidProtocolBufferException( - "Map value cannot be null."); + throw new InvalidProtocolBufferException("Map value cannot be null."); } entryBuilder.setField(keyField, key); entryBuilder.setField(valueField, value); builder.addRepeatedField(field, entryBuilder.build()); } } - + /** * Gets the default value for a field type. Note that we use proto3 * language defaults and ignore any default values set through the - * proto "default" option. + * proto "default" option. */ - private Object getDefaultValue(FieldDescriptor field, - Message.Builder builder) { + private Object getDefaultValue(FieldDescriptor field, Message.Builder builder) { switch (field.getType()) { case INT32: case SINT32: @@ -1377,30 +1341,27 @@ public class JsonFormat { case GROUP: return builder.newBuilderForField(field).getDefaultInstanceForType(); default: - throw new IllegalStateException( - "Invalid field type: " + field.getType()); + throw new IllegalStateException("Invalid field type: " + field.getType()); } } - - private void mergeRepeatedField(FieldDescriptor field, JsonElement json, - Message.Builder builder) throws InvalidProtocolBufferException { + + private void mergeRepeatedField( + FieldDescriptor field, JsonElement json, Message.Builder builder) + throws InvalidProtocolBufferException { if (!(json instanceof JsonArray)) { - throw new InvalidProtocolBufferException( - "Expect an array but found: " + json); + throw new InvalidProtocolBufferException("Expect an array but found: " + json); } JsonArray array = (JsonArray) json; for (int i = 0; i < array.size(); ++i) { Object value = parseFieldValue(field, array.get(i), builder); if (value == null) { - throw new InvalidProtocolBufferException( - "Repeated field elements cannot be null"); + throw new InvalidProtocolBufferException("Repeated field elements cannot be null"); } builder.addRepeatedField(field, value); } } - - private int parseInt32(JsonElement json) - throws InvalidProtocolBufferException { + + private int parseInt32(JsonElement json) throws InvalidProtocolBufferException { try { return Integer.parseInt(json.getAsString()); } catch (Exception e) { @@ -1416,9 +1377,8 @@ public class JsonFormat { throw new InvalidProtocolBufferException("Not an int32 value: " + json); } } - - private long parseInt64(JsonElement json) - throws InvalidProtocolBufferException { + + private long parseInt64(JsonElement json) throws InvalidProtocolBufferException { try { return Long.parseLong(json.getAsString()); } catch (Exception e) { @@ -1434,14 +1394,12 @@ public class JsonFormat { throw new InvalidProtocolBufferException("Not an int32 value: " + json); } } - - private int parseUint32(JsonElement json) - throws InvalidProtocolBufferException { + + private int parseUint32(JsonElement json) throws InvalidProtocolBufferException { try { long result = Long.parseLong(json.getAsString()); if (result < 0 || result > 0xFFFFFFFFL) { - throw new InvalidProtocolBufferException( - "Out of range uint32 value: " + json); + throw new InvalidProtocolBufferException("Out of range uint32 value: " + json); } return (int) result; } catch (InvalidProtocolBufferException e) { @@ -1462,35 +1420,28 @@ public class JsonFormat { } catch (InvalidProtocolBufferException e) { throw e; } catch (Exception e) { - throw new InvalidProtocolBufferException( - "Not an uint32 value: " + json); + throw new InvalidProtocolBufferException("Not an uint32 value: " + json); } } - - private static final BigInteger MAX_UINT64 = - new BigInteger("FFFFFFFFFFFFFFFF", 16); - - private long parseUint64(JsonElement json) - throws InvalidProtocolBufferException { + + private static final BigInteger MAX_UINT64 = new BigInteger("FFFFFFFFFFFFFFFF", 16); + + private long parseUint64(JsonElement json) throws InvalidProtocolBufferException { try { BigDecimal decimalValue = new BigDecimal(json.getAsString()); BigInteger value = decimalValue.toBigIntegerExact(); - if (value.compareTo(BigInteger.ZERO) < 0 - || value.compareTo(MAX_UINT64) > 0) { - throw new InvalidProtocolBufferException( - "Out of range uint64 value: " + json); + if (value.compareTo(BigInteger.ZERO) < 0 || value.compareTo(MAX_UINT64) > 0) { + throw new InvalidProtocolBufferException("Out of range uint64 value: " + json); } return value.longValue(); } catch (InvalidProtocolBufferException e) { throw e; } catch (Exception e) { - throw new InvalidProtocolBufferException( - "Not an uint64 value: " + json); + throw new InvalidProtocolBufferException("Not an uint64 value: " + json); } } - - private boolean parseBool(JsonElement json) - throws InvalidProtocolBufferException { + + private boolean parseBool(JsonElement json) throws InvalidProtocolBufferException { if (json.getAsString().equals("true")) { return true; } @@ -1499,11 +1450,10 @@ public class JsonFormat { } throw new InvalidProtocolBufferException("Invalid bool value: " + json); } - + private static final double EPSILON = 1e-6; - - private float parseFloat(JsonElement json) - throws InvalidProtocolBufferException { + + private float parseFloat(JsonElement json) throws InvalidProtocolBufferException { if (json.getAsString().equals("NaN")) { return Float.NaN; } else if (json.getAsString().equals("Infinity")) { @@ -1521,8 +1471,7 @@ public class JsonFormat { // of tolerance when checking whether the float value is in range. if (value > Float.MAX_VALUE * (1.0 + EPSILON) || value < -Float.MAX_VALUE * (1.0 + EPSILON)) { - throw new InvalidProtocolBufferException( - "Out of range float value: " + json); + throw new InvalidProtocolBufferException("Out of range float value: " + json); } return (float) value; } catch (InvalidProtocolBufferException e) { @@ -1531,19 +1480,17 @@ public class JsonFormat { throw new InvalidProtocolBufferException("Not a float value: " + json); } } - - private static final BigDecimal MORE_THAN_ONE = new BigDecimal( - String.valueOf(1.0 + EPSILON)); + + private static final BigDecimal MORE_THAN_ONE = new BigDecimal(String.valueOf(1.0 + EPSILON)); // When a float value is printed, the printed value might be a little // larger or smaller due to precision loss. Here we need to add a bit // of tolerance when checking whether the float value is in range. - private static final BigDecimal MAX_DOUBLE = new BigDecimal( - String.valueOf(Double.MAX_VALUE)).multiply(MORE_THAN_ONE); - private static final BigDecimal MIN_DOUBLE = new BigDecimal( - String.valueOf(-Double.MAX_VALUE)).multiply(MORE_THAN_ONE); - - private double parseDouble(JsonElement json) - throws InvalidProtocolBufferException { + private static final BigDecimal MAX_DOUBLE = + new BigDecimal(String.valueOf(Double.MAX_VALUE)).multiply(MORE_THAN_ONE); + private static final BigDecimal MIN_DOUBLE = + new BigDecimal(String.valueOf(-Double.MAX_VALUE)).multiply(MORE_THAN_ONE); + + private double parseDouble(JsonElement json) throws InvalidProtocolBufferException { if (json.getAsString().equals("NaN")) { return Double.NaN; } else if (json.getAsString().equals("Infinity")) { @@ -1556,36 +1503,32 @@ public class JsonFormat { // accepts all values. Here we parse the value into a BigDecimal and do // explicit range check on it. BigDecimal value = new BigDecimal(json.getAsString()); - if (value.compareTo(MAX_DOUBLE) > 0 - || value.compareTo(MIN_DOUBLE) < 0) { - throw new InvalidProtocolBufferException( - "Out of range double value: " + json); + if (value.compareTo(MAX_DOUBLE) > 0 || value.compareTo(MIN_DOUBLE) < 0) { + throw new InvalidProtocolBufferException("Out of range double value: " + json); } return value.doubleValue(); } catch (InvalidProtocolBufferException e) { throw e; } catch (Exception e) { - throw new InvalidProtocolBufferException( - "Not an double value: " + json); + throw new InvalidProtocolBufferException("Not an double value: " + json); } } - + private String parseString(JsonElement json) { return json.getAsString(); } - + 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())); + return ByteString.copyFrom(BaseEncoding.base64().decode(json.getAsString())); } - - private EnumValueDescriptor parseEnum(EnumDescriptor enumDescriptor, - JsonElement json) throws InvalidProtocolBufferException { + + private EnumValueDescriptor parseEnum(EnumDescriptor enumDescriptor, JsonElement json) + throws InvalidProtocolBufferException { String value = json.getAsString(); EnumValueDescriptor result = enumDescriptor.findValueByName(value); if (result == null) { @@ -1602,27 +1545,28 @@ public class JsonFormat { // 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()); + "Invalid enum value: " + value + " for enum type: " + enumDescriptor.getFullName()); } } return result; } - - private Object parseFieldValue(FieldDescriptor field, JsonElement json, - Message.Builder builder) throws InvalidProtocolBufferException { + + private Object parseFieldValue(FieldDescriptor field, JsonElement json, Message.Builder builder) + throws InvalidProtocolBufferException { if (json instanceof JsonNull) { if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE - && field.getMessageType().getFullName().equals( - Value.getDescriptor().getFullName())) { + && field.getMessageType().getFullName().equals(Value.getDescriptor().getFullName())) { // For every other type, "null" means absence, but for the special // Value message, it means the "null_value" field has been set. Value value = Value.newBuilder().setNullValueValue(0).build(); - return builder.newBuilderForField(field).mergeFrom( - value.toByteString()).build(); + return builder.newBuilderForField(field).mergeFrom(value.toByteString()).build(); + } else if (field.getJavaType() == FieldDescriptor.JavaType.ENUM + && field.getEnumType().getFullName().equals(NullValue.getDescriptor().getFullName())) { + // If the type of the field is a NullValue, then the value should be explicitly set. + return field.getEnumType().findValueByNumber(0); } return null; } @@ -1642,7 +1586,7 @@ public class JsonFormat { case FLOAT: return parseFloat(json); - + case DOUBLE: return parseDouble(json); @@ -1668,11 +1612,10 @@ public class JsonFormat { Message.Builder subBuilder = builder.newBuilderForField(field); merge(json, subBuilder); return subBuilder.build(); - + default: - throw new InvalidProtocolBufferException( - "Invalid field type: " + field.getType()); - } + throw new InvalidProtocolBufferException("Invalid field type: " + field.getType()); + } } } } 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 3033182a..04758473 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 @@ -35,15 +35,14 @@ import com.google.protobuf.Timestamp; import java.math.BigInteger; import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.TimeZone; /** * Utilities to help create/manipulate Timestamp/Duration + * + * @deprecated Use {@link Durations} and {@link Timestamps} instead. */ -public class TimeUtil { +@Deprecated +public final class TimeUtil { // Timestamp for "0001-01-01T00:00:00Z" public static final long TIMESTAMP_SECONDS_MIN = -62135596800L; @@ -53,28 +52,6 @@ public class TimeUtil { public static final long DURATION_SECONDS_MAX = 315576000000L; private static final long NANOS_PER_SECOND = 1000000000; - private static final long NANOS_PER_MILLISECOND = 1000000; - private static final long NANOS_PER_MICROSECOND = 1000; - private static final long MILLIS_PER_SECOND = 1000; - private static final long MICROS_PER_SECOND = 1000000; - - private static final ThreadLocal timestampFormat = - new ThreadLocal() { - protected SimpleDateFormat initialValue() { - return createTimestampFormat(); - } - }; - - private static SimpleDateFormat createTimestampFormat() { - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); - GregorianCalendar calendar = - new GregorianCalendar(TimeZone.getTimeZone("UTC")); - // We use Proleptic Gregorian Calendar (i.e., Gregorian calendar extends - // backwards to year one) for timestamp formating. - calendar.setGregorianChange(new Date(Long.MIN_VALUE)); - sdf.setCalendar(calendar); - return sdf; - } private TimeUtil() {} @@ -90,27 +67,11 @@ public class TimeUtil { * @return The string representation of the given timestamp. * @throws IllegalArgumentException if the given timestamp is not in the * valid range. + * @deprecated Use {@link Timestamps#toString} instead. */ - public static String toString(Timestamp timestamp) - throws IllegalArgumentException { - StringBuilder result = new StringBuilder(); - // Format the seconds part. - if (timestamp.getSeconds() < TIMESTAMP_SECONDS_MIN - || timestamp.getSeconds() > TIMESTAMP_SECONDS_MAX) { - throw new IllegalArgumentException("Timestamp is out of range."); - } - Date date = new Date(timestamp.getSeconds() * MILLIS_PER_SECOND); - 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."); - } - if (timestamp.getNanos() != 0) { - result.append("."); - result.append(formatNanos(timestamp.getNanos())); - } - result.append("Z"); - return result.toString(); + @Deprecated + public static String toString(Timestamp timestamp) { + return Timestamps.toString(timestamp); } /** @@ -123,59 +84,11 @@ public class TimeUtil { * * @return A Timestamp parsed from the string. * @throws ParseException if parsing fails. + * @deprecated Use {@link Timestamps#parse} instead. */ - + @Deprecated public static Timestamp parseTimestamp(String value) throws ParseException { - int dayOffset = value.indexOf('T'); - if (dayOffset == -1) { - throw new ParseException( - "Failed to parse timestamp: invalid timestamp \"" + value + "\"", 0); - } - int timezoneOffsetPosition = value.indexOf('Z', dayOffset); - if (timezoneOffsetPosition == -1) { - timezoneOffsetPosition = value.indexOf('+', dayOffset); - } - if (timezoneOffsetPosition == -1) { - timezoneOffsetPosition = value.indexOf('-', dayOffset); - } - if (timezoneOffsetPosition == -1) { - throw new ParseException( - "Failed to parse timestamp: missing valid timezone offset.", 0); - } - // Parse seconds and nanos. - String timeValue = value.substring(0, timezoneOffsetPosition); - String secondValue = timeValue; - String nanoValue = ""; - int pointPosition = timeValue.indexOf('.'); - if (pointPosition != -1) { - secondValue = timeValue.substring(0, pointPosition); - nanoValue = timeValue.substring(pointPosition + 1); - } - Date date = timestampFormat.get().parse(secondValue); - long seconds = date.getTime() / MILLIS_PER_SECOND; - int nanos = nanoValue.isEmpty() ? 0 : parseNanos(nanoValue); - // Parse timezone offsets. - if (value.charAt(timezoneOffsetPosition) == 'Z') { - if (value.length() != timezoneOffsetPosition + 1) { - throw new ParseException( - "Failed to parse timestamp: invalid trailing data \"" - + value.substring(timezoneOffsetPosition) + "\"", 0); - } - } else { - String offsetValue = value.substring(timezoneOffsetPosition + 1); - long offset = parseTimezoneOffset(offsetValue); - if (value.charAt(timezoneOffsetPosition) == '+') { - seconds -= offset; - } else { - seconds += offset; - } - } - try { - return normalizedTimestamp(seconds, nanos); - } catch (IllegalArgumentException e) { - throw new ParseException( - "Failed to parse timestmap: timestamp is out of range.", 0); - } + return Timestamps.parse(value); } /** @@ -188,33 +101,11 @@ public class TimeUtil { * @return The string representation of the given duration. * @throws IllegalArgumentException if the given duration is not in the valid * range. + * @deprecated Use {@link Durations#toString} instead. */ - public static String toString(Duration duration) - throws IllegalArgumentException { - if (duration.getSeconds() < DURATION_SECONDS_MIN - || duration.getSeconds() > DURATION_SECONDS_MAX) { - throw new IllegalArgumentException("Duration is out of valid range."); - } - StringBuilder result = new StringBuilder(); - long seconds = duration.getSeconds(); - int nanos = duration.getNanos(); - if (seconds < 0 || nanos < 0) { - if (seconds > 0 || nanos > 0) { - throw new IllegalArgumentException( - "Invalid duration: seconds value and nanos value must have the same" - + "sign."); - } - result.append("-"); - seconds = -seconds; - nanos = -nanos; - } - result.append(seconds); - if (nanos != 0) { - result.append("."); - result.append(formatNanos(nanos)); - } - result.append("s"); - return result.toString(); + @Deprecated + public static String toString(Duration duration) { + return Durations.toString(duration); } /** @@ -222,54 +113,31 @@ public class TimeUtil { * * @return A Duration parsed from the string. * @throws ParseException if parsing fails. + * @deprecated Use {@link Durations#parse} instead. */ + @Deprecated public static Duration parseDuration(String value) throws ParseException { - // Must ended with "s". - if (value.isEmpty() || value.charAt(value.length() - 1) != 's') { - throw new ParseException("Invalid duration string: " + value, 0); - } - boolean negative = false; - if (value.charAt(0) == '-') { - negative = true; - value = value.substring(1); - } - String secondValue = value.substring(0, value.length() - 1); - String nanoValue = ""; - int pointPosition = secondValue.indexOf('.'); - if (pointPosition != -1) { - nanoValue = secondValue.substring(pointPosition + 1); - secondValue = secondValue.substring(0, pointPosition); - } - long seconds = Long.parseLong(secondValue); - int nanos = nanoValue.isEmpty() ? 0 : parseNanos(nanoValue); - if (seconds < 0) { - throw new ParseException("Invalid duration string: " + value, 0); - } - if (negative) { - seconds = -seconds; - nanos = -nanos; - } - try { - return normalizedDuration(seconds, nanos); - } catch (IllegalArgumentException e) { - throw new ParseException("Duration value is out of range.", 0); - } + return Durations.parse(value); } /** * Create a Timestamp from the number of milliseconds elapsed from the epoch. + * + * @deprecated Use {@link Timestamps#fromMillis} instead. */ + @Deprecated public static Timestamp createTimestampFromMillis(long milliseconds) { - return normalizedTimestamp(milliseconds / MILLIS_PER_SECOND, - (int) (milliseconds % MILLIS_PER_SECOND * NANOS_PER_MILLISECOND)); + return Timestamps.fromMillis(milliseconds); } /** * Create a Duration from the number of milliseconds. + * + * @deprecated Use {@link Durations#fromMillis} instead. */ + @Deprecated public static Duration createDurationFromMillis(long milliseconds) { - return normalizedDuration(milliseconds / MILLIS_PER_SECOND, - (int) (milliseconds % MILLIS_PER_SECOND * NANOS_PER_MILLISECOND)); + return Durations.fromMillis(milliseconds); } /** @@ -278,36 +146,44 @@ public class TimeUtil { *

The result will be rounded down to the nearest millisecond. E.g., if the * timestamp represents "1969-12-31T23:59:59.999999999Z", it will be rounded * to -1 millisecond. + * + * @deprecated Use {@link Timestamps#toMillis} instead. */ + @Deprecated public static long toMillis(Timestamp timestamp) { - return timestamp.getSeconds() * MILLIS_PER_SECOND + timestamp.getNanos() - / NANOS_PER_MILLISECOND; + return Timestamps.toMillis(timestamp); } /** * Convert a Duration to the number of milliseconds.The result will be * rounded towards 0 to the nearest millisecond. E.g., if the duration * represents -1 nanosecond, it will be rounded to 0. + * + * @deprecated Use {@link Durations#toMillis} instead. */ + @Deprecated public static long toMillis(Duration duration) { - return duration.getSeconds() * MILLIS_PER_SECOND + duration.getNanos() - / NANOS_PER_MILLISECOND; + return Durations.toMillis(duration); } /** * Create a Timestamp from the number of microseconds elapsed from the epoch. + * + * @deprecated Use {@link Timestamps#fromMicros} instead. */ + @Deprecated public static Timestamp createTimestampFromMicros(long microseconds) { - return normalizedTimestamp(microseconds / MICROS_PER_SECOND, - (int) (microseconds % MICROS_PER_SECOND * NANOS_PER_MICROSECOND)); + return Timestamps.fromMicros(microseconds); } /** * Create a Duration from the number of microseconds. + * + * @deprecated Use {@link Durations#fromMicros} instead. */ + @Deprecated public static Duration createDurationFromMicros(long microseconds) { - return normalizedDuration(microseconds / MICROS_PER_SECOND, - (int) (microseconds % MICROS_PER_SECOND * NANOS_PER_MICROSECOND)); + return Durations.fromMicros(microseconds); } /** @@ -316,111 +192,141 @@ public class TimeUtil { *

The result will be rounded down to the nearest microsecond. E.g., if the * timestamp represents "1969-12-31T23:59:59.999999999Z", it will be rounded * to -1 millisecond. + * + * @deprecated Use {@link Timestamps#toMicros} instead. */ + @Deprecated public static long toMicros(Timestamp timestamp) { - return timestamp.getSeconds() * MICROS_PER_SECOND + timestamp.getNanos() - / NANOS_PER_MICROSECOND; + return Timestamps.toMicros(timestamp); } /** * Convert a Duration to the number of microseconds.The result will be * rounded towards 0 to the nearest microseconds. E.g., if the duration * represents -1 nanosecond, it will be rounded to 0. + * + * @deprecated Use {@link Durations#toMicros} instead. */ + @Deprecated public static long toMicros(Duration duration) { - return duration.getSeconds() * MICROS_PER_SECOND + duration.getNanos() - / NANOS_PER_MICROSECOND; + return Durations.toMicros(duration); } /** * Create a Timestamp from the number of nanoseconds elapsed from the epoch. + * + * @deprecated Use {@link Timestamps#fromNanos} instead. */ + @Deprecated public static Timestamp createTimestampFromNanos(long nanoseconds) { - return normalizedTimestamp(nanoseconds / NANOS_PER_SECOND, - (int) (nanoseconds % NANOS_PER_SECOND)); + return Timestamps.fromNanos(nanoseconds); } /** * Create a Duration from the number of nanoseconds. + * + * @deprecated Use {@link Durations#fromNanos} instead. */ + @Deprecated public static Duration createDurationFromNanos(long nanoseconds) { - return normalizedDuration(nanoseconds / NANOS_PER_SECOND, - (int) (nanoseconds % NANOS_PER_SECOND)); + return Durations.fromNanos(nanoseconds); } /** * Convert a Timestamp to the number of nanoseconds elapsed from the epoch. + * + * @deprecated Use {@link Timestamps#toNanos} instead. */ + @Deprecated public static long toNanos(Timestamp timestamp) { - return timestamp.getSeconds() * NANOS_PER_SECOND + timestamp.getNanos(); + return Timestamps.toNanos(timestamp); } /** * Convert a Duration to the number of nanoseconds. + * + * @deprecated Use {@link Durations#toNanos} instead. */ + @Deprecated public static long toNanos(Duration duration) { - return duration.getSeconds() * NANOS_PER_SECOND + duration.getNanos(); + return Durations.toNanos(duration); } /** * Get the current time. + * + * @deprecated Use {@code Timestamps.fromMillis(System.currentTimeMillis())} instead. */ + @Deprecated public static Timestamp getCurrentTime() { - return createTimestampFromMillis(System.currentTimeMillis()); + return Timestamps.fromMillis(System.currentTimeMillis()); } /** * Get the epoch. + * + * @deprecated Use {@code Timestamps.fromMillis(0)} instead. */ + @Deprecated public static Timestamp getEpoch() { return Timestamp.getDefaultInstance(); } /** * Calculate the difference between two timestamps. + * + * @deprecated Use {@link Timestamps#between} instead. */ + @Deprecated public static Duration distance(Timestamp from, Timestamp to) { - return normalizedDuration(to.getSeconds() - from.getSeconds(), - to.getNanos() - from.getNanos()); + return Timestamps.between(from, to); } /** * Add a duration to a timestamp. + * + * @deprecated Use {@link Timestamps#add} instead. */ + @Deprecated public static Timestamp add(Timestamp start, Duration length) { - return normalizedTimestamp(start.getSeconds() + length.getSeconds(), - start.getNanos() + length.getNanos()); + return Timestamps.add(start, length); } /** * Subtract a duration from a timestamp. + * + * @deprecated Use {@link Timestamps#subtract} instead. */ + @Deprecated public static Timestamp subtract(Timestamp start, Duration length) { - return normalizedTimestamp(start.getSeconds() - length.getSeconds(), - start.getNanos() - length.getNanos()); + return Timestamps.subtract(start, length); } /** * Add two durations. + * + * @deprecated Use {@link Durations#add} instead. */ + @Deprecated public static Duration add(Duration d1, Duration d2) { - return normalizedDuration(d1.getSeconds() + d2.getSeconds(), - d1.getNanos() + d2.getNanos()); + return Durations.add(d1, d2); } /** * Subtract a duration from another. + * + * @deprecated Use {@link Durations#subtract} instead. */ + @Deprecated public static Duration subtract(Duration d1, Duration d2) { - return normalizedDuration(d1.getSeconds() - d2.getSeconds(), - d1.getNanos() - d2.getNanos()); + return Durations.subtract(d1, d2); } // Multiplications and divisions. + // TODO(kak): Delete this. public static Duration multiply(Duration duration, double times) { - double result = duration.getSeconds() * times + duration.getNanos() * times - / 1000000000.0; + double result = duration.getSeconds() * times + duration.getNanos() * times / 1000000000.0; if (result < Long.MIN_VALUE || result > Long.MAX_VALUE) { throw new IllegalArgumentException("Result is out of valid range."); } @@ -428,50 +334,49 @@ public class TimeUtil { int nanos = (int) ((result - seconds) * 1000000000); return normalizedDuration(seconds, nanos); } - + + // TODO(kak): Delete this. public static Duration divide(Duration duration, double value) { return multiply(duration, 1.0 / value); } - + + // TODO(kak): Delete this. public static Duration multiply(Duration duration, long times) { - return createDurationFromBigInteger( - toBigInteger(duration).multiply(toBigInteger(times))); + return createDurationFromBigInteger(toBigInteger(duration).multiply(toBigInteger(times))); } - + + // TODO(kak): Delete this. public static Duration divide(Duration duration, long times) { - return createDurationFromBigInteger( - toBigInteger(duration).divide(toBigInteger(times))); + return createDurationFromBigInteger(toBigInteger(duration).divide(toBigInteger(times))); } - + + // TODO(kak): Delete this. public static long divide(Duration d1, Duration d2) { return toBigInteger(d1).divide(toBigInteger(d2)).longValue(); } - + + // TODO(kak): Delete this. public static Duration remainder(Duration d1, Duration d2) { - return createDurationFromBigInteger( - toBigInteger(d1).remainder(toBigInteger(d2))); + return createDurationFromBigInteger(toBigInteger(d1).remainder(toBigInteger(d2))); } - + private static final BigInteger NANOS_PER_SECOND_BIG_INTEGER = new BigInteger(String.valueOf(NANOS_PER_SECOND)); - + private static BigInteger toBigInteger(Duration duration) { return toBigInteger(duration.getSeconds()) - .multiply(NANOS_PER_SECOND_BIG_INTEGER) - .add(toBigInteger(duration.getNanos())); + .multiply(NANOS_PER_SECOND_BIG_INTEGER) + .add(toBigInteger(duration.getNanos())); } - + private static BigInteger toBigInteger(long value) { return new BigInteger(String.valueOf(value)); } - + private static Duration createDurationFromBigInteger(BigInteger value) { - long seconds = value.divide( - new BigInteger(String.valueOf(NANOS_PER_SECOND))).longValue(); - int nanos = value.remainder( - new BigInteger(String.valueOf(NANOS_PER_SECOND))).intValue(); + long seconds = value.divide(new BigInteger(String.valueOf(NANOS_PER_SECOND))).longValue(); + int nanos = value.remainder(new BigInteger(String.valueOf(NANOS_PER_SECOND))).intValue(); return normalizedDuration(seconds, nanos); - } private static Duration normalizedDuration(long seconds, int nanos) { @@ -492,58 +397,4 @@ public class TimeUtil { } return Duration.newBuilder().setSeconds(seconds).setNanos(nanos).build(); } - - private static Timestamp normalizedTimestamp(long seconds, int nanos) { - if (nanos <= -NANOS_PER_SECOND || nanos >= NANOS_PER_SECOND) { - seconds += nanos / NANOS_PER_SECOND; - nanos %= NANOS_PER_SECOND; - } - if (nanos < 0) { - nanos += NANOS_PER_SECOND; - seconds -= 1; - } - if (seconds < TIMESTAMP_SECONDS_MIN || seconds > TIMESTAMP_SECONDS_MAX) { - throw new IllegalArgumentException("Timestamp is out of valid range."); - } - return Timestamp.newBuilder().setSeconds(seconds).setNanos(nanos).build(); - } - - /** - * Format the nano part of a timestamp or a duration. - */ - private static String formatNanos(int nanos) { - assert nanos >= 1 && nanos <= 999999999; - // Determine whether to use 3, 6, or 9 digits for the nano part. - if (nanos % NANOS_PER_MILLISECOND == 0) { - return String.format("%1$03d", nanos / NANOS_PER_MILLISECOND); - } else if (nanos % NANOS_PER_MICROSECOND == 0) { - return String.format("%1$06d", nanos / NANOS_PER_MICROSECOND); - } else { - return String.format("%1$09d", nanos); - } - } - - private static int parseNanos(String value) throws ParseException { - int result = 0; - for (int i = 0; i < 9; ++i) { - result = result * 10; - if (i < value.length()) { - if (value.charAt(i) < '0' || value.charAt(i) > '9') { - throw new ParseException("Invalid nanosecnds.", 0); - } - result += value.charAt(i) - '0'; - } - } - return result; - } - - private static long parseTimezoneOffset(String value) throws ParseException { - int pos = value.indexOf(':'); - if (pos == -1) { - throw new ParseException("Invalid offset value: " + value, 0); - } - String hours = value.substring(0, pos); - String minutes = value.substring(pos + 1); - return (Long.parseLong(hours) * 60 + Long.parseLong(minutes)) * 60; - } } 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 new file mode 100644 index 00000000..f1f202da --- /dev/null +++ b/java/util/src/main/java/com/google/protobuf/util/Timestamps.java @@ -0,0 +1,349 @@ +// 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.util; + +import com.google.protobuf.Duration; +import com.google.protobuf.Timestamp; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.TimeZone; + +/** + * Utilities to help create/manipulate {@code protobuf/timestamp.proto}. + */ +public final class Timestamps { + // Timestamp for "0001-01-01T00:00:00Z" + static final long TIMESTAMP_SECONDS_MIN = -62135596800L; + + // Timestamp for "9999-12-31T23:59:59Z" + static final long TIMESTAMP_SECONDS_MAX = 253402300799L; + + static final long NANOS_PER_SECOND = 1000000000; + static final long NANOS_PER_MILLISECOND = 1000000; + static final long NANOS_PER_MICROSECOND = 1000; + static final long MILLIS_PER_SECOND = 1000; + static final long MICROS_PER_SECOND = 1000000; + + // TODO(kak): Do we want to expose Timestamp constants for MAX/MIN? + + private static final ThreadLocal timestampFormat = + new ThreadLocal() { + protected SimpleDateFormat initialValue() { + return createTimestampFormat(); + } + }; + + private static SimpleDateFormat createTimestampFormat() { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + GregorianCalendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC")); + // We use Proleptic Gregorian Calendar (i.e., Gregorian calendar extends + // backwards to year one) for timestamp formating. + calendar.setGregorianChange(new Date(Long.MIN_VALUE)); + sdf.setCalendar(calendar); + return sdf; + } + + private Timestamps() {} + + /** + * Returns true if the given {@link Timestamp} is valid. The {@code seconds} value must be in the + * range [-62,135,596,800, +253,402,300,799] (i.e., between 0001-01-01T00:00:00Z and + * 9999-12-31T23:59:59Z). The {@code nanos} value must be in the range [0, +999,999,999]. + * + *

Note: Negative second values with fractions must still have non-negative nanos value that + * counts forward in time. + */ + public static boolean isValid(Timestamp timestamp) { + return isValid(timestamp.getSeconds(), timestamp.getNanos()); + } + + /** + * Returns true if the given number of seconds and nanos is a valid {@link Timestamp}. The + * {@code seconds} value must be in the range [-62,135,596,800, +253,402,300,799] (i.e., between + * 0001-01-01T00:00:00Z and 9999-12-31T23:59:59Z). The {@code nanos} value must be in the range + * [0, +999,999,999]. + * + *

Note: Negative second values with fractions must still have non-negative nanos value that + * counts forward in time. + */ + public static boolean isValid(long seconds, long nanos) { + if (seconds < TIMESTAMP_SECONDS_MIN || seconds > TIMESTAMP_SECONDS_MAX) { + return false; + } + if (nanos < 0 || nanos >= NANOS_PER_SECOND) { + return false; + } + return true; + } + + /** + * Throws an {@link IllegalArgumentException} if the given seconds/nanos are not + * a valid {@link Timestamp}. + */ + private static void checkValid(long seconds, int nanos) { + if (!isValid(seconds, nanos)) { + throw new IllegalArgumentException(String.format( + "Timestamp is not valid. See proto definition for valid values. " + + "Seconds (%s) must be in range [-62,135,596,800, +253,402,300,799]." + + "Nanos (%s) must be in range [0, +999,999,999].", + seconds, nanos)); + } + } + + /** + * Convert Timestamp to RFC 3339 date string format. The output will always + * be Z-normalized and uses 3, 6 or 9 fractional digits as required to + * represent the exact value. Note that Timestamp can only represent time + * from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. See + * https://www.ietf.org/rfc/rfc3339.txt + * + *

Example of generated format: "1972-01-01T10:00:20.021Z" + * + * @return The string representation of the given timestamp. + * @throws IllegalArgumentException if the given timestamp is not in the + * valid range. + */ + public static String toString(Timestamp timestamp) { + long seconds = timestamp.getSeconds(); + int nanos = timestamp.getNanos(); + checkValid(seconds, nanos); + StringBuilder result = new StringBuilder(); + // Format the seconds part. + Date date = new Date(seconds * MILLIS_PER_SECOND); + result.append(timestampFormat.get().format(date)); + // Format the nanos part. + if (nanos != 0) { + result.append("."); + result.append(formatNanos(nanos)); + } + result.append("Z"); + return result.toString(); + } + + /** + * Parse from RFC 3339 date string to Timestamp. This method accepts all + * outputs of {@link #toString(Timestamp)} and it also accepts any fractional + * digits (or none) and any offset as long as they fit into nano-seconds + * precision. + * + *

Example of accepted format: "1972-01-01T10:00:20.021-05:00" + * + * @return A Timestamp parsed from the string. + * @throws ParseException if parsing fails. + */ + public static Timestamp parse(String value) throws ParseException { + int dayOffset = value.indexOf('T'); + if (dayOffset == -1) { + throw new ParseException("Failed to parse timestamp: invalid timestamp \"" + value + "\"", 0); + } + int timezoneOffsetPosition = value.indexOf('Z', dayOffset); + if (timezoneOffsetPosition == -1) { + timezoneOffsetPosition = value.indexOf('+', dayOffset); + } + if (timezoneOffsetPosition == -1) { + timezoneOffsetPosition = value.indexOf('-', dayOffset); + } + if (timezoneOffsetPosition == -1) { + throw new ParseException("Failed to parse timestamp: missing valid timezone offset.", 0); + } + // Parse seconds and nanos. + String timeValue = value.substring(0, timezoneOffsetPosition); + String secondValue = timeValue; + String nanoValue = ""; + int pointPosition = timeValue.indexOf('.'); + if (pointPosition != -1) { + secondValue = timeValue.substring(0, pointPosition); + nanoValue = timeValue.substring(pointPosition + 1); + } + Date date = timestampFormat.get().parse(secondValue); + long seconds = date.getTime() / MILLIS_PER_SECOND; + int nanos = nanoValue.isEmpty() ? 0 : parseNanos(nanoValue); + // Parse timezone offsets. + if (value.charAt(timezoneOffsetPosition) == 'Z') { + if (value.length() != timezoneOffsetPosition + 1) { + throw new ParseException( + "Failed to parse timestamp: invalid trailing data \"" + + value.substring(timezoneOffsetPosition) + + "\"", + 0); + } + } else { + String offsetValue = value.substring(timezoneOffsetPosition + 1); + long offset = parseTimezoneOffset(offsetValue); + if (value.charAt(timezoneOffsetPosition) == '+') { + seconds -= offset; + } else { + seconds += offset; + } + } + try { + return normalizedTimestamp(seconds, nanos); + } catch (IllegalArgumentException e) { + throw new ParseException("Failed to parse timestmap: timestamp is out of range.", 0); + } + } + + /** + * Create a Timestamp from the number of milliseconds elapsed from the epoch. + */ + public static Timestamp fromMillis(long milliseconds) { + return normalizedTimestamp( + milliseconds / MILLIS_PER_SECOND, + (int) (milliseconds % MILLIS_PER_SECOND * NANOS_PER_MILLISECOND)); + } + + /** + * Convert a Timestamp to the number of milliseconds elapsed from the epoch. + * + *

The result will be rounded down to the nearest millisecond. E.g., if the + * timestamp represents "1969-12-31T23:59:59.999999999Z", it will be rounded + * to -1 millisecond. + */ + public static long toMillis(Timestamp timestamp) { + return timestamp.getSeconds() * MILLIS_PER_SECOND + + timestamp.getNanos() / NANOS_PER_MILLISECOND; + } + + /** + * Create a Timestamp from the number of microseconds elapsed from the epoch. + */ + public static Timestamp fromMicros(long microseconds) { + return normalizedTimestamp( + microseconds / MICROS_PER_SECOND, + (int) (microseconds % MICROS_PER_SECOND * NANOS_PER_MICROSECOND)); + } + + /** + * Convert a Timestamp to the number of microseconds elapsed from the epoch. + * + *

The result will be rounded down to the nearest microsecond. E.g., if the + * timestamp represents "1969-12-31T23:59:59.999999999Z", it will be rounded + * to -1 millisecond. + */ + public static long toMicros(Timestamp timestamp) { + return timestamp.getSeconds() * MICROS_PER_SECOND + + timestamp.getNanos() / NANOS_PER_MICROSECOND; + } + + /** + * Create a Timestamp from the number of nanoseconds elapsed from the epoch. + */ + public static Timestamp fromNanos(long nanoseconds) { + return normalizedTimestamp( + nanoseconds / NANOS_PER_SECOND, (int) (nanoseconds % NANOS_PER_SECOND)); + } + + /** + * Convert a Timestamp to the number of nanoseconds elapsed from the epoch. + */ + public static long toNanos(Timestamp timestamp) { + return timestamp.getSeconds() * NANOS_PER_SECOND + timestamp.getNanos(); + } + + /** + * Calculate the difference between two timestamps. + */ + public static Duration between(Timestamp from, Timestamp to) { + return Durations.normalizedDuration( + to.getSeconds() - from.getSeconds(), to.getNanos() - from.getNanos()); + } + + /** + * Add a duration to a timestamp. + */ + public static Timestamp add(Timestamp start, Duration length) { + return normalizedTimestamp( + start.getSeconds() + length.getSeconds(), start.getNanos() + length.getNanos()); + } + + /** + * Subtract a duration from a timestamp. + */ + public static Timestamp subtract(Timestamp start, Duration length) { + return normalizedTimestamp( + start.getSeconds() - length.getSeconds(), start.getNanos() - length.getNanos()); + } + + private static Timestamp normalizedTimestamp(long seconds, int nanos) { + if (nanos <= -NANOS_PER_SECOND || nanos >= NANOS_PER_SECOND) { + seconds += nanos / NANOS_PER_SECOND; + nanos %= NANOS_PER_SECOND; + } + if (nanos < 0) { + nanos += NANOS_PER_SECOND; + seconds -= 1; + } + checkValid(seconds, nanos); + return Timestamp.newBuilder().setSeconds(seconds).setNanos(nanos).build(); + } + + private static long parseTimezoneOffset(String value) throws ParseException { + int pos = value.indexOf(':'); + if (pos == -1) { + throw new ParseException("Invalid offset value: " + value, 0); + } + String hours = value.substring(0, pos); + String minutes = value.substring(pos + 1); + return (Long.parseLong(hours) * 60 + Long.parseLong(minutes)) * 60; + } + + static int parseNanos(String value) throws ParseException { + int result = 0; + for (int i = 0; i < 9; ++i) { + result = result * 10; + if (i < value.length()) { + if (value.charAt(i) < '0' || value.charAt(i) > '9') { + throw new ParseException("Invalid nanosecnds.", 0); + } + result += value.charAt(i) - '0'; + } + } + return result; + } + + /** + * Format the nano part of a timestamp or a duration. + */ + static String formatNanos(int nanos) { + assert nanos >= 1 && nanos <= 999999999; + // Determine whether to use 3, 6, or 9 digits for the nano part. + if (nanos % NANOS_PER_MILLISECOND == 0) { + return String.format("%1$03d", nanos / NANOS_PER_MILLISECOND); + } else if (nanos % NANOS_PER_MICROSECOND == 0) { + return String.format("%1$06d", nanos / NANOS_PER_MICROSECOND); + } else { + return String.format("%1$09d", nanos); + } + } +} 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 194f7b9c..1a998570 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 @@ -41,52 +41,55 @@ public class FieldMaskUtilTest extends TestCase { public void testIsValid() throws Exception { assertTrue(FieldMaskUtil.isValid(NestedTestAllTypes.class, "payload")); assertFalse(FieldMaskUtil.isValid(NestedTestAllTypes.class, "nonexist")); - assertTrue(FieldMaskUtil.isValid( - NestedTestAllTypes.class, "payload.optional_int32")); - assertTrue(FieldMaskUtil.isValid( - NestedTestAllTypes.class, "payload.repeated_int32")); - assertTrue(FieldMaskUtil.isValid( - NestedTestAllTypes.class, "payload.optional_nested_message")); - assertTrue(FieldMaskUtil.isValid( - NestedTestAllTypes.class, "payload.repeated_nested_message")); - assertFalse(FieldMaskUtil.isValid( - 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.class, "payload.optional_int32")); + assertTrue(FieldMaskUtil.isValid(NestedTestAllTypes.class, "payload.repeated_int32")); + assertTrue(FieldMaskUtil.isValid(NestedTestAllTypes.class, "payload.optional_nested_message")); + assertTrue(FieldMaskUtil.isValid(NestedTestAllTypes.class, "payload.repeated_nested_message")); + assertFalse(FieldMaskUtil.isValid(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")); + + 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( - NestedTestAllTypes.class, "payload.repeated_nested_message.bb")); + assertFalse( + FieldMaskUtil.isValid(NestedTestAllTypes.class, "payload.repeated_nested_message.bb")); // Non-message fields cannot have sub-paths. - assertFalse(FieldMaskUtil.isValid( - NestedTestAllTypes.class, "payload.optional_int32.bb")); + assertFalse(FieldMaskUtil.isValid(NestedTestAllTypes.class, "payload.optional_int32.bb")); } - + public void testToString() throws Exception { assertEquals("", FieldMaskUtil.toString(FieldMask.getDefaultInstance())); FieldMask mask = FieldMask.newBuilder().addPaths("foo").build(); assertEquals("foo", FieldMaskUtil.toString(mask)); mask = FieldMask.newBuilder().addPaths("foo").addPaths("bar").build(); assertEquals("foo,bar", FieldMaskUtil.toString(mask)); - + // Empty field paths are ignored. - mask = FieldMask.newBuilder().addPaths("").addPaths("foo").addPaths(""). - addPaths("bar").addPaths("").build(); + mask = + FieldMask.newBuilder() + .addPaths("") + .addPaths("foo") + .addPaths("") + .addPaths("bar") + .addPaths("") + .build(); assertEquals("foo,bar", FieldMaskUtil.toString(mask)); } @@ -111,8 +114,7 @@ public class FieldMaskUtilTest extends TestCase { mask = FieldMaskUtil.fromString(NestedTestAllTypes.class, ",payload"); try { - mask = FieldMaskUtil.fromString( - NestedTestAllTypes.class, "payload,nonexist"); + mask = FieldMaskUtil.fromString(NestedTestAllTypes.class, "payload,nonexist"); fail("Exception is expected."); } catch (IllegalArgumentException e) { // Expected. @@ -143,7 +145,33 @@ public class FieldMaskUtilTest extends TestCase { } catch (IllegalArgumentException expected) { } } - + + public void testToJsonString() throws Exception { + FieldMask mask = FieldMask.getDefaultInstance(); + assertEquals("", FieldMaskUtil.toJsonString(mask)); + mask = FieldMask.newBuilder().addPaths("foo").build(); + assertEquals("foo", FieldMaskUtil.toJsonString(mask)); + mask = FieldMask.newBuilder().addPaths("foo.bar_baz").addPaths("").build(); + assertEquals("foo.barBaz", FieldMaskUtil.toJsonString(mask)); + mask = FieldMask.newBuilder().addPaths("foo").addPaths("bar_baz").build(); + assertEquals("foo,barBaz", FieldMaskUtil.toJsonString(mask)); + } + + public void testFromJsonString() throws Exception { + FieldMask mask = FieldMaskUtil.fromJsonString(""); + assertEquals(0, mask.getPathsCount()); + mask = FieldMaskUtil.fromJsonString("foo"); + assertEquals(1, mask.getPathsCount()); + assertEquals("foo", mask.getPaths(0)); + mask = FieldMaskUtil.fromJsonString("foo.barBaz"); + assertEquals(1, mask.getPathsCount()); + assertEquals("foo.bar_baz", mask.getPaths(0)); + mask = FieldMaskUtil.fromJsonString("foo,barBaz"); + assertEquals(2, mask.getPathsCount()); + assertEquals("foo", mask.getPaths(0)); + assertEquals("bar_baz", mask.getPaths(1)); + } + public void testUnion() throws Exception { // Only test a simple case here and expect // {@link FieldMaskTreeTest#testAddFieldPath} to cover all scenarios. @@ -161,7 +189,7 @@ public class FieldMaskUtilTest extends TestCase { 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 // {@link FieldMaskTreeTest#testIntersectFieldPath} to cover all scenarios. @@ -170,13 +198,14 @@ public class FieldMaskUtilTest extends TestCase { FieldMask result = FieldMaskUtil.intersection(mask1, mask2); assertEquals("bar.baz,bar.quz,foo.bar", FieldMaskUtil.toString(result)); } - + public void testMerge() throws Exception { // Only test a simple case here and expect // {@link FieldMaskTreeTest#testMerge} to cover all scenarios. - NestedTestAllTypes source = NestedTestAllTypes.newBuilder() - .setPayload(TestAllTypes.newBuilder().setOptionalInt32(1234)) - .build(); + NestedTestAllTypes source = + NestedTestAllTypes.newBuilder() + .setPayload(TestAllTypes.newBuilder().setOptionalInt32(1234)) + .build(); NestedTestAllTypes.Builder builder = NestedTestAllTypes.newBuilder(); FieldMaskUtil.merge(FieldMaskUtil.fromString("payload"), source, builder); assertEquals(1234, builder.getPayload().getOptionalInt32()); 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 d95b626c..4d9a417d 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 @@ -41,6 +41,7 @@ import com.google.protobuf.Int64Value; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.ListValue; import com.google.protobuf.Message; +import com.google.protobuf.NullValue; import com.google.protobuf.StringValue; import com.google.protobuf.Struct; import com.google.protobuf.UInt32Value; @@ -82,7 +83,7 @@ public class JsonFormatTest extends TestCase { builder.setOptionalDouble(1.25); builder.setOptionalBool(true); builder.setOptionalString("Hello world!"); - builder.setOptionalBytes(ByteString.copyFrom(new byte[]{0, 1, 2})); + builder.setOptionalBytes(ByteString.copyFrom(new byte[] {0, 1, 2})); builder.setOptionalNestedEnum(NestedEnum.BAR); builder.getOptionalNestedMessageBuilder().setValue(100); @@ -100,7 +101,7 @@ public class JsonFormatTest extends TestCase { builder.addRepeatedDouble(1.25); builder.addRepeatedBool(true); builder.addRepeatedString("Hello world!"); - builder.addRepeatedBytes(ByteString.copyFrom(new byte[]{0, 1, 2})); + builder.addRepeatedBytes(ByteString.copyFrom(new byte[] {0, 1, 2})); builder.addRepeatedNestedEnum(NestedEnum.BAR); builder.addRepeatedNestedMessageBuilder().setValue(100); @@ -118,7 +119,7 @@ public class JsonFormatTest extends TestCase { builder.addRepeatedDouble(11.25); builder.addRepeatedBool(true); builder.addRepeatedString("ello world!"); - builder.addRepeatedBytes(ByteString.copyFrom(new byte[]{1, 2})); + builder.addRepeatedBytes(ByteString.copyFrom(new byte[] {1, 2})); builder.addRepeatedNestedEnum(NestedEnum.BAZ); builder.addRepeatedNestedMessageBuilder().setValue(200); } @@ -198,20 +199,22 @@ public class JsonFormatTest extends TestCase { } public void testUnknownEnumValues() throws Exception { - TestAllTypes message = TestAllTypes.newBuilder() - .setOptionalNestedEnumValue(12345) - .addRepeatedNestedEnumValue(12345) - .addRepeatedNestedEnumValue(0) - .build(); + TestAllTypes message = + TestAllTypes.newBuilder() + .setOptionalNestedEnumValue(12345) + .addRepeatedNestedEnumValue(12345) + .addRepeatedNestedEnumValue(0) + .build(); assertEquals( "{\n" - + " \"optionalNestedEnum\": 12345,\n" - + " \"repeatedNestedEnum\": [12345, \"FOO\"]\n" - + "}", toJsonString(message)); + + " \"optionalNestedEnum\": 12345,\n" + + " \"repeatedNestedEnum\": [12345, \"FOO\"]\n" + + "}", + toJsonString(message)); assertRoundTripEquals(message); TestMap.Builder mapBuilder = TestMap.newBuilder(); - mapBuilder.getMutableInt32ToEnumMapValue().put(1, 0); + mapBuilder.putInt32ToEnumMapValue(1, 0); mapBuilder.getMutableInt32ToEnumMapValue().put(2, 12345); TestMap mapMessage = mapBuilder.build(); assertEquals( @@ -226,19 +229,21 @@ public class JsonFormatTest extends TestCase { } public void testSpecialFloatValues() throws Exception { - TestAllTypes message = TestAllTypes.newBuilder() - .addRepeatedFloat(Float.NaN) - .addRepeatedFloat(Float.POSITIVE_INFINITY) - .addRepeatedFloat(Float.NEGATIVE_INFINITY) - .addRepeatedDouble(Double.NaN) - .addRepeatedDouble(Double.POSITIVE_INFINITY) - .addRepeatedDouble(Double.NEGATIVE_INFINITY) - .build(); + TestAllTypes message = + TestAllTypes.newBuilder() + .addRepeatedFloat(Float.NaN) + .addRepeatedFloat(Float.POSITIVE_INFINITY) + .addRepeatedFloat(Float.NEGATIVE_INFINITY) + .addRepeatedDouble(Double.NaN) + .addRepeatedDouble(Double.POSITIVE_INFINITY) + .addRepeatedDouble(Double.NEGATIVE_INFINITY) + .build(); assertEquals( "{\n" - + " \"repeatedFloat\": [\"NaN\", \"Infinity\", \"-Infinity\"],\n" - + " \"repeatedDouble\": [\"NaN\", \"Infinity\", \"-Infinity\"]\n" - + "}", toJsonString(message)); + + " \"repeatedFloat\": [\"NaN\", \"Infinity\", \"-Infinity\"],\n" + + " \"repeatedDouble\": [\"NaN\", \"Infinity\", \"-Infinity\"]\n" + + "}", + toJsonString(message)); assertRoundTripEquals(message); } @@ -247,15 +252,16 @@ public class JsonFormatTest extends TestCase { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); mergeFromJson( "{\n" - + " \"optionalInt32\": \"1234\",\n" - + " \"optionalUint32\": \"5678\",\n" - + " \"optionalSint32\": \"9012\",\n" - + " \"optionalFixed32\": \"3456\",\n" - + " \"optionalSfixed32\": \"7890\",\n" - + " \"optionalFloat\": \"1.5\",\n" - + " \"optionalDouble\": \"1.25\",\n" - + " \"optionalBool\": \"true\"\n" - + "}", builder); + + " \"optionalInt32\": \"1234\",\n" + + " \"optionalUint32\": \"5678\",\n" + + " \"optionalSint32\": \"9012\",\n" + + " \"optionalFixed32\": \"3456\",\n" + + " \"optionalSfixed32\": \"7890\",\n" + + " \"optionalFloat\": \"1.5\",\n" + + " \"optionalDouble\": \"1.25\",\n" + + " \"optionalBool\": \"true\"\n" + + "}", + builder); TestAllTypes message = builder.build(); assertEquals(1234, message.getOptionalInt32()); assertEquals(5678, message.getOptionalUint32()); @@ -276,8 +282,9 @@ public class JsonFormatTest extends TestCase { + " \"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}; + + "}", + builder); + int[] expectedValues = new int[] {1, 100000, 1, 100000}; assertEquals(4, builder.getRepeatedInt32Count()); assertEquals(4, builder.getRepeatedUint32Count()); assertEquals(4, builder.getRepeatedInt64Count()); @@ -366,51 +373,49 @@ public class JsonFormatTest extends TestCase { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); mergeFromJson( "{\n" - + " \"optionalInt32\": null,\n" - + " \"optionalInt64\": null,\n" - + " \"optionalUint32\": null,\n" - + " \"optionalUint64\": null,\n" - + " \"optionalSint32\": null,\n" - + " \"optionalSint64\": null,\n" - + " \"optionalFixed32\": null,\n" - + " \"optionalFixed64\": null,\n" - + " \"optionalSfixed32\": null,\n" - + " \"optionalSfixed64\": null,\n" - + " \"optionalFloat\": null,\n" - + " \"optionalDouble\": null,\n" - + " \"optionalBool\": null,\n" - + " \"optionalString\": null,\n" - + " \"optionalBytes\": null,\n" - + " \"optionalNestedMessage\": null,\n" - + " \"optionalNestedEnum\": null,\n" - + " \"repeatedInt32\": null,\n" - + " \"repeatedInt64\": null,\n" - + " \"repeatedUint32\": null,\n" - + " \"repeatedUint64\": null,\n" - + " \"repeatedSint32\": null,\n" - + " \"repeatedSint64\": null,\n" - + " \"repeatedFixed32\": null,\n" - + " \"repeatedFixed64\": null,\n" - + " \"repeatedSfixed32\": null,\n" - + " \"repeatedSfixed64\": null,\n" - + " \"repeatedFloat\": null,\n" - + " \"repeatedDouble\": null,\n" - + " \"repeatedBool\": null,\n" - + " \"repeatedString\": null,\n" - + " \"repeatedBytes\": null,\n" - + " \"repeatedNestedMessage\": null,\n" - + " \"repeatedNestedEnum\": null\n" - + "}", builder); + + " \"optionalInt32\": null,\n" + + " \"optionalInt64\": null,\n" + + " \"optionalUint32\": null,\n" + + " \"optionalUint64\": null,\n" + + " \"optionalSint32\": null,\n" + + " \"optionalSint64\": null,\n" + + " \"optionalFixed32\": null,\n" + + " \"optionalFixed64\": null,\n" + + " \"optionalSfixed32\": null,\n" + + " \"optionalSfixed64\": null,\n" + + " \"optionalFloat\": null,\n" + + " \"optionalDouble\": null,\n" + + " \"optionalBool\": null,\n" + + " \"optionalString\": null,\n" + + " \"optionalBytes\": null,\n" + + " \"optionalNestedMessage\": null,\n" + + " \"optionalNestedEnum\": null,\n" + + " \"repeatedInt32\": null,\n" + + " \"repeatedInt64\": null,\n" + + " \"repeatedUint32\": null,\n" + + " \"repeatedUint64\": null,\n" + + " \"repeatedSint32\": null,\n" + + " \"repeatedSint64\": null,\n" + + " \"repeatedFixed32\": null,\n" + + " \"repeatedFixed64\": null,\n" + + " \"repeatedSfixed32\": null,\n" + + " \"repeatedSfixed64\": null,\n" + + " \"repeatedFloat\": null,\n" + + " \"repeatedDouble\": null,\n" + + " \"repeatedBool\": null,\n" + + " \"repeatedString\": null,\n" + + " \"repeatedBytes\": null,\n" + + " \"repeatedNestedMessage\": null,\n" + + " \"repeatedNestedEnum\": null\n" + + "}", + builder); TestAllTypes message = builder.build(); assertEquals(TestAllTypes.getDefaultInstance(), message); // Repeated field elements cannot be null. try { builder = TestAllTypes.newBuilder(); - mergeFromJson( - "{\n" - + " \"repeatedInt32\": [null, null],\n" - + "}", builder); + mergeFromJson("{\n" + " \"repeatedInt32\": [null, null],\n" + "}", builder); fail(); } catch (InvalidProtocolBufferException e) { // Exception expected. @@ -418,16 +423,21 @@ public class JsonFormatTest extends TestCase { try { builder = TestAllTypes.newBuilder(); - mergeFromJson( - "{\n" - + " \"repeatedNestedMessage\": [null, null],\n" - + "}", builder); + mergeFromJson("{\n" + " \"repeatedNestedMessage\": [null, null],\n" + "}", builder); fail(); } catch (InvalidProtocolBufferException e) { // Exception expected. } } + public void testNullInOneof() throws Exception { + TestOneof.Builder builder = TestOneof.newBuilder(); + mergeFromJson("{\n" + " \"oneofNullValue\": null \n" + "}", builder); + TestOneof message = builder.build(); + assertEquals(TestOneof.OneofFieldCase.ONEOF_NULL_VALUE, message.getOneofFieldCase()); + assertEquals(NullValue.NULL_VALUE, message.getOneofNullValue()); + } + 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 @@ -441,7 +451,8 @@ public class JsonFormatTest extends TestCase { "{\n" + " \"optionalNestedMessage\": {},\n" + " \"optional_nested_message\": {}\n" - + "}", builder); + + "}", + builder); fail(); } catch (InvalidProtocolBufferException e) { // Exception expected. @@ -452,9 +463,10 @@ public class JsonFormatTest extends TestCase { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); mergeFromJson( "{\n" - + " \"repeatedNestedMessage\": [null, null],\n" - + " \"repeated_nested_message\": [null, null]\n" - + "}", builder); + + " \"repeatedNestedMessage\": [null, null],\n" + + " \"repeated_nested_message\": [null, null]\n" + + "}", + builder); fail(); } catch (InvalidProtocolBufferException e) { // Exception expected. @@ -463,11 +475,7 @@ public class JsonFormatTest extends TestCase { // Duplicated oneof fields. try { TestOneof.Builder builder = TestOneof.newBuilder(); - mergeFromJson( - "{\n" - + " \"oneofInt32\": 1,\n" - + " \"oneof_int32\": 2\n" - + "}", builder); + mergeFromJson("{\n" + " \"oneofInt32\": 1,\n" + " \"oneof_int32\": 2\n" + "}", builder); fail(); } catch (InvalidProtocolBufferException e) { // Exception expected. @@ -476,143 +484,138 @@ public class JsonFormatTest extends TestCase { public void testMapFields() throws Exception { TestMap.Builder builder = TestMap.newBuilder(); - builder.getMutableInt32ToInt32Map().put(1, 10); - builder.getMutableInt64ToInt32Map().put(1234567890123456789L, 10); - builder.getMutableUint32ToInt32Map().put(2, 20); - builder.getMutableUint64ToInt32Map().put(2234567890123456789L, 20); - builder.getMutableSint32ToInt32Map().put(3, 30); - builder.getMutableSint64ToInt32Map().put(3234567890123456789L, 30); - builder.getMutableFixed32ToInt32Map().put(4, 40); - builder.getMutableFixed64ToInt32Map().put(4234567890123456789L, 40); - builder.getMutableSfixed32ToInt32Map().put(5, 50); - builder.getMutableSfixed64ToInt32Map().put(5234567890123456789L, 50); - builder.getMutableBoolToInt32Map().put(false, 6); - builder.getMutableStringToInt32Map().put("Hello", 10); - - builder.getMutableInt32ToInt64Map().put(1, 1234567890123456789L); - builder.getMutableInt32ToUint32Map().put(2, 20); - builder.getMutableInt32ToUint64Map().put(2, 2234567890123456789L); - builder.getMutableInt32ToSint32Map().put(3, 30); - builder.getMutableInt32ToSint64Map().put(3, 3234567890123456789L); - builder.getMutableInt32ToFixed32Map().put(4, 40); - builder.getMutableInt32ToFixed64Map().put(4, 4234567890123456789L); - builder.getMutableInt32ToSfixed32Map().put(5, 50); - builder.getMutableInt32ToSfixed64Map().put(5, 5234567890123456789L); - builder.getMutableInt32ToFloatMap().put(6, 1.5f); - builder.getMutableInt32ToDoubleMap().put(6, 1.25); - builder.getMutableInt32ToBoolMap().put(7, false); - builder.getMutableInt32ToStringMap().put(7, "World"); - builder.getMutableInt32ToBytesMap().put( - 8, ByteString.copyFrom(new byte[]{1, 2, 3})); - builder.getMutableInt32ToMessageMap().put( - 8, NestedMessage.newBuilder().setValue(1234).build()); - builder.getMutableInt32ToEnumMap().put(9, NestedEnum.BAR); + builder.putInt32ToInt32Map(1, 10); + builder.putInt64ToInt32Map(1234567890123456789L, 10); + builder.putUint32ToInt32Map(2, 20); + builder.putUint64ToInt32Map(2234567890123456789L, 20); + builder.putSint32ToInt32Map(3, 30); + builder.putSint64ToInt32Map(3234567890123456789L, 30); + builder.putFixed32ToInt32Map(4, 40); + builder.putFixed64ToInt32Map(4234567890123456789L, 40); + builder.putSfixed32ToInt32Map(5, 50); + builder.putSfixed64ToInt32Map(5234567890123456789L, 50); + builder.putBoolToInt32Map(false, 6); + builder.putStringToInt32Map("Hello", 10); + + builder.putInt32ToInt64Map(1, 1234567890123456789L); + builder.putInt32ToUint32Map(2, 20); + builder.putInt32ToUint64Map(2, 2234567890123456789L); + builder.putInt32ToSint32Map(3, 30); + builder.putInt32ToSint64Map(3, 3234567890123456789L); + builder.putInt32ToFixed32Map(4, 40); + builder.putInt32ToFixed64Map(4, 4234567890123456789L); + builder.putInt32ToSfixed32Map(5, 50); + builder.putInt32ToSfixed64Map(5, 5234567890123456789L); + builder.putInt32ToFloatMap(6, 1.5f); + builder.putInt32ToDoubleMap(6, 1.25); + builder.putInt32ToBoolMap(7, false); + builder.putInt32ToStringMap(7, "World"); + builder.putInt32ToBytesMap(8, ByteString.copyFrom(new byte[] {1, 2, 3})); + builder.putInt32ToMessageMap(8, NestedMessage.newBuilder().setValue(1234).build()); + builder.putInt32ToEnumMap(9, NestedEnum.BAR); TestMap message = builder.build(); assertEquals( "{\n" - + " \"int32ToInt32Map\": {\n" - + " \"1\": 10\n" - + " },\n" - + " \"int64ToInt32Map\": {\n" - + " \"1234567890123456789\": 10\n" - + " },\n" - + " \"uint32ToInt32Map\": {\n" - + " \"2\": 20\n" - + " },\n" - + " \"uint64ToInt32Map\": {\n" - + " \"2234567890123456789\": 20\n" - + " },\n" - + " \"sint32ToInt32Map\": {\n" - + " \"3\": 30\n" - + " },\n" - + " \"sint64ToInt32Map\": {\n" - + " \"3234567890123456789\": 30\n" - + " },\n" - + " \"fixed32ToInt32Map\": {\n" - + " \"4\": 40\n" - + " },\n" - + " \"fixed64ToInt32Map\": {\n" - + " \"4234567890123456789\": 40\n" - + " },\n" - + " \"sfixed32ToInt32Map\": {\n" - + " \"5\": 50\n" - + " },\n" - + " \"sfixed64ToInt32Map\": {\n" - + " \"5234567890123456789\": 50\n" - + " },\n" - + " \"boolToInt32Map\": {\n" - + " \"false\": 6\n" - + " },\n" - + " \"stringToInt32Map\": {\n" - + " \"Hello\": 10\n" - + " },\n" - + " \"int32ToInt64Map\": {\n" - + " \"1\": \"1234567890123456789\"\n" - + " },\n" - + " \"int32ToUint32Map\": {\n" - + " \"2\": 20\n" - + " },\n" - + " \"int32ToUint64Map\": {\n" - + " \"2\": \"2234567890123456789\"\n" - + " },\n" - + " \"int32ToSint32Map\": {\n" - + " \"3\": 30\n" - + " },\n" - + " \"int32ToSint64Map\": {\n" - + " \"3\": \"3234567890123456789\"\n" - + " },\n" - + " \"int32ToFixed32Map\": {\n" - + " \"4\": 40\n" - + " },\n" - + " \"int32ToFixed64Map\": {\n" - + " \"4\": \"4234567890123456789\"\n" - + " },\n" - + " \"int32ToSfixed32Map\": {\n" - + " \"5\": 50\n" - + " },\n" - + " \"int32ToSfixed64Map\": {\n" - + " \"5\": \"5234567890123456789\"\n" - + " },\n" - + " \"int32ToFloatMap\": {\n" - + " \"6\": 1.5\n" - + " },\n" - + " \"int32ToDoubleMap\": {\n" - + " \"6\": 1.25\n" - + " },\n" - + " \"int32ToBoolMap\": {\n" - + " \"7\": false\n" - + " },\n" - + " \"int32ToStringMap\": {\n" - + " \"7\": \"World\"\n" - + " },\n" - + " \"int32ToBytesMap\": {\n" - + " \"8\": \"AQID\"\n" - + " },\n" - + " \"int32ToMessageMap\": {\n" - + " \"8\": {\n" - + " \"value\": 1234\n" - + " }\n" - + " },\n" - + " \"int32ToEnumMap\": {\n" - + " \"9\": \"BAR\"\n" - + " }\n" - + "}", toJsonString(message)); + + " \"int32ToInt32Map\": {\n" + + " \"1\": 10\n" + + " },\n" + + " \"int64ToInt32Map\": {\n" + + " \"1234567890123456789\": 10\n" + + " },\n" + + " \"uint32ToInt32Map\": {\n" + + " \"2\": 20\n" + + " },\n" + + " \"uint64ToInt32Map\": {\n" + + " \"2234567890123456789\": 20\n" + + " },\n" + + " \"sint32ToInt32Map\": {\n" + + " \"3\": 30\n" + + " },\n" + + " \"sint64ToInt32Map\": {\n" + + " \"3234567890123456789\": 30\n" + + " },\n" + + " \"fixed32ToInt32Map\": {\n" + + " \"4\": 40\n" + + " },\n" + + " \"fixed64ToInt32Map\": {\n" + + " \"4234567890123456789\": 40\n" + + " },\n" + + " \"sfixed32ToInt32Map\": {\n" + + " \"5\": 50\n" + + " },\n" + + " \"sfixed64ToInt32Map\": {\n" + + " \"5234567890123456789\": 50\n" + + " },\n" + + " \"boolToInt32Map\": {\n" + + " \"false\": 6\n" + + " },\n" + + " \"stringToInt32Map\": {\n" + + " \"Hello\": 10\n" + + " },\n" + + " \"int32ToInt64Map\": {\n" + + " \"1\": \"1234567890123456789\"\n" + + " },\n" + + " \"int32ToUint32Map\": {\n" + + " \"2\": 20\n" + + " },\n" + + " \"int32ToUint64Map\": {\n" + + " \"2\": \"2234567890123456789\"\n" + + " },\n" + + " \"int32ToSint32Map\": {\n" + + " \"3\": 30\n" + + " },\n" + + " \"int32ToSint64Map\": {\n" + + " \"3\": \"3234567890123456789\"\n" + + " },\n" + + " \"int32ToFixed32Map\": {\n" + + " \"4\": 40\n" + + " },\n" + + " \"int32ToFixed64Map\": {\n" + + " \"4\": \"4234567890123456789\"\n" + + " },\n" + + " \"int32ToSfixed32Map\": {\n" + + " \"5\": 50\n" + + " },\n" + + " \"int32ToSfixed64Map\": {\n" + + " \"5\": \"5234567890123456789\"\n" + + " },\n" + + " \"int32ToFloatMap\": {\n" + + " \"6\": 1.5\n" + + " },\n" + + " \"int32ToDoubleMap\": {\n" + + " \"6\": 1.25\n" + + " },\n" + + " \"int32ToBoolMap\": {\n" + + " \"7\": false\n" + + " },\n" + + " \"int32ToStringMap\": {\n" + + " \"7\": \"World\"\n" + + " },\n" + + " \"int32ToBytesMap\": {\n" + + " \"8\": \"AQID\"\n" + + " },\n" + + " \"int32ToMessageMap\": {\n" + + " \"8\": {\n" + + " \"value\": 1234\n" + + " }\n" + + " },\n" + + " \"int32ToEnumMap\": {\n" + + " \"9\": \"BAR\"\n" + + " }\n" + + "}", + toJsonString(message)); assertRoundTripEquals(message); // Test multiple entries. builder = TestMap.newBuilder(); - builder.getMutableInt32ToInt32Map().put(1, 2); - builder.getMutableInt32ToInt32Map().put(3, 4); + builder.putInt32ToInt32Map(1, 2); + builder.putInt32ToInt32Map(3, 4); message = builder.build(); assertEquals( - "{\n" - + " \"int32ToInt32Map\": {\n" - + " \"1\": 2,\n" - + " \"3\": 4\n" - + " }\n" - + "}", toJsonString(message)); + "{\n" + " \"int32ToInt32Map\": {\n" + " \"1\": 2,\n" + " \"3\": 4\n" + " }\n" + "}", + toJsonString(message)); assertRoundTripEquals(message); } @@ -621,9 +624,10 @@ public class JsonFormatTest extends TestCase { TestMap.Builder builder = TestMap.newBuilder(); mergeFromJson( "{\n" - + " \"int32ToInt32Map\": {null: 1},\n" - + " \"int32ToMessageMap\": {null: 2}\n" - + "}", builder); + + " \"int32ToInt32Map\": {null: 1},\n" + + " \"int32ToMessageMap\": {null: 2}\n" + + "}", + builder); fail(); } catch (InvalidProtocolBufferException e) { // Exception expected. @@ -633,9 +637,10 @@ public class JsonFormatTest extends TestCase { TestMap.Builder builder = TestMap.newBuilder(); mergeFromJson( "{\n" - + " \"int32ToInt32Map\": {\"1\": null},\n" - + " \"int32ToMessageMap\": {\"2\": null}\n" - + "}", builder); + + " \"int32ToInt32Map\": {\"1\": null},\n" + + " \"int32ToMessageMap\": {\"2\": null}\n" + + "}", + builder); fail(); } catch (InvalidProtocolBufferException e) { // Exception expected. @@ -645,10 +650,7 @@ public class JsonFormatTest extends TestCase { public void testParserAcceptNonQuotedObjectKey() throws Exception { TestMap.Builder builder = TestMap.newBuilder(); mergeFromJson( - "{\n" - + " int32ToInt32Map: {1: 2},\n" - + " stringToInt32Map: {hello: 3}\n" - + "}", builder); + "{\n" + " int32ToInt32Map: {1: 2},\n" + " stringToInt32Map: {hello: 3}\n" + "}", builder); TestMap message = builder.build(); assertEquals(2, message.getInt32ToInt32Map().get(1).intValue()); assertEquals(3, message.getStringToInt32Map().get("hello").intValue()); @@ -669,16 +671,17 @@ public class JsonFormatTest extends TestCase { assertEquals( "{\n" - + " \"int32Value\": 0,\n" - + " \"uint32Value\": 0,\n" - + " \"int64Value\": \"0\",\n" - + " \"uint64Value\": \"0\",\n" - + " \"floatValue\": 0.0,\n" - + " \"doubleValue\": 0.0,\n" - + " \"boolValue\": false,\n" - + " \"stringValue\": \"\",\n" - + " \"bytesValue\": \"\"\n" - + "}", toJsonString(message)); + + " \"int32Value\": 0,\n" + + " \"uint32Value\": 0,\n" + + " \"int64Value\": \"0\",\n" + + " \"uint64Value\": \"0\",\n" + + " \"floatValue\": 0.0,\n" + + " \"doubleValue\": 0.0,\n" + + " \"boolValue\": false,\n" + + " \"stringValue\": \"\",\n" + + " \"bytesValue\": \"\"\n" + + "}", + toJsonString(message)); assertRoundTripEquals(message); builder = TestWrappers.newBuilder(); @@ -690,57 +693,52 @@ public class JsonFormatTest extends TestCase { builder.getFloatValueBuilder().setValue(5.0f); builder.getDoubleValueBuilder().setValue(6.0); builder.getStringValueBuilder().setValue("7"); - builder.getBytesValueBuilder().setValue(ByteString.copyFrom(new byte[]{8})); + builder.getBytesValueBuilder().setValue(ByteString.copyFrom(new byte[] {8})); message = builder.build(); assertEquals( "{\n" - + " \"int32Value\": 1,\n" - + " \"uint32Value\": 3,\n" - + " \"int64Value\": \"2\",\n" - + " \"uint64Value\": \"4\",\n" - + " \"floatValue\": 5.0,\n" - + " \"doubleValue\": 6.0,\n" - + " \"boolValue\": true,\n" - + " \"stringValue\": \"7\",\n" - + " \"bytesValue\": \"CA==\"\n" - + "}", toJsonString(message)); + + " \"int32Value\": 1,\n" + + " \"uint32Value\": 3,\n" + + " \"int64Value\": \"2\",\n" + + " \"uint64Value\": \"4\",\n" + + " \"floatValue\": 5.0,\n" + + " \"doubleValue\": 6.0,\n" + + " \"boolValue\": true,\n" + + " \"stringValue\": \"7\",\n" + + " \"bytesValue\": \"CA==\"\n" + + "}", + toJsonString(message)); assertRoundTripEquals(message); } public void testTimestamp() throws Exception { - TestTimestamp message = TestTimestamp.newBuilder() - .setTimestampValue(TimeUtil.parseTimestamp("1970-01-01T00:00:00Z")) - .build(); + TestTimestamp message = + TestTimestamp.newBuilder() + .setTimestampValue(Timestamps.parse("1970-01-01T00:00:00Z")) + .build(); assertEquals( - "{\n" - + " \"timestampValue\": \"1970-01-01T00:00:00Z\"\n" - + "}", toJsonString(message)); + "{\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(); + TestDuration message = + TestDuration.newBuilder().setDurationValue(Durations.parse("12345s")).build(); - assertEquals( - "{\n" - + " \"durationValue\": \"12345s\"\n" - + "}", toJsonString(message)); + 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(); + TestFieldMask message = + TestFieldMask.newBuilder() + .setFieldMaskValue(FieldMaskUtil.fromString("foo.bar,baz,foo_bar.baz")) + .build(); assertEquals( - "{\n" - + " \"fieldMaskValue\": \"foo.bar,baz\"\n" - + "}", toJsonString(message)); + "{\n" + " \"fieldMaskValue\": \"foo.bar,baz,fooBar.baz\"\n" + "}", toJsonString(message)); assertRoundTripEquals(message); } @@ -748,45 +746,39 @@ public class JsonFormatTest extends TestCase { // Build a struct with all possible values. TestStruct.Builder builder = TestStruct.newBuilder(); Struct.Builder structBuilder = builder.getStructValueBuilder(); - structBuilder.getMutableFields().put( - "null_value", Value.newBuilder().setNullValueValue(0).build()); - structBuilder.getMutableFields().put( - "number_value", Value.newBuilder().setNumberValue(1.25).build()); - structBuilder.getMutableFields().put( - "string_value", Value.newBuilder().setStringValue("hello").build()); + structBuilder.putFields("null_value", Value.newBuilder().setNullValueValue(0).build()); + structBuilder.putFields("number_value", Value.newBuilder().setNumberValue(1.25).build()); + structBuilder.putFields("string_value", Value.newBuilder().setStringValue("hello").build()); Struct.Builder subStructBuilder = Struct.newBuilder(); - subStructBuilder.getMutableFields().put( - "number_value", Value.newBuilder().setNumberValue(1234).build()); - structBuilder.getMutableFields().put( + subStructBuilder.putFields("number_value", Value.newBuilder().setNumberValue(1234).build()); + structBuilder.putFields( "struct_value", Value.newBuilder().setStructValue(subStructBuilder.build()).build()); ListValue.Builder listBuilder = ListValue.newBuilder(); listBuilder.addValues(Value.newBuilder().setNumberValue(1.125).build()); listBuilder.addValues(Value.newBuilder().setNullValueValue(0).build()); - structBuilder.getMutableFields().put( + structBuilder.putFields( "list_value", Value.newBuilder().setListValue(listBuilder.build()).build()); TestStruct message = builder.build(); assertEquals( "{\n" - + " \"structValue\": {\n" - + " \"null_value\": null,\n" - + " \"number_value\": 1.25,\n" - + " \"string_value\": \"hello\",\n" - + " \"struct_value\": {\n" - + " \"number_value\": 1234.0\n" - + " },\n" - + " \"list_value\": [1.125, null]\n" - + " }\n" - + "}", toJsonString(message)); + + " \"structValue\": {\n" + + " \"null_value\": null,\n" + + " \"number_value\": 1.25,\n" + + " \"string_value\": \"hello\",\n" + + " \"struct_value\": {\n" + + " \"number_value\": 1234.0\n" + + " },\n" + + " \"list_value\": [1.125, null]\n" + + " }\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)); + assertEquals("{\n" + " \"value\": null\n" + "}", toJsonString(message)); assertRoundTripEquals(message); builder = TestStruct.newBuilder(); @@ -794,10 +786,7 @@ public class JsonFormatTest extends TestCase { 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)); + assertEquals("{\n" + " \"listValue\": [31831.125, null]\n" + "}", toJsonString(message)); assertRoundTripEquals(message); } @@ -813,158 +802,169 @@ public class JsonFormatTest extends TestCase { // Expected. } - JsonFormat.TypeRegistry registry = JsonFormat.TypeRegistry.newBuilder() - .add(TestAllTypes.getDescriptor()).build(); + JsonFormat.TypeRegistry registry = + JsonFormat.TypeRegistry.newBuilder().add(TestAllTypes.getDescriptor()).build(); JsonFormat.Printer printer = JsonFormat.printer().usingTypeRegistry(registry); assertEquals( "{\n" - + " \"anyValue\": {\n" - + " \"@type\": \"type.googleapis.com/json_test.TestAllTypes\",\n" - + " \"optionalInt32\": 1234\n" - + " }\n" - + "}" , printer.print(message)); + + " \"anyValue\": {\n" + + " \"@type\": \"type.googleapis.com/json_test.TestAllTypes\",\n" + + " \"optionalInt32\": 1234\n" + + " }\n" + + "}", + printer.print(message)); assertRoundTripEquals(message, registry); - // Well-known types have a special formatting when embedded in Any. // // 1. Any in Any. Any anyMessage = Any.pack(Any.pack(content)); assertEquals( "{\n" - + " \"@type\": \"type.googleapis.com/google.protobuf.Any\",\n" - + " \"value\": {\n" - + " \"@type\": \"type.googleapis.com/json_test.TestAllTypes\",\n" - + " \"optionalInt32\": 1234\n" - + " }\n" - + "}", printer.print(anyMessage)); + + " \"@type\": \"type.googleapis.com/google.protobuf.Any\",\n" + + " \"value\": {\n" + + " \"@type\": \"type.googleapis.com/json_test.TestAllTypes\",\n" + + " \"optionalInt32\": 1234\n" + + " }\n" + + "}", + printer.print(anyMessage)); assertRoundTripEquals(anyMessage, registry); // 2. Wrappers in Any. anyMessage = Any.pack(Int32Value.newBuilder().setValue(12345).build()); assertEquals( "{\n" - + " \"@type\": \"type.googleapis.com/google.protobuf.Int32Value\",\n" - + " \"value\": 12345\n" - + "}", printer.print(anyMessage)); + + " \"@type\": \"type.googleapis.com/google.protobuf.Int32Value\",\n" + + " \"value\": 12345\n" + + "}", + printer.print(anyMessage)); assertRoundTripEquals(anyMessage, registry); anyMessage = Any.pack(UInt32Value.newBuilder().setValue(12345).build()); assertEquals( "{\n" - + " \"@type\": \"type.googleapis.com/google.protobuf.UInt32Value\",\n" - + " \"value\": 12345\n" - + "}", printer.print(anyMessage)); + + " \"@type\": \"type.googleapis.com/google.protobuf.UInt32Value\",\n" + + " \"value\": 12345\n" + + "}", + printer.print(anyMessage)); assertRoundTripEquals(anyMessage, registry); anyMessage = Any.pack(Int64Value.newBuilder().setValue(12345).build()); assertEquals( "{\n" - + " \"@type\": \"type.googleapis.com/google.protobuf.Int64Value\",\n" - + " \"value\": \"12345\"\n" - + "}", printer.print(anyMessage)); + + " \"@type\": \"type.googleapis.com/google.protobuf.Int64Value\",\n" + + " \"value\": \"12345\"\n" + + "}", + printer.print(anyMessage)); assertRoundTripEquals(anyMessage, registry); anyMessage = Any.pack(UInt64Value.newBuilder().setValue(12345).build()); assertEquals( "{\n" - + " \"@type\": \"type.googleapis.com/google.protobuf.UInt64Value\",\n" - + " \"value\": \"12345\"\n" - + "}", printer.print(anyMessage)); + + " \"@type\": \"type.googleapis.com/google.protobuf.UInt64Value\",\n" + + " \"value\": \"12345\"\n" + + "}", + printer.print(anyMessage)); assertRoundTripEquals(anyMessage, registry); anyMessage = Any.pack(FloatValue.newBuilder().setValue(12345).build()); assertEquals( "{\n" - + " \"@type\": \"type.googleapis.com/google.protobuf.FloatValue\",\n" - + " \"value\": 12345.0\n" - + "}", printer.print(anyMessage)); + + " \"@type\": \"type.googleapis.com/google.protobuf.FloatValue\",\n" + + " \"value\": 12345.0\n" + + "}", + printer.print(anyMessage)); assertRoundTripEquals(anyMessage, registry); anyMessage = Any.pack(DoubleValue.newBuilder().setValue(12345).build()); assertEquals( "{\n" - + " \"@type\": \"type.googleapis.com/google.protobuf.DoubleValue\",\n" - + " \"value\": 12345.0\n" - + "}", printer.print(anyMessage)); + + " \"@type\": \"type.googleapis.com/google.protobuf.DoubleValue\",\n" + + " \"value\": 12345.0\n" + + "}", + printer.print(anyMessage)); assertRoundTripEquals(anyMessage, registry); anyMessage = Any.pack(BoolValue.newBuilder().setValue(true).build()); assertEquals( "{\n" - + " \"@type\": \"type.googleapis.com/google.protobuf.BoolValue\",\n" - + " \"value\": true\n" - + "}", printer.print(anyMessage)); + + " \"@type\": \"type.googleapis.com/google.protobuf.BoolValue\",\n" + + " \"value\": true\n" + + "}", + printer.print(anyMessage)); assertRoundTripEquals(anyMessage, registry); anyMessage = Any.pack(StringValue.newBuilder().setValue("Hello").build()); assertEquals( "{\n" - + " \"@type\": \"type.googleapis.com/google.protobuf.StringValue\",\n" - + " \"value\": \"Hello\"\n" - + "}", printer.print(anyMessage)); + + " \"@type\": \"type.googleapis.com/google.protobuf.StringValue\",\n" + + " \"value\": \"Hello\"\n" + + "}", + printer.print(anyMessage)); assertRoundTripEquals(anyMessage, registry); - anyMessage = Any.pack(BytesValue.newBuilder().setValue( - ByteString.copyFrom(new byte[]{1, 2})).build()); + anyMessage = + Any.pack(BytesValue.newBuilder().setValue(ByteString.copyFrom(new byte[] {1, 2})).build()); assertEquals( "{\n" - + " \"@type\": \"type.googleapis.com/google.protobuf.BytesValue\",\n" - + " \"value\": \"AQI=\"\n" - + "}", printer.print(anyMessage)); + + " \"@type\": \"type.googleapis.com/google.protobuf.BytesValue\",\n" + + " \"value\": \"AQI=\"\n" + + "}", + printer.print(anyMessage)); assertRoundTripEquals(anyMessage, registry); // 3. Timestamp in Any. - anyMessage = Any.pack(TimeUtil.parseTimestamp("1969-12-31T23:59:59Z")); + anyMessage = Any.pack(Timestamps.parse("1969-12-31T23:59:59Z")); assertEquals( "{\n" - + " \"@type\": \"type.googleapis.com/google.protobuf.Timestamp\",\n" - + " \"value\": \"1969-12-31T23:59:59Z\"\n" - + "}", printer.print(anyMessage)); + + " \"@type\": \"type.googleapis.com/google.protobuf.Timestamp\",\n" + + " \"value\": \"1969-12-31T23:59:59Z\"\n" + + "}", + printer.print(anyMessage)); assertRoundTripEquals(anyMessage, registry); // 4. Duration in Any - anyMessage = Any.pack(TimeUtil.parseDuration("12345.10s")); + anyMessage = Any.pack(Durations.parse("12345.10s")); assertEquals( "{\n" - + " \"@type\": \"type.googleapis.com/google.protobuf.Duration\",\n" - + " \"value\": \"12345.100s\"\n" - + "}", printer.print(anyMessage)); + + " \"@type\": \"type.googleapis.com/google.protobuf.Duration\",\n" + + " \"value\": \"12345.100s\"\n" + + "}", + printer.print(anyMessage)); assertRoundTripEquals(anyMessage, registry); // 5. FieldMask in Any anyMessage = Any.pack(FieldMaskUtil.fromString("foo.bar,baz")); assertEquals( "{\n" - + " \"@type\": \"type.googleapis.com/google.protobuf.FieldMask\",\n" - + " \"value\": \"foo.bar,baz\"\n" - + "}", printer.print(anyMessage)); + + " \"@type\": \"type.googleapis.com/google.protobuf.FieldMask\",\n" + + " \"value\": \"foo.bar,baz\"\n" + + "}", + printer.print(anyMessage)); assertRoundTripEquals(anyMessage, registry); // 6. Struct in Any Struct.Builder structBuilder = Struct.newBuilder(); - structBuilder.getMutableFields().put( - "number", Value.newBuilder().setNumberValue(1.125).build()); + structBuilder.putFields("number", Value.newBuilder().setNumberValue(1.125).build()); anyMessage = Any.pack(structBuilder.build()); assertEquals( "{\n" - + " \"@type\": \"type.googleapis.com/google.protobuf.Struct\",\n" - + " \"value\": {\n" - + " \"number\": 1.125\n" - + " }\n" - + "}", printer.print(anyMessage)); + + " \"@type\": \"type.googleapis.com/google.protobuf.Struct\",\n" + + " \"value\": {\n" + + " \"number\": 1.125\n" + + " }\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)); + + " \"@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(); - mergeFromJson( - "{\n" - + " \"optionalInt32\": 1234\n" - + "}", builder); + mergeFromJson("{\n" + " \"optionalInt32\": 1234\n" + "}", builder); fail("Exception is expected."); } catch (IOException e) { // Expected. @@ -976,9 +976,10 @@ public class JsonFormatTest extends TestCase { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); mergeFromJson( "{\n" - + " \"@type\": \"type.googleapis.com/json_test.TestAllTypes\",\n" - + " \"optionalInt32\": 12345\n" - + "}", builder); + + " \"@type\": \"type.googleapis.com/json_test.TestAllTypes\",\n" + + " \"optionalInt32\": 12345\n" + + "}", + builder); fail("Exception is expected."); } catch (IOException e) { // Expected. @@ -988,10 +989,7 @@ public class JsonFormatTest extends TestCase { public void testParserRejectTrailingComma() throws Exception { try { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); - mergeFromJson( - "{\n" - + " \"optionalInt32\": 12345,\n" - + "}", builder); + mergeFromJson("{\n" + " \"optionalInt32\": 12345,\n" + "}", builder); fail("Exception is expected."); } catch (IOException e) { // Expected. @@ -1022,10 +1020,7 @@ public class JsonFormatTest extends TestCase { public void testParserRejectInvalidEnumValue() throws Exception { try { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); - mergeFromJson( - "{\n" - + " \"optionalNestedEnum\": \"XXX\"\n" - + "}", builder); + mergeFromJson("{\n" + " \"optionalNestedEnum\": \"XXX\"\n" + "}", builder); fail("Exception is expected."); } catch (InvalidProtocolBufferException e) { // Expected. 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 4c31b2b3..a41528ec 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 @@ -84,6 +84,7 @@ public class TimeUtilTest extends TestCase { 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; @@ -102,8 +103,8 @@ public class TimeUtilTest extends TestCase { } if (result.getSeconds() != values[index].getSeconds() || result.getNanos() != values[index].getNanos()) { - errorMessage = "Actual result: " + result.toString() + ", expected: " - + values[index].toString(); + errorMessage = + "Actual result: " + result.toString() + ", expected: " + values[index].toString(); break; } index = (index + 1) % strings.length; @@ -112,26 +113,26 @@ public class TimeUtilTest extends TestCase { } 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", - }; + 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 int RUNNING_TIME = 5000; // in milliseconds. final List threads = new ArrayList(); stopParsingThreads = false; errorMessage = ""; for (int i = 0; i < THREAD_COUNT; i++) { - Thread thread = new ParseTimestampThread( - timestampStrings, timestampValues); + Thread thread = new ParseTimestampThread(timestampStrings, timestampValues); thread.start(); threads.add(thread); } @@ -146,8 +147,8 @@ public class TimeUtilTest extends TestCase { public void testTimetampInvalidFormat() throws Exception { try { // Value too small. - Timestamp value = Timestamp.newBuilder() - .setSeconds(TimeUtil.TIMESTAMP_SECONDS_MIN - 1).build(); + Timestamp value = + Timestamp.newBuilder().setSeconds(TimeUtil.TIMESTAMP_SECONDS_MIN - 1).build(); TimeUtil.toString(value); Assert.fail("Exception is expected."); } catch (IllegalArgumentException e) { @@ -156,14 +157,14 @@ public class TimeUtilTest extends TestCase { try { // Value too large. - Timestamp value = Timestamp.newBuilder() - .setSeconds(TimeUtil.TIMESTAMP_SECONDS_MAX + 1).build(); + Timestamp value = + Timestamp.newBuilder().setSeconds(TimeUtil.TIMESTAMP_SECONDS_MAX + 1).build(); TimeUtil.toString(value); Assert.fail("Exception is expected."); } catch (IllegalArgumentException e) { // Expected. } - + try { // Invalid nanos value. Timestamp value = Timestamp.newBuilder().setNanos(-1).build(); @@ -279,8 +280,7 @@ public class TimeUtilTest extends TestCase { public void testDurationInvalidFormat() throws Exception { try { // Value too small. - Duration value = Duration.newBuilder() - .setSeconds(TimeUtil.DURATION_SECONDS_MIN - 1).build(); + Duration value = Duration.newBuilder().setSeconds(TimeUtil.DURATION_SECONDS_MIN - 1).build(); TimeUtil.toString(value); Assert.fail("Exception is expected."); } catch (IllegalArgumentException e) { @@ -289,18 +289,16 @@ public class TimeUtilTest extends TestCase { try { // Value too large. - Duration value = Duration.newBuilder() - .setSeconds(TimeUtil.DURATION_SECONDS_MAX + 1).build(); + Duration value = Duration.newBuilder().setSeconds(TimeUtil.DURATION_SECONDS_MAX + 1).build(); TimeUtil.toString(value); Assert.fail("Exception is expected."); } catch (IllegalArgumentException e) { // Expected. } - + try { // Invalid nanos value. - Duration value = Duration.newBuilder().setSeconds(1).setNanos(-1) - .build(); + Duration value = Duration.newBuilder().setSeconds(1).setNanos(-1).build(); TimeUtil.toString(value); Assert.fail("Exception is expected."); } catch (IllegalArgumentException e) { @@ -309,8 +307,7 @@ public class TimeUtilTest extends TestCase { try { // Invalid nanos value. - Duration value = Duration.newBuilder().setSeconds(-1).setNanos(1) - .build(); + Duration value = Duration.newBuilder().setSeconds(-1).setNanos(1).build(); TimeUtil.toString(value); Assert.fail("Exception is expected."); } catch (IllegalArgumentException e) { @@ -367,8 +364,7 @@ public class TimeUtilTest extends TestCase { } public void testTimestampConversion() throws Exception { - Timestamp timestamp = - TimeUtil.parseTimestamp("1970-01-01T00:00:01.111111111Z"); + Timestamp timestamp = TimeUtil.parseTimestamp("1970-01-01T00:00:01.111111111Z"); assertEquals(1111111111, TimeUtil.toNanos(timestamp)); assertEquals(1111111, TimeUtil.toMicros(timestamp)); assertEquals(1111, TimeUtil.toMillis(timestamp)); @@ -378,7 +374,7 @@ public class TimeUtilTest extends TestCase { assertEquals("1970-01-01T00:00:01.111111Z", TimeUtil.toString(timestamp)); timestamp = TimeUtil.createTimestampFromMillis(1111); assertEquals("1970-01-01T00:00:01.111Z", TimeUtil.toString(timestamp)); - + timestamp = TimeUtil.parseTimestamp("1969-12-31T23:59:59.111111111Z"); assertEquals(-888888889, TimeUtil.toNanos(timestamp)); assertEquals(-888889, TimeUtil.toMicros(timestamp)); @@ -402,7 +398,7 @@ public class TimeUtilTest extends TestCase { assertEquals("1.111111s", TimeUtil.toString(duration)); duration = TimeUtil.createDurationFromMillis(1111); assertEquals("1.111s", TimeUtil.toString(duration)); - + duration = TimeUtil.parseDuration("-1.111111111s"); assertEquals(-1111111111, TimeUtil.toNanos(duration)); assertEquals(-1111111, TimeUtil.toMicros(duration)); @@ -459,29 +455,28 @@ public class TimeUtilTest extends TestCase { duration = TimeUtil.add(duration, duration); assertEquals("-2.250s", TimeUtil.toString(duration)); - + duration = TimeUtil.subtract(duration, TimeUtil.parseDuration("-1s")); assertEquals("-1.250s", TimeUtil.toString(duration)); - + // Multiplications (with results larger than Long.MAX_VALUE in nanoseconds). duration = TimeUtil.parseDuration("0.999999999s"); - assertEquals("315575999684.424s", - TimeUtil.toString(TimeUtil.multiply(duration, 315576000000L))); + assertEquals( + "315575999684.424s", TimeUtil.toString(TimeUtil.multiply(duration, 315576000000L))); duration = TimeUtil.parseDuration("-0.999999999s"); - assertEquals("-315575999684.424s", - TimeUtil.toString(TimeUtil.multiply(duration, 315576000000L))); - assertEquals("315575999684.424s", - TimeUtil.toString(TimeUtil.multiply(duration, -315576000000L))); - + assertEquals( + "-315575999684.424s", TimeUtil.toString(TimeUtil.multiply(duration, 315576000000L))); + assertEquals( + "315575999684.424s", TimeUtil.toString(TimeUtil.multiply(duration, -315576000000L))); + // Divisions (with values larger than Long.MAX_VALUE in nanoseconds). Duration d1 = TimeUtil.parseDuration("315576000000s"); Duration d2 = TimeUtil.subtract(d1, TimeUtil.createDurationFromNanos(1)); assertEquals(1, TimeUtil.divide(d1, d2)); assertEquals(0, TimeUtil.divide(d2, d1)); assertEquals("0.000000001s", TimeUtil.toString(TimeUtil.remainder(d1, d2))); - assertEquals("315575999999.999999999s", - TimeUtil.toString(TimeUtil.remainder(d2, d1))); - + assertEquals("315575999999.999999999s", TimeUtil.toString(TimeUtil.remainder(d2, d1))); + // Divisions involving negative values. // // (-5) / 2 = -2, remainder = -1 diff --git a/java/util/src/test/proto/com/google/protobuf/util/json_test.proto b/java/util/src/test/proto/com/google/protobuf/util/json_test.proto index 686edc42..4bf223f2 100644 --- a/java/util/src/test/proto/com/google/protobuf/util/json_test.proto +++ b/java/util/src/test/proto/com/google/protobuf/util/json_test.proto @@ -124,6 +124,7 @@ message TestOneof { oneof oneof_field { int32 oneof_int32 = 1; TestAllTypes.NestedMessage oneof_nested_message = 2; + google.protobuf.NullValue oneof_null_value = 3; } } -- cgit v1.2.3