001package Torello.JSON; 002 003import javax.json.*; 004import java.math.BigDecimal; 005import java.math.BigInteger; 006import java.util.function.Function; 007 008import static javax.json.JsonValue.ValueType.*; 009import static Torello.JSON.JFlag.*; 010import static Torello.JSON.RJInternal.*; 011 012/** 013 * Builds on the J2EE Standard Release JSON Parsing Tools by providing additional 014 * help with converting JSON Data into the Best-Fit 015 * <B STYLE='color: red'><CODE>java.lang.Number</CODE></B> 016 * 017 * <EMBED CLASS='external-html' DATA-FILE-ID=ALL_CLASSES_NOTE> 018 * <EMBED CLASS='external-html' DATA-FILE-ID=READ_NUMBER_JSON> 019 * 020 * @see Json 021 * @see JsonObject 022 * @see JsonArray 023 */ 024@Torello.JavaDoc.Annotations.StaticFunctional 025public class ReadNumberJSON 026{ 027 // This is a static class. Has no program state. 028 private ReadNumberJSON() { } 029 030 031 // ******************************************************************************************** 032 // ******************************************************************************************** 033 // Number from JsonArray 034 // ******************************************************************************************** 035 // ******************************************************************************************** 036 037 038 /** 039 * Retrieve a {@link JsonArray} element, and transform it to a {@code java.lang.Number}. 040 * <EMBED CLASS=defs DATA-JTYPE=JsonNumber DATA-TYPE='java.lang.Number'> 041 * <EMBED CLASS='external-html' DATA-FILE-ID=READ_NUM_NOTE> 042 * 043 * @param ja Any instance of {@link JsonArray} 044 * @param index A valid index into {@code 'ja'} 045 * @param throwOnNull Asks that an exception throw if Json-Null is encountered 046 * @return <EMBED CLASS='external-html' DATA-FILE-ID=READ_NUM_RET_JA> 047 * 048 * @throws IndexOutOfBoundsException If {@code 'index'} is out of the bounds of {@code 'ja'} 049 * @throws JsonTypeArrException <EMBED CLASS='external-html' DATA-FILE-ID=READ_NUM_JTAEX> 050 * @throws JsonNullArrException <EMBED CLASS='external-html' DATA-FILE-ID=READ_NUM_JNAEX> 051 * 052 * @see JsonValue#getValueType() 053 * @see #convertToNumber(JsonNumber) 054 */ 055 public static Number get(final JsonArray ja, final int index, final boolean throwOnNull) 056 { 057 // This will throw an IndexOutOfBoundsException if the index is out of bounds. 058 JsonValue jv = ja.get(index); 059 060 switch (jv.getValueType()) 061 { 062 case NULL: 063 064 // This is simple-stuff (not rocket-science). "Type Mapping" Code has to worry 065 // about what the meaning of "null" should be. 066 067 if (throwOnNull) throw new JsonNullArrException(ja, index, NUMBER, Number.class); 068 else return null; 069 070 case NUMBER: return convertToNumber((JsonNumber) jv); 071 072 // The JsonValue at the specified array-index does not contain a JsonString. 073 default: throw new JsonTypeArrException(ja, index, NUMBER, jv, Number.class); 074 } 075 } 076 077 /** 078 * Retrieve a {@link JsonArray} element, and transform it to a {@code java.lang.Number}. 079 * <EMBED CLASS=defs DATA-TYPE='java.lang.Number' DATA-JTYPE=JsonNumber> 080 * <EMBED CLASS='external-html' DATA-FILE-ID=READ_NUM_NOTE> 081 * 082 * @param ja Any instance of {@link JsonArray} 083 * @param index The array index containing the element to retrieve. 084 * @param FLAGS Return-value / exception-throw constants defined in {@link JFlag} 085 * @param defaultValue <EMBED CLASS='external-html' DATA-FILE-ID=COMMON_DEF_VAL> 086 * @return Best fit boxed number, or null. 087 * 088 * @throws IndexOutOfBoundsException <EMBED CLASS='external-html' DATA-FILE-ID=IOOBEX> 089 * @throws JsonNullArrException <EMBED CLASS='external-html' DATA-FILE-ID=JNAEX> 090 * @throws JsonTypeArrException <EMBED CLASS='external-html' DATA-FILE-ID=JTAEX> 091 * 092 * @see ReadBoxedJSON#GET(JsonArray, int, int, Number, Class, Function, Function) 093 * @see #convertToNumber(JsonNumber) 094 */ 095 public static Number get( 096 final JsonArray ja, 097 final int index, 098 final int FLAGS, 099 final Number defaultValue 100 ) 101 { 102 return ReadBoxedJSON.GET 103 (ja, index, FLAGS, defaultValue, Number.class, ReadNumberJSON::convertToNumber, null); 104 } 105 106 /** 107 * Retrieve a {@link JsonArray} element containing a {@link JsonString}, and transform it to a 108 * {@code java.lang.Number}, with either a user-provided parser, or the standard java parser 109 * 110 * <EMBED CLASS=defs DATA-TYPE='java.lang.Number' DATA-JTYPE=JsonString 111 * DATA-PARSER='new BigDecimal'> 112 * 113 * <BR /><BR />If {@code 'parser'} is passed null, the default parser is used, which is just 114 * the {@code java.math.BigDecimal} constructor which accepts a {@code String}. What is done 115 * with the parsed {@code BigDecimal} instance is explained below. 116 * 117 * <BR /><BR /><EMBED CLASS='external-html' DATA-FILE-ID=READ_NUM_PARSE_DESC> 118 * 119 * @param ja Any instance of {@link JsonArray} 120 * @param index The array index containing the {@link JsonString} element to retrieve. 121 * @param FLAGS Return-value / exception-throw constants defined in {@link JFlag} 122 * @param defaultValue <EMBED CLASS='external-html' DATA-FILE-ID=COMMON_DEF_VAL> 123 * @param optionalParser <EMBED CLASS='external-html' DATA-FILE-ID=COMMON_PARSER> 124 * @return <EMBED CLASS='external-html' DATA-FILE-ID=PARSE_BOXED_RET_JA> 125 * 126 * @throws IndexOutOfBoundsException <EMBED CLASS='external-html' DATA-FILE-ID=IOOBEX> 127 * @throws JsonStrParseArrException <EMBED CLASS='external-html' DATA-FILE-ID=JSPAEX> 128 * @throws JsonNullArrException <EMBED CLASS='external-html' DATA-FILE-ID=JNAEX> 129 * @throws JsonTypeArrException <EMBED CLASS='external-html' DATA-FILE-ID=JTAEX> 130 * 131 * @see ParseBoxedJSON#PARSE(JsonObject, String, int, Number, Class, Function, Function, Function) 132 * @see #convertToNumber(String) 133 */ 134 public static Number parse( 135 final JsonArray ja, 136 final int index, 137 final int FLAGS, 138 final Number defaultValue, 139 final Function<String, Number> optionalParser 140 ) 141 { 142 return ParseBoxedJSON.PARSE( 143 ja, index, FLAGS, defaultValue, Number.class, optionalParser, 144 ReadNumberJSON::convertToNumber, null /* Not Needed, convertToNumber won't throw */ 145 ); 146 } 147 148 149 // ******************************************************************************************** 150 // ******************************************************************************************** 151 // Number from JsonObject 152 // ******************************************************************************************** 153 // ******************************************************************************************** 154 155 156 /** 157 * Extract a {@link JsonObject} property, and transform it to a {@code java.lang.Number}. 158 * <EMBED CLASS=defs DATA-TYPE='java.lang.Number' DATA-JTYPE=JsonNumber> 159 * <EMBED CLASS='external-html' DATA-FILE-ID=READ_NUM_NOTE> 160 * 161 * @param jo Any instance of {@link JsonObject}. 162 * @param propertyName Any property name contained by {@code 'jo'} 163 * @param isOptional <EMBED CLASS='external-html' DATA-FILE-ID=COMMON_IS_OPTIONAL> 164 * @param throwOnNull <EMBED CLASS='external-html' DATA-FILE-ID=COMMON_THROW_ON_JO> 165 * @return <EMBED CLASS='external-html' DATA-FILE-ID=READ_NUM_RET_JO> 166 * 167 * @throws JsonPropMissingException <EMBED CLASS='external-html' DATA-FILE-ID=READ_NUM_JPMEX> 168 * @throws JsonTypeObjException <EMBED CLASS='external-html' DATA-FILE-ID=READ_NUM_JTOEX> 169 * @throws JsonNullObjException <EMBED CLASS='external-html' DATA-FILE-ID=READ_NUM_JNOEX> 170 * 171 * @see JsonValue#getValueType() 172 * @see #convertToNumber(JsonNumber) 173 */ 174 public static Number get( 175 final JsonObject jo, 176 final String propertyName, 177 final boolean isOptional, 178 final boolean throwOnNull 179 ) 180 { 181 if (! jo.containsKey(propertyName)) 182 { 183 if (isOptional) return null; 184 else throw new JsonPropMissingException(jo, propertyName, NUMBER, Number.class); 185 } 186 187 JsonValue jv = jo.get(propertyName); 188 189 switch (jv.getValueType()) 190 { 191 case NULL: 192 // This is simple-stuff (not rocket-science). "Type Mapping" Code has to worry 193 // about what the meaning of "null" should be. 194 195 if (throwOnNull) throw new JsonNullObjException 196 (jo, propertyName, NUMBER, Number.class); 197 198 else return null; 199 200 case NUMBER: return convertToNumber((JsonNumber) jv); 201 202 // The JsonObject propertydoes not contain a JsonNumber. 203 default: throw new JsonTypeObjException 204 (jo, propertyName, NUMBER, jv, Number.class); 205 } 206 } 207 208 /** 209 * Retrieve a {@link JsonObject} property, and transform it to a {@code java.lang.Number}. 210 * <EMBED CLASS=defs DATA-TYPE='java.lang.Number' DATA-JTYPE=JsonNumber> 211 * <EMBED CLASS='external-html' DATA-FILE-ID=READ_NUM_NOTE> 212 * 213 * @param jo Any instance of {@link JsonObject} 214 * @param propertyName Any property name contained by {@code 'jo'} 215 * @param FLAGS Return-value / exception-throw constants defined in {@link JFlag} 216 * @param defaultValue <EMBED CLASS='external-html' DATA-FILE-ID=COMMON_DEF_VAL> 217 * @return Best fit boxed number, or null. 218 * 219 * @throws JsonPropMissingException <EMBED CLASS='external-html' DATA-FILE-ID=JPMEX> 220 * @throws JsonNullObjException <EMBED CLASS='external-html' DATA-FILE-ID=JNOEX> 221 * @throws JsonTypeObjException <EMBED CLASS='external-html' DATA-FILE-ID=JTOEX> 222 * 223 * @see ReadBoxedJSON#GET(JsonObject, String, int, Number, Class, Function, Function) 224 * @see #convertToNumber(JsonNumber) 225 */ 226 public static Number get( 227 final JsonObject jo, 228 final String propertyName, 229 final int FLAGS, 230 final Number defaultValue 231 ) 232 { 233 return ReadBoxedJSON.GET( 234 jo, propertyName, FLAGS, defaultValue, Number.class, 235 ReadNumberJSON::convertToNumber, 236 null 237 ); 238 } 239 240 /** 241 * Retrieve a {@link JsonObject} property containing a {@link JsonString}, and transform it to 242 * a {@code java.lang.Number}, with either a user-provided parser, or the standard java 243 * parser 244 * 245 * <EMBED CLASS=defs DATA-TYPE='java.lang.Number' DATA-JTYPE=JsonString 246 * DATA-PARSER='new BigDecimal'> 247 * 248 * <BR /><BR />If {@code 'parser'} is passed null, the default parser is used, which is just 249 * the {@code java.math.BigDecimal} constructor which accepts a {@code String}. What is done 250 * with the parsed {@code BigDecimal} instance is explained below. 251 * 252 * <BR /><BR /><EMBED CLASS='external-html' DATA-FILE-ID=READ_NUM_PARSE_DESC> 253 * 254 * @param jo Any instance of {@link JsonObject} 255 * @param propertyName Any property name contained by {@code 'jo'} 256 * @param FLAGS Return-value / exception-throw constants defined in {@link JFlag} 257 * @param defaultValue <EMBED CLASS='external-html' DATA-FILE-ID=COMMON_DEF_VAL> 258 * @param optionalParser <EMBED CLASS='external-html' DATA-FILE-ID=COMMON_PARSER> 259 * @return <EMBED CLASS='external-html' DATA-FILE-ID=PARSE_BOXED_RET_JO> 260 * 261 * @throws JsonPropMissingException <EMBED CLASS='external-html' DATA-FILE-ID=JPMEX> 262 * @throws JsonStrParseObjException <EMBED CLASS='external-html' DATA-FILE-ID=JSPOEX> 263 * @throws JsonNullObjException <EMBED CLASS='external-html' DATA-FILE-ID=JNOEX> 264 * @throws JsonTypeObjException <EMBED CLASS='external-html' DATA-FILE-ID=JTOEX> 265 * 266 * @see ParseBoxedJSON#PARSE(JsonObject, String, int, Number, Class, Function, Function, Function) 267 * @see #convertToNumber(String) 268 */ 269 public static Number parse( 270 final JsonObject jo, 271 final String propertyName, 272 final int FLAGS, 273 final Number defaultValue, 274 final Function<String, Number> optionalParser 275 ) 276 { 277 return ParseBoxedJSON.PARSE( 278 jo, propertyName, FLAGS, defaultValue, Number.class, optionalParser, 279 ReadNumberJSON::convertToNumber, 280 null /* Not Needed, convertToNumber won't throw */ 281 ); 282 } 283 284 285 // ******************************************************************************************** 286 // ******************************************************************************************** 287 // Number from JsonString Parse **OR** from JsonNumber 288 // ******************************************************************************************** 289 // ******************************************************************************************** 290 291 292 /** 293 * <EMBED CLASS=defs DATA-TYPE=Number DATA-PARSER='new BigDecimal'> 294 * <EMBED CLASS='external-html' DATA-FILE-ID=RORP_BOXED_WF_JA> 295 * 296 * @param ja Any {@link JsonArray} 297 * @param i Any index into the {@code JsonArray} 298 * @param FLAGS Return-value / exception-throw constants defined in {@link JFlag} 299 * @param defaultValue <EMBED CLASS='external-html' DATA-FILE-ID=ROPB_DEFVAL> 300 * @param optionalParser <EMBED CLASS='external-html' DATA-FILE-ID=ROPB_PARSER> 301 * @return <EMBED CLASS='external-html' DATA-FILE-ID=ROPB_RET_JA> 302 * 303 * @throws IndexOutOfBoundsException <EMBED CLASS='external-html' DATA-FILE-ID=IOOBEX> 304 * @throws JsonStrParseArrException <EMBED CLASS='external-html' DATA-FILE-ID=ROPB_JSPAEX> 305 * @throws JsonNullArrException <EMBED CLASS='external-html' DATA-FILE-ID=ROPB_JNAEX> 306 * @throws JsonTypeArrException <EMBED CLASS='external-html' DATA-FILE-ID=ROPB_JTAEX> 307 * 308 * @see #get(JsonArray, int, int, Number) 309 * @see #parse(JsonArray, int, int, Number, Function) 310 */ 311 public static Number get( 312 final JsonArray ja, 313 final int i, 314 final int FLAGS, 315 final Number defaultValue, 316 Function<String, Number> optionalParser 317 ) 318 { 319 JsonValue.ValueType t; 320 321 return ((i >= ja.size()) || ((t=ja.get(i).getValueType()) == NUMBER) || (t != STRING)) 322 ? get(ja, i, FLAGS, defaultValue) 323 : parse(ja, i, FLAGS, defaultValue, optionalParser); 324 } 325 326 /** 327 * <EMBED CLASS=defs DATA-TYPE=Number DATA-PARSER='new BigDecimal'> 328 * <EMBED CLASS='external-html' DATA-FILE-ID=RORP_BOXED_WF_JO> 329 * 330 * @param jo Any {@link JsonObject} 331 * @param propertyName Any of the properties defined in the {@code JsonObject} 332 * @param FLAGS Return-value / exception-throw constants defined in {@link JFlag} 333 * @param defaultValue <EMBED CLASS='external-html' DATA-FILE-ID=ROPB_DEFVAL> 334 * @param optionalParser <EMBED CLASS='external-html' DATA-FILE-ID=ROPB_PARSER> 335 * @return <EMBED CLASS='external-html' DATA-FILE-ID=ROPB_RET_JO> 336 * 337 * @throws JsonPropMissingException <EMBED CLASS='external-html' DATA-FILE-ID=JPMEX> 338 * @throws JsonStrParseObjException <EMBED CLASS='external-html' DATA-FILE-ID=ROPB_JSPOEX> 339 * @throws JsonNullObjException <EMBED CLASS='external-html' DATA-FILE-ID=ROPB_JNOEX> 340 * @throws JsonTypeObjException <EMBED CLASS='external-html' DATA-FILE-ID=ROPB_JTOEX> 341 * 342 * @see #get(JsonObject, String, int, Number) 343 * @see #parse(JsonObject, String, int, Number, Function) 344 */ 345 public static Number get( 346 final JsonObject jo, 347 final String propertyName, 348 final int FLAGS, 349 final Number defaultValue, 350 final Function<String, Number> optionalParser 351 ) 352 { 353 JsonValue.ValueType t; 354 355 return ( (! jo.containsKey(propertyName)) 356 || ((t = jo.get(propertyName).getValueType()) == NUMBER) 357 || (t != STRING) 358 ) 359 ? get(jo, propertyName, FLAGS, defaultValue) 360 : parse(jo, propertyName, FLAGS, defaultValue, optionalParser); 361 } 362 363 364 // ******************************************************************************************** 365 // ******************************************************************************************** 366 // Internal Use Only Helpers 367 // ******************************************************************************************** 368 // ******************************************************************************************** 369 370 371 /** 372 * Converts any {@link JsonNumber} into one of the inheriting subclasses of Java class 373 * {@code Number} 374 * @param jn Any {@link JsonNumber} 375 * @return The most appropriate intance of {@code java.lang.Number} 376 * @see #get(JsonObject, String, int, Number) 377 * @see ]#get(JsonArray, int, int, Number) 378 * @see JsonNumber#isIntegral() 379 * @see JsonNumber#bigIntegerValue() 380 * @see JsonNumber#bigDecimalValue() 381 */ 382 protected static Number convertToNumber(final JsonNumber jn) 383 { 384 if (jn.isIntegral()) 385 { 386 BigInteger bi = jn.bigIntegerValue(); 387 int l = bi.bitLength(); 388 389 if (l <= 32) return Integer.valueOf(bi.intValue()); 390 if (l <= 64) return Long.valueOf(bi.longValue()); 391 392 return bi; 393 } 394 395 else 396 { 397 BigDecimal bd = jn.bigDecimalValue(); 398 399 // This probably isn't the most efficient thing I've ever written, but I do not 400 // have the energy to stare at java.math.BigDecimal at the moment. The JavaDoc for 401 // this JSON => Java-Type Conversion is quite intricate. I will figure this out at 402 // at later date. 403 404 float f = bd.floatValue(); 405 406 if ( (! Float.isInfinite(f)) 407 && (BigDecimal.valueOf(f).compareTo(bd) == 0) 408 ) 409 return (Float) f; 410 411 double d = bd.doubleValue(); 412 413 if ( (! Double.isInfinite(d)) 414 && (BigDecimal.valueOf(d).compareTo(bd) == 0) 415 ) 416 return (Double) d; 417 418 return bd; 419 } 420 } 421 422 /** 423 * Converts any {@code java.lang.String} into one of the inheriting subclasses of Java class 424 * {@code Number} 425 * @param s Any {@code String} 426 * @return The most appropriate instance of {@code java.lang.Number} 427 * @throws NumberFormatException If the input {@code String} isn't properly formatted as a 428 * number. 429 * @see ReadNumberJSON#parse(JsonObject, String, int, Number, Function) 430 * @see ReadNumberJSON#parse(JsonArray, int, int, Number, Function) 431 */ 432 protected static Number convertToNumber(final String s) 433 { return convertToNumber(new BigDecimal(s.trim())); } 434 435 /** 436 * Converts any {@code java.math.BigDecimal} into one of the inheriting subclasses of 437 * {@code Number}. 438 * @param bd Any {@code BigDecimal} 439 * @return The most appropriate instance of {@code java.lang.Number} 440 */ 441 protected static Number convertToNumber(final BigDecimal bd) 442 { 443 if (bd.scale() == 0) 444 { 445 BigInteger bi = bd.toBigInteger(); 446 int l = bi.bitLength(); 447 448 if (l <= 32) return Integer.valueOf(bi.intValue()); 449 if (l <= 64) return Long.valueOf(bi.longValue()); 450 return bi; 451 } 452 453 else 454 { 455 // This probably isn't the most efficient thing I've ever written, but I do not 456 // have the energy to stare at java.math.BigDecimal at the moment. The JavaDoc for 457 // this JSON => Java-Type Conversion is quite intricate. I will figure this out at 458 // at later date. 459 460 float f = bd.floatValue(); 461 462 if ( (! Float.isInfinite(f)) 463 && (BigDecimal.valueOf(f).compareTo(bd) == 0) 464 ) 465 return (Float) f; 466 467 double d = bd.doubleValue(); 468 469 if ( (! Double.isInfinite(d)) 470 && (BigDecimal.valueOf(d).compareTo(bd) == 0) 471 ) 472 return (Double) d; 473 474 return bd; 475 } 476 } 477 478}