1 /* 2 * Copyright 2019 The gRPC Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package io.grpc.internal; 18 19 import static com.google.common.math.LongMath.checkedAdd; 20 21 import java.text.ParseException; 22 import java.util.List; 23 import java.util.Locale; 24 import java.util.Map; 25 import java.util.concurrent.TimeUnit; 26 import javax.annotation.Nullable; 27 28 /** 29 * Helper utility to work with JSON values in Java types. Includes the JSON dialect used by 30 * Protocol Buffers. 31 */ 32 public class JsonUtil { 33 /** 34 * Gets a list from an object for the given key. If the key is not present, this returns null. 35 * If the value is not a List, throws an exception. 36 */ 37 @Nullable getList(Map<String, ?> obj, String key)38 public static List<?> getList(Map<String, ?> obj, String key) { 39 assert key != null; 40 if (!obj.containsKey(key)) { 41 return null; 42 } 43 Object value = obj.get(key); 44 if (!(value instanceof List)) { 45 throw new ClassCastException( 46 String.format("value '%s' for key '%s' in '%s' is not List", value, key, obj)); 47 } 48 return (List<?>) value; 49 } 50 51 /** 52 * Gets a list from an object for the given key, and verifies all entries are objects. If the key 53 * is not present, this returns null. If the value is not a List or an entry is not an object, 54 * throws an exception. 55 */ 56 @Nullable getListOfObjects(Map<String, ?> obj, String key)57 public static List<Map<String, ?>> getListOfObjects(Map<String, ?> obj, String key) { 58 List<?> list = getList(obj, key); 59 if (list == null) { 60 return null; 61 } 62 return checkObjectList(list); 63 } 64 65 /** 66 * Gets a list from an object for the given key, and verifies all entries are strings. If the key 67 * is not present, this returns null. If the value is not a List or an entry is not a string, 68 * throws an exception. 69 */ 70 @Nullable getListOfStrings(Map<String, ?> obj, String key)71 public static List<String> getListOfStrings(Map<String, ?> obj, String key) { 72 List<?> list = getList(obj, key); 73 if (list == null) { 74 return null; 75 } 76 return checkStringList(list); 77 } 78 79 /** 80 * Gets an object from an object for the given key. If the key is not present, this returns null. 81 * If the value is not a Map, throws an exception. 82 */ 83 @SuppressWarnings("unchecked") 84 @Nullable getObject(Map<String, ?> obj, String key)85 public static Map<String, ?> getObject(Map<String, ?> obj, String key) { 86 assert key != null; 87 if (!obj.containsKey(key)) { 88 return null; 89 } 90 Object value = obj.get(key); 91 if (!(value instanceof Map)) { 92 throw new ClassCastException( 93 String.format("value '%s' for key '%s' in '%s' is not object", value, key, obj)); 94 } 95 return (Map<String, ?>) value; 96 } 97 98 /** 99 * Gets a number from an object for the given key. If the key is not present, this returns null. 100 * If the value does not represent a double, throws an exception. 101 */ 102 @Nullable getNumberAsDouble(Map<String, ?> obj, String key)103 public static Double getNumberAsDouble(Map<String, ?> obj, String key) { 104 assert key != null; 105 if (!obj.containsKey(key)) { 106 return null; 107 } 108 Object value = obj.get(key); 109 if (value instanceof Double) { 110 return (Double) value; 111 } 112 if (value instanceof String) { 113 try { 114 return Double.parseDouble((String) value); 115 } catch (NumberFormatException e) { 116 throw new IllegalArgumentException( 117 String.format("value '%s' for key '%s' is not a double", value, key)); 118 } 119 } 120 throw new IllegalArgumentException( 121 String.format("value '%s' for key '%s' in '%s' is not a number", value, key, obj)); 122 } 123 124 /** 125 * Gets a number from an object for the given key. If the key is not present, this returns null. 126 * If the value does not represent a float, throws an exception. 127 */ 128 @Nullable getNumberAsFloat(Map<String, ?> obj, String key)129 public static Float getNumberAsFloat(Map<String, ?> obj, String key) { 130 assert key != null; 131 if (!obj.containsKey(key)) { 132 return null; 133 } 134 Object value = obj.get(key); 135 if (value instanceof Float) { 136 return (Float) value; 137 } 138 if (value instanceof String) { 139 try { 140 return Float.parseFloat((String) value); 141 } catch (NumberFormatException e) { 142 throw new IllegalArgumentException( 143 String.format("string value '%s' for key '%s' cannot be parsed as a float", value, 144 key)); 145 } 146 } 147 throw new IllegalArgumentException( 148 String.format("value %s for key '%s' is not a float", value, key)); 149 } 150 151 /** 152 * Gets a number from an object for the given key, casted to an integer. If the key is not 153 * present, this returns null. If the value does not represent an integer, throws an exception. 154 */ 155 @Nullable getNumberAsInteger(Map<String, ?> obj, String key)156 public static Integer getNumberAsInteger(Map<String, ?> obj, String key) { 157 assert key != null; 158 if (!obj.containsKey(key)) { 159 return null; 160 } 161 Object value = obj.get(key); 162 if (value instanceof Double) { 163 Double d = (Double) value; 164 int i = d.intValue(); 165 if (i != d) { 166 throw new ClassCastException("Number expected to be integer: " + d); 167 } 168 return i; 169 } 170 if (value instanceof String) { 171 try { 172 return Integer.parseInt((String) value); 173 } catch (NumberFormatException e) { 174 throw new IllegalArgumentException( 175 String.format("value '%s' for key '%s' is not an integer", value, key)); 176 } 177 } 178 throw new IllegalArgumentException( 179 String.format("value '%s' for key '%s' is not an integer", value, key)); 180 } 181 182 /** 183 * Gets a number from an object for the given key, casted to an long. If the key is not 184 * present, this returns null. If the value does not represent a long integer, throws an 185 * exception. 186 */ getNumberAsLong(Map<String, ?> obj, String key)187 public static Long getNumberAsLong(Map<String, ?> obj, String key) { 188 assert key != null; 189 if (!obj.containsKey(key)) { 190 return null; 191 } 192 Object value = obj.get(key); 193 if (value instanceof Double) { 194 Double d = (Double) value; 195 long l = d.longValue(); 196 if (l != d) { 197 throw new ClassCastException("Number expected to be long: " + d); 198 } 199 return l; 200 } 201 if (value instanceof String) { 202 try { 203 return Long.parseLong((String) value); 204 } catch (NumberFormatException e) { 205 throw new IllegalArgumentException( 206 String.format("value '%s' for key '%s' is not a long integer", value, key)); 207 } 208 } 209 throw new IllegalArgumentException( 210 String.format("value '%s' for key '%s' is not a long integer", value, key)); 211 } 212 213 /** 214 * Gets a string from an object for the given key. If the key is not present, this returns null. 215 * If the value is not a String, throws an exception. 216 */ 217 @Nullable getString(Map<String, ?> obj, String key)218 public static String getString(Map<String, ?> obj, String key) { 219 assert key != null; 220 if (!obj.containsKey(key)) { 221 return null; 222 } 223 Object value = obj.get(key); 224 if (!(value instanceof String)) { 225 throw new ClassCastException( 226 String.format("value '%s' for key '%s' in '%s' is not String", value, key, obj)); 227 } 228 return (String) value; 229 } 230 231 /** 232 * Gets a string from an object for the given key, parsed as a duration (defined by protobuf). If 233 * the key is not present, this returns null. If the value is not a String or not properly 234 * formatted, throws an exception. 235 */ getStringAsDuration(Map<String, ?> obj, String key)236 public static Long getStringAsDuration(Map<String, ?> obj, String key) { 237 String value = getString(obj, key); 238 if (value == null) { 239 return null; 240 } 241 try { 242 return parseDuration(value); 243 } catch (ParseException e) { 244 throw new RuntimeException(e); 245 } 246 } 247 248 /** 249 * Gets a boolean from an object for the given key. If the key is not present, this returns null. 250 * If the value is not a Boolean, throws an exception. 251 */ 252 @Nullable getBoolean(Map<String, ?> obj, String key)253 public static Boolean getBoolean(Map<String, ?> obj, String key) { 254 assert key != null; 255 if (!obj.containsKey(key)) { 256 return null; 257 } 258 Object value = obj.get(key); 259 if (!(value instanceof Boolean)) { 260 throw new ClassCastException( 261 String.format("value '%s' for key '%s' in '%s' is not Boolean", value, key, obj)); 262 } 263 return (Boolean) value; 264 } 265 266 /** 267 * Casts a list of unchecked JSON values to a list of checked objects in Java type. 268 * If the given list contains a value that is not a Map, throws an exception. 269 */ 270 @SuppressWarnings("unchecked") checkObjectList(List<?> rawList)271 public static List<Map<String, ?>> checkObjectList(List<?> rawList) { 272 for (int i = 0; i < rawList.size(); i++) { 273 if (!(rawList.get(i) instanceof Map)) { 274 throw new ClassCastException( 275 String.format( 276 Locale.US, "value %s for idx %d in %s is not object", rawList.get(i), i, rawList)); 277 } 278 } 279 return (List<Map<String, ?>>) rawList; 280 } 281 282 /** 283 * Casts a list of unchecked JSON values to a list of String. If the given list 284 * contains a value that is not a String, throws an exception. 285 */ 286 @SuppressWarnings("unchecked") checkStringList(List<?> rawList)287 public static List<String> checkStringList(List<?> rawList) { 288 for (int i = 0; i < rawList.size(); i++) { 289 if (!(rawList.get(i) instanceof String)) { 290 throw new ClassCastException( 291 String.format( 292 Locale.US, 293 "value '%s' for idx %d in '%s' is not string", rawList.get(i), i, rawList)); 294 } 295 } 296 return (List<String>) rawList; 297 } 298 299 private static final long DURATION_SECONDS_MIN = -315576000000L; 300 private static final long DURATION_SECONDS_MAX = 315576000000L; 301 302 /** 303 * Parse from a string to produce a duration. Copy of 304 * {@link com.google.protobuf.util.Durations#parse}. 305 * 306 * @return A Duration parsed from the string. 307 * @throws ParseException if parsing fails. 308 */ parseDuration(String value)309 private static long parseDuration(String value) throws ParseException { 310 // Must ended with "s". 311 if (value.isEmpty() || value.charAt(value.length() - 1) != 's') { 312 throw new ParseException("Invalid duration string: " + value, 0); 313 } 314 boolean negative = false; 315 if (value.charAt(0) == '-') { 316 negative = true; 317 value = value.substring(1); 318 } 319 String secondValue = value.substring(0, value.length() - 1); 320 String nanoValue = ""; 321 int pointPosition = secondValue.indexOf('.'); 322 if (pointPosition != -1) { 323 nanoValue = secondValue.substring(pointPosition + 1); 324 secondValue = secondValue.substring(0, pointPosition); 325 } 326 long seconds = Long.parseLong(secondValue); 327 int nanos = nanoValue.isEmpty() ? 0 : parseNanos(nanoValue); 328 if (seconds < 0) { 329 throw new ParseException("Invalid duration string: " + value, 0); 330 } 331 if (negative) { 332 seconds = -seconds; 333 nanos = -nanos; 334 } 335 try { 336 return normalizedDuration(seconds, nanos); 337 } catch (IllegalArgumentException e) { 338 throw new ParseException("Duration value is out of range.", 0); 339 } 340 } 341 342 /** 343 * Copy of {@link com.google.protobuf.util.Timestamps#parseNanos}. 344 */ parseNanos(String value)345 private static int parseNanos(String value) throws ParseException { 346 int result = 0; 347 for (int i = 0; i < 9; ++i) { 348 result = result * 10; 349 if (i < value.length()) { 350 if (value.charAt(i) < '0' || value.charAt(i) > '9') { 351 throw new ParseException("Invalid nanoseconds.", 0); 352 } 353 result += value.charAt(i) - '0'; 354 } 355 } 356 return result; 357 } 358 359 private static final long NANOS_PER_SECOND = TimeUnit.SECONDS.toNanos(1); 360 361 /** 362 * Copy of {@link com.google.protobuf.util.Durations#normalizedDuration}. 363 */ 364 @SuppressWarnings("NarrowingCompoundAssignment") normalizedDuration(long seconds, int nanos)365 private static long normalizedDuration(long seconds, int nanos) { 366 if (nanos <= -NANOS_PER_SECOND || nanos >= NANOS_PER_SECOND) { 367 seconds = checkedAdd(seconds, nanos / NANOS_PER_SECOND); 368 nanos %= NANOS_PER_SECOND; 369 } 370 if (seconds > 0 && nanos < 0) { 371 nanos += NANOS_PER_SECOND; // no overflow since nanos is negative (and we're adding) 372 seconds--; // no overflow since seconds is positive (and we're decrementing) 373 } 374 if (seconds < 0 && nanos > 0) { 375 nanos -= NANOS_PER_SECOND; // no overflow since nanos is positive (and we're subtracting) 376 seconds++; // no overflow since seconds is negative (and we're incrementing) 377 } 378 if (!durationIsValid(seconds, nanos)) { 379 throw new IllegalArgumentException(String.format( 380 "Duration is not valid. See proto definition for valid values. " 381 + "Seconds (%s) must be in range [-315,576,000,000, +315,576,000,000]. " 382 + "Nanos (%s) must be in range [-999,999,999, +999,999,999]. " 383 + "Nanos must have the same sign as seconds", seconds, nanos)); 384 } 385 return saturatedAdd(TimeUnit.SECONDS.toNanos(seconds), nanos); 386 } 387 388 /** 389 * Returns true if the given number of seconds and nanos is a valid {@code Duration}. The {@code 390 * seconds} value must be in the range [-315,576,000,000, +315,576,000,000]. The {@code nanos} 391 * value must be in the range [-999,999,999, +999,999,999]. 392 * 393 * <p><b>Note:</b> Durations less than one second are represented with a 0 {@code seconds} field 394 * and a positive or negative {@code nanos} field. For durations of one second or more, a non-zero 395 * value for the {@code nanos} field must be of the same sign as the {@code seconds} field. 396 * 397 * <p>Copy of {@link com.google.protobuf.util.Duration#isValid}.</p> 398 */ durationIsValid(long seconds, int nanos)399 private static boolean durationIsValid(long seconds, int nanos) { 400 if (seconds < DURATION_SECONDS_MIN || seconds > DURATION_SECONDS_MAX) { 401 return false; 402 } 403 if (nanos < -999999999L || nanos >= NANOS_PER_SECOND) { 404 return false; 405 } 406 if (seconds < 0 || nanos < 0) { 407 if (seconds > 0 || nanos > 0) { 408 return false; 409 } 410 } 411 return true; 412 } 413 414 /** 415 * Returns the sum of {@code a} and {@code b} unless it would overflow or underflow in which case 416 * {@code Long.MAX_VALUE} or {@code Long.MIN_VALUE} is returned, respectively. 417 * 418 * <p>Copy of {@link com.google.common.math.LongMath#saturatedAdd}.</p> 419 * 420 */ 421 @SuppressWarnings("ShortCircuitBoolean") saturatedAdd(long a, long b)422 private static long saturatedAdd(long a, long b) { 423 long naiveSum = a + b; 424 if ((a ^ b) < 0 | (a ^ naiveSum) >= 0) { 425 // If a and b have different signs or a has the same sign as the result then there was no 426 // overflow, return. 427 return naiveSum; 428 } 429 // we did over/under flow, if the sign is negative we should return MAX otherwise MIN 430 return Long.MAX_VALUE + ((naiveSum >>> (Long.SIZE - 1)) ^ 1); 431 } 432 } 433