16
16
17
17
package io .grpc .internal ;
18
18
19
+ import static com .google .common .math .LongMath .checkedAdd ;
20
+
21
+ import java .text .ParseException ;
19
22
import java .util .List ;
20
23
import java .util .Map ;
24
+ import java .util .concurrent .TimeUnit ;
21
25
import javax .annotation .Nullable ;
22
26
23
27
/**
24
- * Helper utility to work with JSON values in Java types.
28
+ * Helper utility to work with JSON values in Java types. Includes the JSON dialect used by
29
+ * Protocol Buffers.
25
30
*/
26
31
public class JsonUtil {
27
32
/**
28
33
* Gets a list from an object for the given key. If the key is not present, this returns null.
29
34
* If the value is not a List, throws an exception.
30
35
*/
31
- @ SuppressWarnings ("unchecked" )
32
36
@ Nullable
33
37
public static List <?> getList (Map <String , ?> obj , String key ) {
34
38
assert key != null ;
@@ -43,6 +47,34 @@ public static List<?> getList(Map<String, ?> obj, String key) {
43
47
return (List <?>) value ;
44
48
}
45
49
50
+ /**
51
+ * Gets a list from an object for the given key, and verifies all entries are objects. If the key
52
+ * is not present, this returns null. If the value is not a List or an entry is not an object,
53
+ * throws an exception.
54
+ */
55
+ @ Nullable
56
+ public static List <Map <String , ?>> getListOfObjects (Map <String , ?> obj , String key ) {
57
+ List <?> list = getList (obj , key );
58
+ if (list == null ) {
59
+ return null ;
60
+ }
61
+ return checkObjectList (list );
62
+ }
63
+
64
+ /**
65
+ * Gets a list from an object for the given key, and verifies all entries are strings. If the key
66
+ * is not present, this returns null. If the value is not a List or an entry is not a string,
67
+ * throws an exception.
68
+ */
69
+ @ Nullable
70
+ public static List <String > getListOfStrings (Map <String , ?> obj , String key ) {
71
+ List <?> list = getList (obj , key );
72
+ if (list == null ) {
73
+ return null ;
74
+ }
75
+ return checkStringList (list );
76
+ }
77
+
46
78
/**
47
79
* Gets an object from an object for the given key. If the key is not present, this returns null.
48
80
* If the value is not a Map, throws an exception.
@@ -63,11 +95,11 @@ public static List<?> getList(Map<String, ?> obj, String key) {
63
95
}
64
96
65
97
/**
66
- * Gets a double from an object for the given key. If the key is not present, this returns null.
98
+ * Gets a number from an object for the given key. If the key is not present, this returns null.
67
99
* If the value is not a Double, throws an exception.
68
100
*/
69
101
@ Nullable
70
- public static Double getDouble (Map <String , ?> obj , String key ) {
102
+ public static Double getNumber (Map <String , ?> obj , String key ) {
71
103
assert key != null ;
72
104
if (!obj .containsKey (key )) {
73
105
return null ;
@@ -80,6 +112,23 @@ public static Double getDouble(Map<String, ?> obj, String key) {
80
112
return (Double ) value ;
81
113
}
82
114
115
+ /**
116
+ * Gets a number from an object for the given key, casted to an integer. If the key is not
117
+ * present, this returns null. If the value is not a Double or loses precision when cast to an
118
+ * integer, throws an exception.
119
+ */
120
+ public static Integer getNumberAsInteger (Map <String , ?> obj , String key ) {
121
+ Double d = getNumber (obj , key );
122
+ if (d == null ) {
123
+ return null ;
124
+ }
125
+ int i = d .intValue ();
126
+ if (i != d ) {
127
+ throw new ClassCastException ("Number expected to be integer: " + d );
128
+ }
129
+ return i ;
130
+ }
131
+
83
132
/**
84
133
* Gets a string from an object for the given key. If the key is not present, this returns null.
85
134
* If the value is not a String, throws an exception.
@@ -98,6 +147,23 @@ public static String getString(Map<String, ?> obj, String key) {
98
147
return (String ) value ;
99
148
}
100
149
150
+ /**
151
+ * Gets a string from an object for the given key, parsed as a duration (defined by protobuf). If
152
+ * the key is not present, this returns null. If the value is not a String or not properly
153
+ * formatted, throws an exception.
154
+ */
155
+ public static Long getStringAsDuration (Map <String , ?> obj , String key ) {
156
+ String value = getString (obj , key );
157
+ if (value == null ) {
158
+ return null ;
159
+ }
160
+ try {
161
+ return parseDuration (value );
162
+ } catch (ParseException e ) {
163
+ throw new RuntimeException (e );
164
+ }
165
+ }
166
+
101
167
/**
102
168
* Gets a boolean from an object for the given key. If the key is not present, this returns null.
103
169
* If the value is not a Boolean, throws an exception.
@@ -146,4 +212,138 @@ public static List<String> checkStringList(List<?> rawList) {
146
212
}
147
213
return (List <String >) rawList ;
148
214
}
215
+
216
+ private static final long DURATION_SECONDS_MIN = -315576000000L ;
217
+ private static final long DURATION_SECONDS_MAX = 315576000000L ;
218
+
219
+ /**
220
+ * Parse from a string to produce a duration. Copy of
221
+ * {@link com.google.protobuf.util.Durations#parse}.
222
+ *
223
+ * @return A Duration parsed from the string.
224
+ * @throws ParseException if parsing fails.
225
+ */
226
+ private static long parseDuration (String value ) throws ParseException {
227
+ // Must ended with "s".
228
+ if (value .isEmpty () || value .charAt (value .length () - 1 ) != 's' ) {
229
+ throw new ParseException ("Invalid duration string: " + value , 0 );
230
+ }
231
+ boolean negative = false ;
232
+ if (value .charAt (0 ) == '-' ) {
233
+ negative = true ;
234
+ value = value .substring (1 );
235
+ }
236
+ String secondValue = value .substring (0 , value .length () - 1 );
237
+ String nanoValue = "" ;
238
+ int pointPosition = secondValue .indexOf ('.' );
239
+ if (pointPosition != -1 ) {
240
+ nanoValue = secondValue .substring (pointPosition + 1 );
241
+ secondValue = secondValue .substring (0 , pointPosition );
242
+ }
243
+ long seconds = Long .parseLong (secondValue );
244
+ int nanos = nanoValue .isEmpty () ? 0 : parseNanos (nanoValue );
245
+ if (seconds < 0 ) {
246
+ throw new ParseException ("Invalid duration string: " + value , 0 );
247
+ }
248
+ if (negative ) {
249
+ seconds = -seconds ;
250
+ nanos = -nanos ;
251
+ }
252
+ try {
253
+ return normalizedDuration (seconds , nanos );
254
+ } catch (IllegalArgumentException e ) {
255
+ throw new ParseException ("Duration value is out of range." , 0 );
256
+ }
257
+ }
258
+
259
+ /**
260
+ * Copy of {@link com.google.protobuf.util.Timestamps#parseNanos}.
261
+ */
262
+ private static int parseNanos (String value ) throws ParseException {
263
+ int result = 0 ;
264
+ for (int i = 0 ; i < 9 ; ++i ) {
265
+ result = result * 10 ;
266
+ if (i < value .length ()) {
267
+ if (value .charAt (i ) < '0' || value .charAt (i ) > '9' ) {
268
+ throw new ParseException ("Invalid nanoseconds." , 0 );
269
+ }
270
+ result += value .charAt (i ) - '0' ;
271
+ }
272
+ }
273
+ return result ;
274
+ }
275
+
276
+ private static final long NANOS_PER_SECOND = TimeUnit .SECONDS .toNanos (1 );
277
+
278
+ /**
279
+ * Copy of {@link com.google.protobuf.util.Durations#normalizedDuration}.
280
+ */
281
+ @ SuppressWarnings ("NarrowingCompoundAssignment" )
282
+ private static long normalizedDuration (long seconds , int nanos ) {
283
+ if (nanos <= -NANOS_PER_SECOND || nanos >= NANOS_PER_SECOND ) {
284
+ seconds = checkedAdd (seconds , nanos / NANOS_PER_SECOND );
285
+ nanos %= NANOS_PER_SECOND ;
286
+ }
287
+ if (seconds > 0 && nanos < 0 ) {
288
+ nanos += NANOS_PER_SECOND ; // no overflow since nanos is negative (and we're adding)
289
+ seconds --; // no overflow since seconds is positive (and we're decrementing)
290
+ }
291
+ if (seconds < 0 && nanos > 0 ) {
292
+ nanos -= NANOS_PER_SECOND ; // no overflow since nanos is positive (and we're subtracting)
293
+ seconds ++; // no overflow since seconds is negative (and we're incrementing)
294
+ }
295
+ if (!durationIsValid (seconds , nanos )) {
296
+ throw new IllegalArgumentException (String .format (
297
+ "Duration is not valid. See proto definition for valid values. "
298
+ + "Seconds (%s) must be in range [-315,576,000,000, +315,576,000,000]. "
299
+ + "Nanos (%s) must be in range [-999,999,999, +999,999,999]. "
300
+ + "Nanos must have the same sign as seconds" , seconds , nanos ));
301
+ }
302
+ return saturatedAdd (TimeUnit .SECONDS .toNanos (seconds ), nanos );
303
+ }
304
+
305
+ /**
306
+ * Returns true if the given number of seconds and nanos is a valid {@code Duration}. The {@code
307
+ * seconds} value must be in the range [-315,576,000,000, +315,576,000,000]. The {@code nanos}
308
+ * value must be in the range [-999,999,999, +999,999,999].
309
+ *
310
+ * <p><b>Note:</b> Durations less than one second are represented with a 0 {@code seconds} field
311
+ * and a positive or negative {@code nanos} field. For durations of one second or more, a non-zero
312
+ * value for the {@code nanos} field must be of the same sign as the {@code seconds} field.
313
+ *
314
+ * <p>Copy of {@link com.google.protobuf.util.Duration#isValid}.</p>
315
+ */
316
+ private static boolean durationIsValid (long seconds , int nanos ) {
317
+ if (seconds < DURATION_SECONDS_MIN || seconds > DURATION_SECONDS_MAX ) {
318
+ return false ;
319
+ }
320
+ if (nanos < -999999999L || nanos >= NANOS_PER_SECOND ) {
321
+ return false ;
322
+ }
323
+ if (seconds < 0 || nanos < 0 ) {
324
+ if (seconds > 0 || nanos > 0 ) {
325
+ return false ;
326
+ }
327
+ }
328
+ return true ;
329
+ }
330
+
331
+ /**
332
+ * Returns the sum of {@code a} and {@code b} unless it would overflow or underflow in which case
333
+ * {@code Long.MAX_VALUE} or {@code Long.MIN_VALUE} is returned, respectively.
334
+ *
335
+ * <p>Copy of {@link com.google.common.math.LongMath#saturatedAdd}.</p>
336
+ *
337
+ */
338
+ @ SuppressWarnings ("ShortCircuitBoolean" )
339
+ private static long saturatedAdd (long a , long b ) {
340
+ long naiveSum = a + b ;
341
+ if ((a ^ b ) < 0 | (a ^ naiveSum ) >= 0 ) {
342
+ // If a and b have different signs or a has the same sign as the result then there was no
343
+ // overflow, return.
344
+ return naiveSum ;
345
+ }
346
+ // we did over/under flow, if the sign is negative we should return MAX otherwise MIN
347
+ return Long .MAX_VALUE + ((naiveSum >>> (Long .SIZE - 1 )) ^ 1 );
348
+ }
149
349
}
0 commit comments