Skip to content

Commit bcda439

Browse files
committed
core: Simplify ServiceConfigUtil via utility methods
This should be functionally equivalent. More can be done, but wanted all the changes to be "obvious" when reading them.
1 parent c606519 commit bcda439

File tree

3 files changed

+266
-318
lines changed

3 files changed

+266
-318
lines changed

core/src/main/java/io/grpc/internal/DnsNameResolver.java

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -478,29 +478,20 @@ static ResolutionResults resolveAll(
478478

479479
@Nullable
480480
private static final Double getPercentageFromChoice(Map<String, ?> serviceConfigChoice) {
481-
if (!serviceConfigChoice.containsKey(SERVICE_CONFIG_CHOICE_PERCENTAGE_KEY)) {
482-
return null;
483-
}
484-
return JsonUtil.getDouble(serviceConfigChoice, SERVICE_CONFIG_CHOICE_PERCENTAGE_KEY);
481+
return JsonUtil.getNumber(serviceConfigChoice, SERVICE_CONFIG_CHOICE_PERCENTAGE_KEY);
485482
}
486483

487484
@Nullable
488485
private static final List<String> getClientLanguagesFromChoice(
489486
Map<String, ?> serviceConfigChoice) {
490-
if (!serviceConfigChoice.containsKey(SERVICE_CONFIG_CHOICE_CLIENT_LANGUAGE_KEY)) {
491-
return null;
492-
}
493-
return JsonUtil.checkStringList(
494-
JsonUtil.getList(serviceConfigChoice, SERVICE_CONFIG_CHOICE_CLIENT_LANGUAGE_KEY));
487+
return JsonUtil.getListOfStrings(
488+
serviceConfigChoice, SERVICE_CONFIG_CHOICE_CLIENT_LANGUAGE_KEY);
495489
}
496490

497491
@Nullable
498492
private static final List<String> getHostnamesFromChoice(Map<String, ?> serviceConfigChoice) {
499-
if (!serviceConfigChoice.containsKey(SERVICE_CONFIG_CHOICE_CLIENT_HOSTNAME_KEY)) {
500-
return null;
501-
}
502-
return JsonUtil.checkStringList(
503-
JsonUtil.getList(serviceConfigChoice, SERVICE_CONFIG_CHOICE_CLIENT_HOSTNAME_KEY));
493+
return JsonUtil.getListOfStrings(
494+
serviceConfigChoice, SERVICE_CONFIG_CHOICE_CLIENT_HOSTNAME_KEY);
504495
}
505496

506497
/**

core/src/main/java/io/grpc/internal/JsonUtil.java

Lines changed: 204 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,23 @@
1616

1717
package io.grpc.internal;
1818

19+
import static com.google.common.math.LongMath.checkedAdd;
20+
21+
import java.text.ParseException;
1922
import java.util.List;
2023
import java.util.Map;
24+
import java.util.concurrent.TimeUnit;
2125
import javax.annotation.Nullable;
2226

2327
/**
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.
2530
*/
2631
public class JsonUtil {
2732
/**
2833
* Gets a list from an object for the given key. If the key is not present, this returns null.
2934
* If the value is not a List, throws an exception.
3035
*/
31-
@SuppressWarnings("unchecked")
3236
@Nullable
3337
public static List<?> getList(Map<String, ?> obj, String key) {
3438
assert key != null;
@@ -43,6 +47,34 @@ public static List<?> getList(Map<String, ?> obj, String key) {
4347
return (List<?>) value;
4448
}
4549

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+
4678
/**
4779
* Gets an object from an object for the given key. If the key is not present, this returns null.
4880
* If the value is not a Map, throws an exception.
@@ -63,11 +95,11 @@ public static List<?> getList(Map<String, ?> obj, String key) {
6395
}
6496

6597
/**
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.
6799
* If the value is not a Double, throws an exception.
68100
*/
69101
@Nullable
70-
public static Double getDouble(Map<String, ?> obj, String key) {
102+
public static Double getNumber(Map<String, ?> obj, String key) {
71103
assert key != null;
72104
if (!obj.containsKey(key)) {
73105
return null;
@@ -80,6 +112,23 @@ public static Double getDouble(Map<String, ?> obj, String key) {
80112
return (Double) value;
81113
}
82114

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+
83132
/**
84133
* Gets a string from an object for the given key. If the key is not present, this returns null.
85134
* If the value is not a String, throws an exception.
@@ -98,6 +147,23 @@ public static String getString(Map<String, ?> obj, String key) {
98147
return (String) value;
99148
}
100149

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+
101167
/**
102168
* Gets a boolean from an object for the given key. If the key is not present, this returns null.
103169
* If the value is not a Boolean, throws an exception.
@@ -146,4 +212,138 @@ public static List<String> checkStringList(List<?> rawList) {
146212
}
147213
return (List<String>) rawList;
148214
}
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+
}
149349
}

0 commit comments

Comments
 (0)