aboutsummaryrefslogtreecommitdiff
path: root/java/util
diff options
context:
space:
mode:
authorBo Yang <teboring@google.com>2016-09-19 13:45:07 -0700
committerBo Yang <teboring@google.com>2016-10-10 11:23:36 -0700
commitcc8ca5b6a5478b40546d4206392eb1471454460d (patch)
treec0b45abfa16d7d373a6ea8f7fe50f1de00ab938e /java/util
parent337a028bb65ccca4dda768695950b5aba53ae2c9 (diff)
downloadprotobuf-cc8ca5b6a5478b40546d4206392eb1471454460d.tar.gz
protobuf-cc8ca5b6a5478b40546d4206392eb1471454460d.tar.bz2
protobuf-cc8ca5b6a5478b40546d4206392eb1471454460d.zip
Integrate internal changes
Diffstat (limited to 'java/util')
-rw-r--r--java/util/src/main/java/com/google/protobuf/util/Durations.java182
-rw-r--r--java/util/src/main/java/com/google/protobuf/util/JsonFormat.java15
-rw-r--r--java/util/src/main/java/com/google/protobuf/util/Timestamps.java207
-rw-r--r--java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java110
-rw-r--r--java/util/src/test/java/com/google/protobuf/util/TimeUtilTest.java7
-rw-r--r--java/util/src/test/proto/com/google/protobuf/util/json_test.proto1
6 files changed, 363 insertions, 159 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
index 5fe6ebca..9333168d 100644
--- a/java/util/src/main/java/com/google/protobuf/util/Durations.java
+++ b/java/util/src/main/java/com/google/protobuf/util/Durations.java
@@ -30,50 +30,86 @@
package com.google.protobuf.util;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.math.IntMath.checkedAdd;
+import static com.google.common.math.IntMath.checkedSubtract;
+import static com.google.common.math.LongMath.checkedAdd;
+import static com.google.common.math.LongMath.checkedMultiply;
+import static com.google.common.math.LongMath.checkedSubtract;
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.common.collect.ComparisonChain;
import com.google.protobuf.Duration;
-
import java.text.ParseException;
+import java.util.Comparator;
/**
- * Utilities to help create/manipulate {@code protobuf/duration.proto}.
+ * Utilities to help create/manipulate {@code protobuf/duration.proto}. All operations throw an
+ * {@link IllegalArgumentException} if the input(s) are not {@linkplain #isValid(Duration) valid}.
*/
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?
+ /** A constant holding the minimum valid {@link Duration}, approximately {@code -10,000} years. */
+ public static final Duration MIN_VALUE =
+ Duration.newBuilder().setSeconds(DURATION_SECONDS_MIN).setNanos(-999999999).build();
+
+ /** A constant holding the maximum valid {@link Duration}, approximately {@code +10,000} years. */
+ public static final Duration MAX_VALUE =
+ Duration.newBuilder().setSeconds(DURATION_SECONDS_MAX).setNanos(999999999).build();
private Durations() {}
+ private static final Comparator<Duration> COMPARATOR =
+ new Comparator<Duration>() {
+ @Override
+ public int compare(Duration d1, Duration d2) {
+ checkValid(d1);
+ checkValid(d2);
+
+ return ComparisonChain.start()
+ .compare(d1.getSeconds(), d2.getSeconds())
+ .compare(d1.getNanos(), d2.getNanos())
+ .result();
+ }
+ };
+
+ /**
+ * Returns a {@link Comparator} for {@link Duration}s which sorts in increasing chronological
+ * order. Nulls and invalid {@link Duration}s are not allowed (see {@link #isValid}).
+ */
+ public static Comparator<Duration> comparator() {
+ return COMPARATOR;
+ }
+
/**
* 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.
+ * <p><b>Note:</b> 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].
+ * 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.
+ * <p><b>Note:</b> 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) {
+ public static boolean isValid(long seconds, int nanos) {
if (seconds < DURATION_SECONDS_MIN || seconds > DURATION_SECONDS_MAX) {
return false;
}
@@ -88,35 +124,35 @@ public final class Durations {
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));
- }
+ /** Throws an {@link IllegalArgumentException} if the given {@link Duration} is not valid. */
+ public static Duration checkValid(Duration duration) {
+ long seconds = duration.getSeconds();
+ int nanos = duration.getNanos();
+ checkArgument(
+ isValid(seconds, nanos),
+ "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);
+ return duration;
}
/**
- * 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
+ * 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.
+ * @throws IllegalArgumentException if the given duration is not in the valid range.
*/
public static String toString(Duration duration) {
+ checkValid(duration);
+
long seconds = duration.getSeconds();
int nanos = duration.getNanos();
- checkValid(seconds, nanos);
StringBuilder result = new StringBuilder();
if (seconds < 0 || nanos < 0) {
@@ -172,9 +208,20 @@ public final class Durations {
}
}
+ /** Create a Duration from the number of seconds. */
+ public static Duration fromSeconds(long seconds) {
+ return normalizedDuration(seconds, 0);
+ }
+
/**
- * Create a Duration from the number of milliseconds.
+ * Convert a Duration to the number of seconds. The result will be rounded towards 0 to the
+ * nearest second. E.g., if the duration represents -1 nanosecond, it will be rounded to 0.
*/
+ public static long toSeconds(Duration duration) {
+ return checkValid(duration).getSeconds();
+ }
+
+ /** Create a Duration from the number of milliseconds. */
public static Duration fromMillis(long milliseconds) {
return normalizedDuration(
milliseconds / MILLIS_PER_SECOND,
@@ -182,17 +229,17 @@ public final class Durations {
}
/**
- * 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.
+ * 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;
+ checkValid(duration);
+ return checkedAdd(
+ checkedMultiply(duration.getSeconds(), MILLIS_PER_SECOND),
+ duration.getNanos() / NANOS_PER_MILLISECOND);
}
- /**
- * Create a Duration from the number of microseconds.
- */
+ /** Create a Duration from the number of microseconds. */
public static Duration fromMicros(long microseconds) {
return normalizedDuration(
microseconds / MICROS_PER_SECOND,
@@ -200,57 +247,60 @@ public final class Durations {
}
/**
- * 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.
+ * 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;
+ checkValid(duration);
+ return checkedAdd(
+ checkedMultiply(duration.getSeconds(), MICROS_PER_SECOND),
+ duration.getNanos() / NANOS_PER_MICROSECOND);
}
- /**
- * Create a Duration from the number of nanoseconds.
- */
+ /** 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.
- */
+ /** Convert a Duration to the number of nanoseconds. */
public static long toNanos(Duration duration) {
- return duration.getSeconds() * NANOS_PER_SECOND + duration.getNanos();
+ checkValid(duration);
+ return checkedAdd(
+ checkedMultiply(duration.getSeconds(), NANOS_PER_SECOND), duration.getNanos());
}
- /**
- * Add two durations.
- */
+ /** Add two durations. */
public static Duration add(Duration d1, Duration d2) {
- return normalizedDuration(d1.getSeconds() + d2.getSeconds(), d1.getNanos() + d2.getNanos());
+ checkValid(d1);
+ checkValid(d2);
+ return normalizedDuration(
+ checkedAdd(d1.getSeconds(), d2.getSeconds()), checkedAdd(d1.getNanos(), d2.getNanos()));
}
- /**
- * Subtract a duration from another.
- */
+ /** Subtract a duration from another. */
public static Duration subtract(Duration d1, Duration d2) {
- return normalizedDuration(d1.getSeconds() - d2.getSeconds(), d1.getNanos() - d2.getNanos());
+ checkValid(d1);
+ checkValid(d2);
+ return normalizedDuration(
+ checkedSubtract(d1.getSeconds(), d2.getSeconds()),
+ checkedSubtract(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;
+ seconds = checkedAdd(seconds, nanos / NANOS_PER_SECOND);
nanos %= NANOS_PER_SECOND;
}
if (seconds > 0 && nanos < 0) {
- nanos += NANOS_PER_SECOND;
- seconds -= 1;
+ nanos += NANOS_PER_SECOND; // no overflow since nanos is negative (and we're adding)
+ seconds--; // no overflow since seconds is positive (and we're decrementing)
}
if (seconds < 0 && nanos > 0) {
- nanos -= NANOS_PER_SECOND;
- seconds += 1;
+ nanos -= NANOS_PER_SECOND; // no overflow since nanos is positive (and we're subtracting)
+ seconds++; // no overflow since seconds is negative (and we're incrementing)
}
- checkValid(seconds, nanos);
- return Duration.newBuilder().setSeconds(seconds).setNanos(nanos).build();
+ Duration duration = Duration.newBuilder().setSeconds(seconds).setNanos(nanos).build();
+ return checkValid(duration);
}
}
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 d4db9c80..6361b4ac 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
@@ -640,6 +640,10 @@ public class JsonFormat {
/** Prints google.protobuf.Any */
private void printAny(MessageOrBuilder message) throws IOException {
+ if (Any.getDefaultInstance().equals(message)) {
+ generator.print("{}");
+ return;
+ }
Descriptor descriptor = message.getDescriptorForType();
FieldDescriptor typeUrlField = descriptor.findFieldByName("type_url");
FieldDescriptor valueField = descriptor.findFieldByName("value");
@@ -1235,6 +1239,9 @@ public class JsonFormat {
throw new InvalidProtocolBufferException("Expect message object but got: " + json);
}
JsonObject object = (JsonObject) json;
+ if (object.entrySet().isEmpty()) {
+ return; // builder never modified, so it will end up building the default instance of Any
+ }
JsonElement typeUrlElement = object.get("@type");
if (typeUrlElement == null) {
throw new InvalidProtocolBufferException("Missing type url when parsing: " + json);
@@ -1327,6 +1334,9 @@ public class JsonFormat {
Message.Builder listBuilder = builder.newBuilderForField(field);
merge(json, listBuilder);
builder.setField(field, listBuilder.build());
+ } else if (json instanceof JsonNull) {
+ builder.setField(
+ type.findFieldByName("null_value"), NullValue.NULL_VALUE.getValueDescriptor());
} else {
throw new IllegalStateException("Unexpected json data: " + json);
}
@@ -1620,11 +1630,6 @@ public class JsonFormat {
}
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()));
}
diff --git a/java/util/src/main/java/com/google/protobuf/util/Timestamps.java b/java/util/src/main/java/com/google/protobuf/util/Timestamps.java
index f1f202da..52b1ab98 100644
--- a/java/util/src/main/java/com/google/protobuf/util/Timestamps.java
+++ b/java/util/src/main/java/com/google/protobuf/util/Timestamps.java
@@ -30,19 +30,29 @@
package com.google.protobuf.util;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.math.IntMath.checkedAdd;
+import static com.google.common.math.IntMath.checkedSubtract;
+import static com.google.common.math.LongMath.checkedAdd;
+import static com.google.common.math.LongMath.checkedMultiply;
+import static com.google.common.math.LongMath.checkedSubtract;
+
+import com.google.common.collect.ComparisonChain;
import com.google.protobuf.Duration;
import com.google.protobuf.Timestamp;
-
import java.text.ParseException;
import java.text.SimpleDateFormat;
+import java.util.Comparator;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;
/**
- * Utilities to help create/manipulate {@code protobuf/timestamp.proto}.
+ * Utilities to help create/manipulate {@code protobuf/timestamp.proto}. All operations throw an
+ * {@link IllegalArgumentException} if the input(s) are not {@linkplain #isValid(Timestamp) valid}.
*/
public final class Timestamps {
+
// Timestamp for "0001-01-01T00:00:00Z"
static final long TIMESTAMP_SECONDS_MIN = -62135596800L;
@@ -55,10 +65,19 @@ public final class Timestamps {
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?
+ /** A constant holding the minimum valid {@link Timestamp}, {@code 0001-01-01T00:00:00Z}. */
+ public static final Timestamp MIN_VALUE =
+ Timestamp.newBuilder().setSeconds(TIMESTAMP_SECONDS_MIN).setNanos(0).build();
+
+ /**
+ * A constant holding the maximum valid {@link Timestamp}, {@code 9999-12-31T23:59:59.999999999Z}.
+ */
+ public static final Timestamp MAX_VALUE =
+ Timestamp.newBuilder().setSeconds(TIMESTAMP_SECONDS_MAX).setNanos(999999999).build();
private static final ThreadLocal<SimpleDateFormat> timestampFormat =
new ThreadLocal<SimpleDateFormat>() {
+ @Override
protected SimpleDateFormat initialValue() {
return createTimestampFormat();
}
@@ -76,28 +95,50 @@ public final class Timestamps {
private Timestamps() {}
+ private static final Comparator<Timestamp> COMPARATOR =
+ new Comparator<Timestamp>() {
+ @Override
+ public int compare(Timestamp t1, Timestamp t2) {
+ checkValid(t1);
+ checkValid(t2);
+
+ return ComparisonChain.start()
+ .compare(t1.getSeconds(), t2.getSeconds())
+ .compare(t1.getNanos(), t2.getNanos())
+ .result();
+ }
+ };
+
+ /**
+ * Returns a {@link Comparator} for {@link Timestamp}s which sorts in increasing chronological
+ * order. Nulls and invalid {@link Timestamp}s are not allowed (see {@link #isValid}).
+ */
+ public static Comparator<Timestamp> comparator() {
+ return COMPARATOR;
+ }
+
/**
* 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.
+ * <p><b>Note:</b> Negative second values with fractional seconds must still have non-negative
+ * nanos values that count 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
+ * 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.
+ * <p><b>Note:</b> Negative second values with fractional seconds must still have non-negative
+ * nanos values that count forward in time.
*/
- public static boolean isValid(long seconds, long nanos) {
+ public static boolean isValid(long seconds, int nanos) {
if (seconds < TIMESTAMP_SECONDS_MIN || seconds > TIMESTAMP_SECONDS_MAX) {
return false;
}
@@ -107,37 +148,37 @@ public final class Timestamps {
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));
- }
+ /** Throws an {@link IllegalArgumentException} if the given {@link Timestamp} is not valid. */
+ public static Timestamp checkValid(Timestamp timestamp) {
+ long seconds = timestamp.getSeconds();
+ int nanos = timestamp.getNanos();
+ checkArgument(
+ isValid(seconds, nanos),
+ "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);
+ return timestamp;
}
/**
- * 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
+ * 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.
+ * @throws IllegalArgumentException if the given timestamp is not in the valid range.
*/
public static String toString(Timestamp timestamp) {
+ checkValid(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);
@@ -152,10 +193,9 @@ public final class Timestamps {
}
/**
- * 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.
+ * 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"
*
@@ -210,13 +250,26 @@ public final class Timestamps {
try {
return normalizedTimestamp(seconds, nanos);
} catch (IllegalArgumentException e) {
- throw new ParseException("Failed to parse timestmap: timestamp is out of range.", 0);
+ throw new ParseException("Failed to parse timestamp: timestamp is out of range.", 0);
}
}
+ /** Create a Timestamp from the number of seconds elapsed from the epoch. */
+ public static Timestamp fromSeconds(long seconds) {
+ return normalizedTimestamp(seconds, 0);
+ }
+
/**
- * Create a Timestamp from the number of milliseconds elapsed from the epoch.
+ * Convert a Timestamp to the number of seconds elapsed from the epoch.
+ *
+ * <p>The result will be rounded down to the nearest second. E.g., if the timestamp represents
+ * "1969-12-31T23:59:59.999999999Z", it will be rounded to -1 second.
*/
+ public static long toSeconds(Timestamp timestamp) {
+ return checkValid(timestamp).getSeconds();
+ }
+
+ /** Create a Timestamp from the number of milliseconds elapsed from the epoch. */
public static Timestamp fromMillis(long milliseconds) {
return normalizedTimestamp(
milliseconds / MILLIS_PER_SECOND,
@@ -226,18 +279,17 @@ public final class Timestamps {
/**
* 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.
+ * <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;
+ checkValid(timestamp);
+ return checkedAdd(
+ checkedMultiply(timestamp.getSeconds(), MILLIS_PER_SECOND),
+ timestamp.getNanos() / NANOS_PER_MILLISECOND);
}
- /**
- * Create a Timestamp from the number of microseconds elapsed from the epoch.
- */
+ /** Create a Timestamp from the number of microseconds elapsed from the epoch. */
public static Timestamp fromMicros(long microseconds) {
return normalizedTimestamp(
microseconds / MICROS_PER_SECOND,
@@ -247,65 +299,67 @@ public final class Timestamps {
/**
* 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.
+ * <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;
+ checkValid(timestamp);
+ return checkedAdd(
+ checkedMultiply(timestamp.getSeconds(), MICROS_PER_SECOND),
+ timestamp.getNanos() / NANOS_PER_MICROSECOND);
}
- /**
- * Create a Timestamp from the number of nanoseconds elapsed from the epoch.
- */
+ /** 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.
- */
+ /** 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();
+ checkValid(timestamp);
+ return checkedAdd(
+ checkedMultiply(timestamp.getSeconds(), NANOS_PER_SECOND), timestamp.getNanos());
}
- /**
- * Calculate the difference between two timestamps.
- */
+ /** Calculate the difference between two timestamps. */
public static Duration between(Timestamp from, Timestamp to) {
+ checkValid(from);
+ checkValid(to);
return Durations.normalizedDuration(
- to.getSeconds() - from.getSeconds(), to.getNanos() - from.getNanos());
+ checkedSubtract(to.getSeconds(), from.getSeconds()),
+ checkedSubtract(to.getNanos(), from.getNanos()));
}
- /**
- * Add a duration to a timestamp.
- */
+ /** Add a duration to a timestamp. */
public static Timestamp add(Timestamp start, Duration length) {
+ checkValid(start);
+ Durations.checkValid(length);
return normalizedTimestamp(
- start.getSeconds() + length.getSeconds(), start.getNanos() + length.getNanos());
+ checkedAdd(start.getSeconds(), length.getSeconds()),
+ checkedAdd(start.getNanos(), length.getNanos()));
}
- /**
- * Subtract a duration from a timestamp.
- */
+ /** Subtract a duration from a timestamp. */
public static Timestamp subtract(Timestamp start, Duration length) {
+ checkValid(start);
+ Durations.checkValid(length);
return normalizedTimestamp(
- start.getSeconds() - length.getSeconds(), start.getNanos() - length.getNanos());
+ checkedSubtract(start.getSeconds(), length.getSeconds()),
+ checkedSubtract(start.getNanos(), length.getNanos()));
}
- private static Timestamp normalizedTimestamp(long seconds, int nanos) {
+ static Timestamp normalizedTimestamp(long seconds, int nanos) {
if (nanos <= -NANOS_PER_SECOND || nanos >= NANOS_PER_SECOND) {
- seconds += nanos / NANOS_PER_SECOND;
+ seconds = checkedAdd(seconds, nanos / NANOS_PER_SECOND);
nanos %= NANOS_PER_SECOND;
}
if (nanos < 0) {
- nanos += NANOS_PER_SECOND;
- seconds -= 1;
+ nanos += NANOS_PER_SECOND; // no overflow since nanos is negative (and we're adding)
+ seconds = checkedSubtract(seconds, 1);
}
- checkValid(seconds, nanos);
- return Timestamp.newBuilder().setSeconds(seconds).setNanos(nanos).build();
+ Timestamp timestamp = Timestamp.newBuilder().setSeconds(seconds).setNanos(nanos).build();
+ return checkValid(timestamp);
}
private static long parseTimezoneOffset(String value) throws ParseException {
@@ -324,7 +378,7 @@ public final class Timestamps {
result = result * 10;
if (i < value.length()) {
if (value.charAt(i) < '0' || value.charAt(i) > '9') {
- throw new ParseException("Invalid nanosecnds.", 0);
+ throw new ParseException("Invalid nanoseconds.", 0);
}
result += value.charAt(i) - '0';
}
@@ -332,11 +386,8 @@ public final class Timestamps {
return result;
}
- /**
- * Format the nano part of a timestamp or a duration.
- */
+ /** 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);
diff --git a/java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java b/java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java
index c11114c0..32739d44 100644
--- a/java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java
+++ b/java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java
@@ -60,12 +60,10 @@ import com.google.protobuf.util.JsonTestProto.TestOneof;
import com.google.protobuf.util.JsonTestProto.TestStruct;
import com.google.protobuf.util.JsonTestProto.TestTimestamp;
import com.google.protobuf.util.JsonTestProto.TestWrappers;
-
-import junit.framework.TestCase;
-
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
+import junit.framework.TestCase;
public class JsonFormatTest extends TestCase {
private void setAllFields(TestAllTypes.Builder builder) {
@@ -819,6 +817,15 @@ public class JsonFormatTest extends TestCase {
printer.print(message));
assertRoundTripEquals(message, registry);
+ TestAny messageWithDefaultAnyValue =
+ TestAny.newBuilder().setAnyValue(Any.getDefaultInstance()).build();
+ assertEquals(
+ "{\n"
+ + " \"anyValue\": {}\n"
+ + "}",
+ printer.print(messageWithDefaultAnyValue));
+ assertRoundTripEquals(messageWithDefaultAnyValue, registry);
+
// Well-known types have a special formatting when embedded in Any.
//
// 1. Any in Any.
@@ -952,6 +959,8 @@ public class JsonFormatTest extends TestCase {
+ "}",
printer.print(anyMessage));
assertRoundTripEquals(anyMessage, registry);
+
+ // 7. Value (number type) in Any
Value.Builder valueBuilder = Value.newBuilder();
valueBuilder.setNumberValue(1);
anyMessage = Any.pack(valueBuilder.build());
@@ -962,6 +971,95 @@ public class JsonFormatTest extends TestCase {
+ "}",
printer.print(anyMessage));
assertRoundTripEquals(anyMessage, registry);
+
+ // 8. Value (null type) in Any
+ anyMessage = Any.pack(Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build());
+ assertEquals(
+ "{\n"
+ + " \"@type\": \"type.googleapis.com/google.protobuf.Value\",\n"
+ + " \"value\": null\n"
+ + "}",
+ printer.print(anyMessage));
+ assertRoundTripEquals(anyMessage, registry);
+ }
+
+ public void testAnyInMaps() throws Exception {
+ JsonFormat.TypeRegistry registry =
+ JsonFormat.TypeRegistry.newBuilder().add(TestAllTypes.getDescriptor()).build();
+ JsonFormat.Printer printer = JsonFormat.printer().usingTypeRegistry(registry);
+
+ TestAny.Builder testAny = TestAny.newBuilder();
+ testAny.putAnyMap("int32_wrapper", Any.pack(Int32Value.newBuilder().setValue(123).build()));
+ testAny.putAnyMap("int64_wrapper", Any.pack(Int64Value.newBuilder().setValue(456).build()));
+ testAny.putAnyMap("timestamp", Any.pack(Timestamps.parse("1969-12-31T23:59:59Z")));
+ testAny.putAnyMap("duration", Any.pack(Durations.parse("12345.1s")));
+ testAny.putAnyMap("field_mask", Any.pack(FieldMaskUtil.fromString("foo.bar,baz")));
+ Value numberValue = Value.newBuilder().setNumberValue(1.125).build();
+ Struct.Builder struct = Struct.newBuilder();
+ struct.putFields("number", numberValue);
+ testAny.putAnyMap("struct", Any.pack(struct.build()));
+ Value nullValue = Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build();
+ testAny.putAnyMap(
+ "list_value",
+ Any.pack(ListValue.newBuilder().addValues(numberValue).addValues(nullValue).build()));
+ testAny.putAnyMap("number_value", Any.pack(numberValue));
+ testAny.putAnyMap("any_value_number", Any.pack(Any.pack(numberValue)));
+ testAny.putAnyMap("any_value_default", Any.pack(Any.getDefaultInstance()));
+ testAny.putAnyMap("default", Any.getDefaultInstance());
+
+ assertEquals(
+ "{\n"
+ + " \"anyMap\": {\n"
+ + " \"int32_wrapper\": {\n"
+ + " \"@type\": \"type.googleapis.com/google.protobuf.Int32Value\",\n"
+ + " \"value\": 123\n"
+ + " },\n"
+ + " \"int64_wrapper\": {\n"
+ + " \"@type\": \"type.googleapis.com/google.protobuf.Int64Value\",\n"
+ + " \"value\": \"456\"\n"
+ + " },\n"
+ + " \"timestamp\": {\n"
+ + " \"@type\": \"type.googleapis.com/google.protobuf.Timestamp\",\n"
+ + " \"value\": \"1969-12-31T23:59:59Z\"\n"
+ + " },\n"
+ + " \"duration\": {\n"
+ + " \"@type\": \"type.googleapis.com/google.protobuf.Duration\",\n"
+ + " \"value\": \"12345.100s\"\n"
+ + " },\n"
+ + " \"field_mask\": {\n"
+ + " \"@type\": \"type.googleapis.com/google.protobuf.FieldMask\",\n"
+ + " \"value\": \"foo.bar,baz\"\n"
+ + " },\n"
+ + " \"struct\": {\n"
+ + " \"@type\": \"type.googleapis.com/google.protobuf.Struct\",\n"
+ + " \"value\": {\n"
+ + " \"number\": 1.125\n"
+ + " }\n"
+ + " },\n"
+ + " \"list_value\": {\n"
+ + " \"@type\": \"type.googleapis.com/google.protobuf.ListValue\",\n"
+ + " \"value\": [1.125, null]\n"
+ + " },\n"
+ + " \"number_value\": {\n"
+ + " \"@type\": \"type.googleapis.com/google.protobuf.Value\",\n"
+ + " \"value\": 1.125\n"
+ + " },\n"
+ + " \"any_value_number\": {\n"
+ + " \"@type\": \"type.googleapis.com/google.protobuf.Any\",\n"
+ + " \"value\": {\n"
+ + " \"@type\": \"type.googleapis.com/google.protobuf.Value\",\n"
+ + " \"value\": 1.125\n"
+ + " }\n"
+ + " },\n"
+ + " \"any_value_default\": {\n"
+ + " \"@type\": \"type.googleapis.com/google.protobuf.Any\",\n"
+ + " \"value\": {}\n"
+ + " },\n"
+ + " \"default\": {}\n"
+ + " }\n"
+ + "}",
+ printer.print(testAny.build()));
+ assertRoundTripEquals(testAny.build(), registry);
}
public void testParserMissingTypeUrl() throws Exception {
@@ -1016,8 +1114,10 @@ public class JsonFormatTest extends TestCase {
public void testParserRejectInvalidBase64() throws Exception {
assertRejects("optionalBytes", "!@#$");
- // We use standard BASE64 with paddings.
- assertRejects("optionalBytes", "AQI");
+ }
+
+ public void testParserAcceptBase64Variants() throws Exception {
+ assertAccepts("optionalBytes", "AQI");
}
public void testParserRejectInvalidEnumValue() throws Exception {
diff --git a/java/util/src/test/java/com/google/protobuf/util/TimeUtilTest.java b/java/util/src/test/java/com/google/protobuf/util/TimeUtilTest.java
index a41528ec..5af83d88 100644
--- a/java/util/src/test/java/com/google/protobuf/util/TimeUtilTest.java
+++ b/java/util/src/test/java/com/google/protobuf/util/TimeUtilTest.java
@@ -32,14 +32,11 @@ package com.google.protobuf.util;
import com.google.protobuf.Duration;
import com.google.protobuf.Timestamp;
-
-import junit.framework.TestCase;
-
-import org.junit.Assert;
-
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
+import junit.framework.TestCase;
+import org.junit.Assert;
/** Unit tests for {@link TimeUtil}. */
public class TimeUtilTest extends TestCase {
diff --git a/java/util/src/test/proto/com/google/protobuf/util/json_test.proto b/java/util/src/test/proto/com/google/protobuf/util/json_test.proto
index 4bf223f2..bd22f65a 100644
--- a/java/util/src/test/proto/com/google/protobuf/util/json_test.proto
+++ b/java/util/src/test/proto/com/google/protobuf/util/json_test.proto
@@ -195,6 +195,7 @@ message TestStruct {
message TestAny {
google.protobuf.Any any_value = 1;
+ map<string, google.protobuf.Any> any_map = 2;
}
message TestCustomJsonName {