001package Torello.JSON;
002
003import Torello.JavaDoc.Annotations.IntoHTMLTable;
004import static Torello.JavaDoc.Annotations.IntoHTMLTable.Background.BlueDither;
005import static Torello.JavaDoc.Annotations.IntoHTMLTable.Background.GreenDither;
006
007import java.math.BigDecimal;
008import java.util.function.Function;
009
010import javax.json.JsonObject;
011import javax.json.JsonArray;
012import javax.json.JsonNumber;
013import javax.json.JsonValue;
014
015import static javax.json.JsonValue.ValueType.TRUE;
016import static javax.json.JsonValue.ValueType.NUMBER;
017import static javax.json.JsonValue.ValueType.NULL;
018
019/**
020 * Builds on the J2EE Standard Release JSON Parsing Tools by providing additional
021 * help with converting JSON Data into <B STYLE='color: red'>Java Primitive-Types</B>
022 * 
023 * <EMBED CLASS='external-html' DATA-FILE-ID=ALL_CLASSES_NOTE>
024 * <EMBED CLASS='external-html' DATA-FILE-ID=READ_PRIM_JSON>
025 * <EMBED CLASS='external-html' DATA-FILE-ID=READ_PRIM_PTABLE>
026 * <EMBED CLASS='external-html' DATA-CH=char DATA-FILE-ID=JAVA_LANG_CHAR>
027 * 
028 * @see JsonObject
029 * @see JsonArray
030 */
031@Torello.JavaDoc.Annotations.StaticFunctional
032public class ReadPrimJSON
033{
034    // This is a static class.  Has no program state.
035    private ReadPrimJSON() { }
036
037
038    // ********************************************************************************************
039    // ********************************************************************************************
040    // Read from a JsonArray
041    // ********************************************************************************************
042    // ********************************************************************************************
043
044
045    /**
046     * <EMBED CLASS='external-html' DATA-TYPE=int DATA-FILE-ID=READ_PRIM_JA>
047     * @see #GET(JsonArray, int, Class)
048     * @see JsonNumber#intValueExact()
049     */
050    @IntoHTMLTable(
051        title="Extract a JsonNumber from a JsonArray, and Transform it to a Java 'int' Primitive",
052        background=BlueDither
053    )
054    public static int getInt(final JsonArray ja, final int index)
055    {
056        final JsonNumber jn = GET(ja, index, int.class);
057
058        try
059            { return jn.intValueExact(); }
060
061        catch (ArithmeticException ae)
062            { throw new JsonArithmeticArrException(ae, ja, index, NUMBER, jn, int.class); }
063    }
064
065    /**
066     * <EMBED CLASS='external-html' DATA-TYPE=long DATA-FILE-ID=READ_PRIM_JA>
067     * @see #GET(JsonArray, int, Class)
068     * @see JsonNumber#longValueExact()
069     */
070    @IntoHTMLTable(
071        title="Extract a JsonNumber from a JsonArray, and Transform it to a Java 'long' Primitive",
072        background=GreenDither
073    )
074    public static long getLong(final JsonArray ja, final int index)
075    {
076        final JsonNumber jn = GET(ja, index, long.class);
077
078        try
079            { return jn.longValueExact(); }
080
081        catch (ArithmeticException ae)
082            { throw new JsonArithmeticArrException(ae, ja, index, NUMBER, jn, long.class); }
083    }
084
085    /**
086     * <EMBED CLASS='external-html' DATA-TYPE=short DATA-FILE-ID=READ_PRIM_JA>
087     * @see #GET(JsonArray, int, Class)
088     * @see JsonNumber#longValueExact()
089     * @see RJInternal#SHORT_FROM_LONG(long)
090     */
091    @IntoHTMLTable(
092        title=
093            "Extract a JsonNumber from a JsonArray, and Transform it to a Java 'short' Primitive",
094        background=BlueDither
095    )
096    public static short getShort(final JsonArray ja, final int index)
097    {
098        final JsonNumber jn = GET(ja, index, short.class);
099
100        try
101            { return RJInternal.SHORT_FROM_LONG(jn.longValueExact()); }
102
103        catch (ArithmeticException ae)
104            { throw new JsonArithmeticArrException(ae, ja, index, NUMBER, jn, short.class); }
105    }
106
107    /**
108     * <EMBED CLASS='external-html' DATA-TYPE=byte DATA-FILE-ID=READ_PRIM_JA>
109     * @see #GET(JsonArray, int, Class)
110     * @see JsonNumber#longValueExact()
111     * @see RJInternal#BYTE_FROM_LONG(long)
112     */
113    @IntoHTMLTable(
114        title="Extract a JsonNumber from a JsonArray, and Transform it to a Java 'byte' Primitive",
115        background=GreenDither
116    )
117    public static byte getByte(final JsonArray ja, final int index)
118    {
119        final JsonNumber jn = GET(ja, index, byte.class);
120
121        try
122            { return RJInternal.BYTE_FROM_LONG(jn.longValueExact()); }
123
124        catch (ArithmeticException ae)
125            { throw new JsonArithmeticArrException(ae, ja, index, NUMBER, jn, byte.class); }
126    }
127
128    /**
129     * <EMBED CLASS='external-html' DATA-TYPE=double DATA-FILE-ID=READ_PRIM_JA>
130     * @see #GET(JsonArray, int, Class)
131     * @see RJInternal#DOUBLE_WITH_CHECK(BigDecimal)
132     */
133    @IntoHTMLTable(
134        title=
135            "Extract a JsonNumber from a JsonArray, and Transform it to a Java 'double' " +
136            "Primitive",
137        background=BlueDither
138    )
139    public static double getDouble(final JsonArray ja, final int index)
140    {
141        final JsonNumber jn = GET(ja, index, double.class);
142
143        try
144            { return RJInternal.DOUBLE_WITH_CHECK(jn.bigDecimalValue()); }
145
146        catch (ArithmeticException ae)
147            { throw new JsonArithmeticArrException(ae, ja, index, NUMBER, jn, double.class); }
148    }
149
150    /**
151     * <EMBED CLASS='external-html' DATA-TYPE=float DATA-FILE-ID=READ_PRIM_JA>
152     * @see #GET(JsonArray, int, Class)
153     * @see RJInternal#FLOAT_WITH_CHECK(BigDecimal)
154     */
155    @IntoHTMLTable(
156        title=
157            "Extract a JsonNumber from a JsonArray, and Transform it to a Java 'float' Primitive",
158        background=GreenDither
159    )
160    public static float getFloat(final JsonArray ja, final int index)
161    {
162        final JsonNumber jn = GET(ja, index, float.class);
163
164        try
165            { return RJInternal.FLOAT_WITH_CHECK(jn.bigDecimalValue()); }
166
167        catch (ArithmeticException ae)
168            { throw new JsonArithmeticArrException(ae, ja, index, NUMBER, jn, float.class); }
169    }
170
171    /** <EMBED CLASS='external-html' DATA-FILE-ID=READ_PRIM_JA_BOOL> */
172    @IntoHTMLTable(
173        title=
174            "Extract Json-True or False from a JsonArray, and Transform it to a Java " +
175            "'boolean' Primitive",
176        background=BlueDither
177    )
178    public static boolean getBoolean(final JsonArray ja, final int index)
179    {
180        final JsonValue jv = ja.get(index);
181
182        switch (jv.getValueType()) // throws IOB if necessary
183        {
184            case NULL: throw new JsonNullPrimitiveArrException
185                (ja, index, TRUE, boolean.class);
186
187            case TRUE:  return true;
188            case FALSE: return false;
189
190            default: throw new JsonTypeArrException
191                (ja, index, TRUE, jv, boolean.class);
192        }
193    }
194
195
196    // ********************************************************************************************
197    // ********************************************************************************************
198    // Internal GET
199    // ********************************************************************************************
200    // ********************************************************************************************
201
202
203    /**
204     * This is an internal helper method for retrieving an element from a {@link JsonArray},
205     * and converting it to one of the standard <B STYLE='color: red;'>Java Types</B>.
206     * 
207     * @param ja                Any instance of {@link JsonArray}
208     * @param index             Any index into the array which holds a {@link JsonNumber}
209     * @param primitiveClass    <EMBED CLASS='external-html' DATA-FILE-ID=READ_PRIM_PCLASS>
210     * 
211     * @return The extracted {@link JsonNumber}.
212     * 
213     * @throws JsonNullPrimitiveArrException If the array element at {@code 'index'} contains
214     *                                       <B STYLE='color: red'>Json-Null</B>
215     * @throws JsonTypeArrException         If the array element at {@code 'index'} doesn't contain
216     *                                      {@link JsonNumber}
217     * @throws JsonArithmeticArrException   If there are any arithmetic problems during conversion
218     * @throws IndexOutOfBoundsException    If {@code 'index'} is out of the bounds of {@code 'ja'}
219     * 
220     * @see #getInt(JsonArray, int)
221     * @see #getLong(JsonArray, int)
222     * @see #getShort(JsonArray, int)
223     * @see #getByte(JsonArray, int)
224     * @see #getDouble(JsonArray, int)
225     * @see #getFloat(JsonArray, int)
226     */
227    protected static <T> JsonNumber GET(
228            final JsonArray ja,
229            final int       index,
230            final Class<T>  primitiveClass
231        )
232    {
233        // This will throw an IndexOutOfBoundsException if the index is out of bounds.
234        final JsonValue jv = ja.get(index);
235
236        switch (jv.getValueType())
237        {
238            case NULL: throw new JsonNullPrimitiveArrException
239                (ja, index, NUMBER, primitiveClass);
240
241            case NUMBER: return (JsonNumber) jv;
242
243            default: throw new JsonTypeArrException
244                (ja, index, NUMBER, jv, primitiveClass);
245        }
246    }
247
248
249    // ********************************************************************************************
250    // ********************************************************************************************
251    // Read from a JsonObject
252    // ********************************************************************************************
253    // ********************************************************************************************
254
255
256    /**
257     * <EMBED CLASS='external-html' DATA-TYPE=int DATA-FILE-ID=READ_PRIM_JO>
258     * @see #GET(JsonObject, String, Class)
259     * @see JsonNumber#intValueExact()
260     */
261    @IntoHTMLTable(
262        title="Extract a JsonNumber from a JsonObject, and Transform it to a Java 'int' Primitive",
263        background=GreenDither
264    )
265    public static int getInt(final JsonObject jo, final String propertyName)
266    {
267        final JsonNumber jn = GET(jo, propertyName, int.class);
268
269        try
270            { return jn.intValueExact(); }
271
272        catch (ArithmeticException ae)
273            { throw new JsonArithmeticObjException(ae, jo, propertyName, NUMBER, jn, int.class); }
274    }
275
276    /**
277     * <EMBED CLASS='external-html' DATA-TYPE=long DATA-FILE-ID=READ_PRIM_JO>
278     * @see #GET(JsonObject, String, Class)
279     * @see JsonNumber#longValueExact()
280     */
281    @IntoHTMLTable(
282        title=
283            "Extract a JsonNumber from a JsonObject, and Transform it to a Java 'long' Primitive",
284        background=BlueDither
285    )
286    public static long getLong(final JsonObject jo, final String propertyName)
287    {
288        final JsonNumber jn = GET(jo, propertyName, long.class);
289
290        try
291            { return jn.longValueExact(); }
292
293        catch (ArithmeticException ae)
294            { throw new JsonArithmeticObjException(ae, jo, propertyName, NUMBER, jn, long.class); }
295    }
296
297    /**
298     * <EMBED CLASS='external-html' DATA-TYPE=short DATA-FILE-ID=READ_PRIM_JO>
299     * @see #GET(JsonObject, String, Class)
300     * @see JsonNumber#longValueExact()
301     * @see RJInternal#SHORT_FROM_LONG(long)
302     */
303    @IntoHTMLTable(
304        title=
305            "Extract a JsonNumber from a JsonObject, and Transform it to a Java 'short' Primitive",
306        background=GreenDither
307    )
308    public static short getShort(final JsonObject jo, final String propertyName)
309    {
310        final JsonNumber jn = GET(jo, propertyName, short.class);
311
312        try
313            { return RJInternal.SHORT_FROM_LONG(jn.longValueExact()); }
314
315        catch (ArithmeticException ae)
316        {
317            throw new JsonArithmeticObjException(ae, jo, propertyName, NUMBER, jn, short.class);
318        }
319    }
320
321    /**
322     * <EMBED CLASS='external-html' DATA-TYPE=byte DATA-FILE-ID=READ_PRIM_JO>
323     * @see #GET(JsonObject, String, Class)
324     * @see JsonNumber#longValueExact()
325     * @see RJInternal#BYTE_FROM_LONG(long)
326     */
327    @IntoHTMLTable(
328        title=
329            "Extract a JsonNumber from a JsonObject, and Transform it to a Java 'byte' Primitive",
330        background=BlueDither
331    )
332    public static byte getByte(final JsonObject jo, final String propertyName)
333    {
334        final JsonNumber jn = GET(jo, propertyName, byte.class);
335
336        try
337            { return RJInternal.BYTE_FROM_LONG(jn.longValueExact()); }
338
339        catch (ArithmeticException ae)
340            { throw new JsonArithmeticObjException(ae, jo, propertyName, NUMBER, jn, byte.class); }
341    }
342
343    /**
344     * <EMBED CLASS='external-html' DATA-TYPE=double DATA-FILE-ID=READ_PRIM_JO>
345     * @see #GET(JsonObject, String, Class)
346     * @see RJInternal#DOUBLE_WITH_CHECK(BigDecimal)
347     */
348    @IntoHTMLTable(
349        title=
350            "Extract a JsonNumber from a JsonObject, and Transform it to a Java 'double' " +
351            "Primitive",
352        background=GreenDither
353    )
354    public static double getDouble(final JsonObject jo, final String propertyName)
355    {
356        final JsonNumber jn = GET(jo, propertyName, double.class);
357
358        try
359            { return RJInternal.DOUBLE_WITH_CHECK(jn.bigDecimalValue()); }
360
361        catch (ArithmeticException ae)
362        {
363            throw new JsonArithmeticObjException(ae, jo, propertyName, NUMBER, jn, double.class);
364        }
365    }
366
367    /**
368     * <EMBED CLASS='external-html' DATA-TYPE=float DATA-FILE-ID=READ_PRIM_JO>
369     * @see #GET(JsonObject, String, Class)
370     * @see RJInternal#FLOAT_WITH_CHECK(BigDecimal)
371     */
372    @IntoHTMLTable(
373        title=
374            "Extract a JsonNumber from a JsonObject, and Transform it to a Java 'float' Primitive",
375        background=BlueDither
376    )
377    public static float getFloat(final JsonObject jo, final String propertyName)
378    {
379        final JsonNumber jn = GET(jo, propertyName, float.class);
380
381        try
382            { return RJInternal.FLOAT_WITH_CHECK(jn.bigDecimalValue()); }
383
384        catch (ArithmeticException ae)
385        {
386            throw new JsonArithmeticObjException(ae, jo, propertyName, NUMBER, jn, float.class);
387        }
388    }
389
390    /** <EMBED CLASS='external-html' DATA-FILE-ID=READ_PRIM_JO_BOOL> */
391    @IntoHTMLTable(
392        title=
393            "Extract Json-True or False from a JsonObject, and Transform it to a Java 'long' " +
394            "Primitive",
395        background=GreenDither
396    )
397    public static boolean getBoolean(final JsonObject jo, final String propertyName)
398    {
399        if (! jo.containsKey(propertyName)) throw new JsonPropMissingException
400            (jo, propertyName, TRUE, boolean.class);
401
402        final JsonValue jv = jo.get(propertyName);
403
404        switch (jv.getValueType())
405        {
406            case NULL: throw new JsonNullPrimitiveObjException
407                (jo, propertyName, TRUE, boolean.class);
408
409            case TRUE:  return true;
410            case FALSE: return false;
411
412            default: throw new JsonTypeObjException
413                (jo, propertyName, TRUE, jv, boolean.class);
414        }
415    }
416
417
418    // ********************************************************************************************
419    // ********************************************************************************************
420    // The Internal Methods
421    // ********************************************************************************************
422    // ********************************************************************************************
423
424
425    /**
426     * This is an internal helper method for retrieving a property from a {@link JsonObject},
427     * and converting it to one of the standard <B STYLE='color: red;'>Java Types</B>.
428     * 
429     * @param jo                Any instance of {@link JsonObject}
430     * @param propertyName      Any property name contained by {@code 'jo'}
431     * @param primitiveClass    <EMBED CLASS='external-html' DATA-FILE-ID=READ_PRIM_PCLASS>
432     * 
433     * @return The extracted {@link JsonNumber}.
434     * 
435     * @throws JsonNullPrimitiveObjException If the specified property contains
436     *                                       <B STYLE='color: red'>Json-Null</B>
437     * @throws JsonPropMissingException     If the property is missing, and {@code 'isOptional'}
438     *                                      is {@code FALSE}.
439     * @throws JsonTypeObjException         If the user specified property doesn't contain a
440     *                                      {@link JsonNumber}
441     * @throws JsonArithmeticObjException   If there any arithmetic problems during the conversion
442     * 
443     * @see #getInt(JsonObject, String)
444     * @see #getLong(JsonObject, String)
445     * @see #getShort(JsonObject, String)
446     * @see #getByte(JsonObject, String)
447     * @see #getDouble(JsonObject, String)
448     * @see #getFloat(JsonObject, String)
449     */
450    protected static <T> JsonNumber GET(
451            final JsonObject    jo,
452            final String        propertyName,
453            final Class<T>      primitiveClass
454        )
455    {
456        // Here, a 'get' request was made for a property that isn't actually listed among the
457        // properties in the provided JsonObject.  Since this internal 'GET' is used by methods
458        // that are trying to return a Java Primitive (like 'int' or 'float'), then an exception
459        // has to be thrown.  The option of returning 'null' isn't possible here!
460    
461        if (! jo.containsKey(propertyName)) throw new JsonPropMissingException
462            (jo, propertyName, NUMBER, primitiveClass);
463
464        final JsonValue jv = jo.get(propertyName);
465
466        switch (jv.getValueType())
467        {
468            case NULL: throw new JsonNullPrimitiveObjException
469                (jo, propertyName, NUMBER, primitiveClass);
470
471            case NUMBER: return (JsonNumber) jv;
472
473            // The JsonObject property does not contain a JsonNumber.
474            default: throw new JsonTypeObjException
475                (jo, propertyName, NUMBER, jv, primitiveClass);
476        }
477    }
478
479}