aboutsummaryrefslogtreecommitdiffhomepage
path: root/java/util/src/main/java/com
diff options
context:
space:
mode:
authorGravatar Adam Cozzette <acozzette@google.com>2016-06-29 15:23:27 -0700
committerGravatar Adam Cozzette <acozzette@google.com>2016-06-29 15:38:03 -0700
commitd64a2d9941c36a7bc2a7959ea10ab8363192ac14 (patch)
tree52330d146ad63d3d70f3baade00d5d1fea8f5e0c /java/util/src/main/java/com
parentc18aa7795a2e02ef700ff8b039d94ecdcc33432f (diff)
Integrated internal changes from Google
This includes all internal changes from around May 20 to now.
Diffstat (limited to 'java/util/src/main/java/com')
-rw-r--r--java/util/src/main/java/com/google/protobuf/util/Durations.java256
-rw-r--r--java/util/src/main/java/com/google/protobuf/util/FieldMaskTree.java48
-rw-r--r--java/util/src/main/java/com/google/protobuf/util/FieldMaskUtil.java78
-rw-r--r--java/util/src/main/java/com/google/protobuf/util/JsonFormat.java715
-rw-r--r--java/util/src/main/java/com/google/protobuf/util/TimeUtil.java381
-rw-r--r--java/util/src/main/java/com/google/protobuf/util/Timestamps.java349
6 files changed, 1130 insertions, 697 deletions
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].
+ *
+ * <p>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].
+ *
+ * <p>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<String, Node> children = new TreeMap<String, Node>();
+ private static final class Node {
+ final SortedMap<String, Node> children = new TreeMap<String, Node>();
}
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<String> 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<? extends Message> type, String value) {
// TODO(xiaofeng): Consider using com.google.common.base.Splitter here instead.
- return fromStringList(
- type, Arrays.asList(value.split(FIELD_PATH_SEPARATOR_REGEX)));
+ 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<? extends Message> type, Iterable<String> paths) {
+ public static FieldMask fromStringList(Class<? extends Message> type, Iterable<String> 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);
}
@@ -146,15 +147,45 @@ public class FieldMaskUtil {
}
/**
+ * 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<String> paths = new ArrayList<String>(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<String> 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<? extends Message> 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<? extends Message> 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.<String, Descriptor>emptyMap());
+ private static final TypeRegistry EMPTY =
+ new TypeRegistry(Collections.<String, Descriptor>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<Descriptor> 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<String> files = new HashSet<String>();
- private Map<String, Descriptor> types =
- new HashMap<String, Descriptor>();
+ private Map<String, Descriptor> types = new HashMap<String, Descriptor>();
}
}
@@ -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<String, WellKnownTypePrinter>
- wellKnownTypePrinters = buildWellKnownTypePrinters();
-
- private static Map<String, WellKnownTypePrinter>
- buildWellKnownTypePrinters() {
- Map<String, WellKnownTypePrinter> printers =
- new HashMap<String, WellKnownTypePrinter>();
+ void print(PrinterImpl printer, MessageOrBuilder message) throws IOException;
+ }
+
+ private static final Map<String, WellKnownTypePrinter> wellKnownTypePrinters =
+ buildWellKnownTypePrinters();
+
+ private static Map<String, WellKnownTypePrinter> buildWellKnownTypePrinters() {
+ Map<String, WellKnownTypePrinter> printers = new HashMap<String, WellKnownTypePrinter>();
// 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<String, WellKnownTypeParser> wellKnownTypeParsers =
buildWellKnownTypeParsers();
-
- private static Map<String, WellKnownTypeParser>
- buildWellKnownTypeParsers() {
- Map<String, WellKnownTypeParser> parsers =
- new HashMap<String, WellKnownTypeParser>();
+
+ private static Map<String, WellKnownTypeParser> buildWellKnownTypeParsers() {
+ Map<String, WellKnownTypeParser> parsers = new HashMap<String, WellKnownTypeParser>();
// 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<Descriptor, Map<String, FieldDescriptor>> fieldNameMaps =
new HashMap<Descriptor, Map<String, FieldDescriptor>>();
-
- private Map<String, FieldDescriptor> getFieldNameMap(
- Descriptor descriptor) {
+
+ private Map<String, FieldDescriptor> getFieldNameMap(Descriptor descriptor) {
if (!fieldNameMaps.containsKey(descriptor)) {
- Map<String, FieldDescriptor> fieldNameMap =
- new HashMap<String, FieldDescriptor>();
+ Map<String, FieldDescriptor> fieldNameMap = new HashMap<String, FieldDescriptor>();
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<String, FieldDescriptor> fieldNameMap =
- getFieldNameMap(builder.getDescriptorForType());
+ Map<String, FieldDescriptor> fieldNameMap = getFieldNameMap(builder.getDescriptorForType());
for (Map.Entry<String, JsonElement> 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<String, JsonElement> 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<SimpleDateFormat> timestampFormat =
- new ThreadLocal<SimpleDateFormat>() {
- protected SimpleDateFormat initialValue() {
- return createTimestampFormat();
- }
- };
-
- private static SimpleDateFormat createTimestampFormat() {
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
- 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 {
* <p>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 {
* <p>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<SimpleDateFormat> timestampFormat =
+ new ThreadLocal<SimpleDateFormat>() {
+ protected SimpleDateFormat initialValue() {
+ return createTimestampFormat();
+ }
+ };
+
+ private static SimpleDateFormat createTimestampFormat() {
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
+ 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].
+ *
+ * <p>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].
+ *
+ * <p>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
+ *
+ * <p>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.
+ *
+ * <p>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.
+ *
+ * <p>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.
+ *
+ * <p>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);
+ }
+ }
+}