001package Torello.Java.JSON;
002
003import javax.json.*;
004import java.lang.reflect.*;
005import java.math.*;
006
007import java.util.function.Function;
008
009import static javax.json.JsonValue.ValueType.*;
010import static Torello.Java.JSON.JFlag.*;
011import static Torello.Java.JSON.RJInternal.*;
012
013/**
014 * Builds on the J2EE Standard Release JSON Parsing Tools by providing additional
015 * help with converting JSON Data into Java Object-Data.
016 * 
017 * <EMBED CLASS='external-html' DATA-FILE-ID=GLASS_FISH_NOTE>
018 * <EMBED CLASS='external-html' DATA-FILE-ID=READ_JSON>
019 * <EMBED CLASS='external-html' DATA-FILE-ID=JOS>
020 * 
021 * @see Json
022 * @see JsonObject
023 * @see JsonArray
024 */
025@Torello.JavaDoc.StaticFunctional
026@Torello.JavaDoc.JDHeaderBackgroundImg(EmbedTagFileID="JSON_JDHBI")
027public class ReadJSON
028{
029    // This is a static class.  Has no program state.
030    private ReadJSON() { }
031
032
033    // ********************************************************************************************
034    // ********************************************************************************************
035    // Object
036    // ********************************************************************************************
037    // ********************************************************************************************
038
039
040    /**
041     * Retrieve a {@link JsonArray} element, and transform it to a Java {@code Object} (POJO).
042     * <EMBED CLASS='external-HTML' DATA-FILE-ID=JR_PC_NOTE>
043     * <EMBED CLASS=defs DATA-JTYPE=JsonObject DATA-TYPE='Type Parameter T'>
044     * 
045     * @param <T>           <EMBED CLASS='external-html' DATA-FILE-ID=JR_TYPE_T>
046     * @param ja            Any instance of {@link JsonArray}
047     * @param index         <EMBED CLASS='external-html' DATA-FILE-ID=JR_INDEX_JA>
048     * @param c             <EMBED CLASS='external-html' DATA-FILE-ID=JR_CLASS>
049     * @param throwOnNull   <EMBED CLASS='external-html' DATA-FILE-ID=JR_TON_JA>
050     * @return              <EMBED CLASS='external-html' DATA-FILE-ID=JR_RET_TON_JA>
051     * 
052     * @throws IndexOutOfBoundsException If {@code 'index'} is out of bounds of {@code 'ja'}
053     * @throws JsonTypeArrException      <EMBED CLASS='external-html' DATA-FILE-ID=JR_JTAEX>
054     * @throws JsonNullArrException      <EMBED CLASS='external-html' DATA-FILE-ID=JR_JNAEX>
055     * @throws JsonException             <EMBED CLASS='external-html' DATA-FILE-ID=JR_JEX_REFL>
056     */
057    public static <T> T getObject(JsonArray ja, int index, Class<T> c, boolean throwOnNull)
058    {
059        // This will throw an IndexOutOfBoundsException if the index is out of bounds.
060        JsonValue jv = ja.get(index);
061
062        switch (jv.getValueType())
063        {
064            case NULL:
065
066                // This is simple-stuff (not rocket-science).  "Type Mapping" Code has to worry
067                // about what the meaning of "null" should be.
068
069                if (throwOnNull) throw new JsonNullArrException(ja, index, OBJECT, c);
070                else return getObject(null, c);
071
072            case OBJECT: return getObject((JsonObject) jv, c);
073
074            // The JsonValue at the specified array-index does not contain a JsonObject.
075            default: throw new JsonTypeArrException(ja, index, OBJECT, jv, c);
076        }
077    }
078
079    /**
080     * Extract a {@link JsonObject} property, and transform it to a Java {@code Object} (POJO).
081     * <EMBED CLASS='external-HTML' DATA-FILE-ID=JR_PC_NOTE>
082     * <EMBED CLASS=defs DATA-TYPE='Type Parameter T' DATA-JTYPE=JsonObject>
083     * 
084     * @param <T>           <EMBED CLASS='external-html' DATA-FILE-ID=JR_TYPE_T>
085     * @param jo            Any instance of {@link JsonObject}.
086     * @param propertyName  <EMBED CLASS='external-html' DATA-FILE-ID=JR_PN_JO>
087     * @param isOptional    <EMBED CLASS='external-html' DATA-FILE-ID=JR_ISOPT_JO>
088     * @param throwOnNull   <EMBED CLASS='external-html' DATA-FILE-ID=JR_TON_JO>
089     * @return              <EMBED CLASS='external-html' DATA-FILE-ID=JR_RET_TON_JO>
090     * 
091     * @throws JsonPropMissingException <EMBED CLASS='external-html' DATA-FILE-ID=JR_JPMEX>
092     * @throws JsonTypeObjException     <EMBED CLASS='external-html' DATA-FILE-ID=JR_JTOEX>
093     * @throws JsonNullObjException     <EMBED CLASS='external-html' DATA-FILE-ID=JR_JNOEX>
094     * @throws JsonException            <EMBED CLASS='external-html' DATA-FILE-ID=JR_JEX_REFL>
095     */
096    public static <T> T getObject(
097            JsonObject jo, String propertyName, Class<T> c, boolean isOptional,
098            boolean throwOnNull
099        )
100    {
101        if (! jo.containsKey(propertyName))
102        {
103            if (isOptional) return null;
104
105            throw new JsonPropMissingException(jo, propertyName, OBJECT, c);
106        }
107
108        JsonValue jv = jo.get(propertyName);
109
110        switch (jv.getValueType())
111        {
112            case NULL:
113
114                // This is simple-stuff (not rocket-science).  "Type Mapping" Code has to
115                // worry about what the meaning of "null" should be.
116
117                if (throwOnNull) throw new JsonNullObjException(jo, propertyName, OBJECT, c);
118
119                return getObject(null, c);
120
121            case OBJECT: return getObject((JsonObject) jv, c);
122
123                // The JsonObject propertydoes not contain a JsonObject.
124            default: throw new JsonTypeObjException(jo, propertyName, OBJECT, jv, c);
125        }
126    }
127
128    /**
129     * This class contains a lot of the reason / impetus for writing {@code 'ReadJSON'}.  This
130     * does converts a {@link JsonObject} into a Java-Object.  The actual binding must be
131     * implemented by the programmer - because the class-type that is passed to this method
132     * (parameter {@code 'c'}) must have a constructor accepting this {@code JsonObject}.
133     * 
134     * <EMBED CLASS='external-html' DATA-FILE-ID=JR_PC_NOTE>
135     * @param <T> <EMBED CLASS='external-html' DATA-FILE-ID=JR_TYPE_T>
136     * 
137     * @param jo This may be any {@link JsonObject} which can be bound to {@code 'c'}.
138     * <BR /><BR /><B>NOTE:</B> This parameter may be null, and if it is, null is passed to the
139     * {@code 'c'} constructor.
140     * 
141     * @param c              <EMBED CLASS='external-html' DATA-FILE-ID=JR_CLASS>
142     * @return               An instance of the class {@code 'T'}, which is specified by {@code 'c'}
143     * @throws JsonException <EMBED CLASS='external-html' DATA-FILE-ID=JR_JEX_REFL>
144     */
145    public static <T> T getObject(JsonObject jo, Class<T> c)
146    {
147        Constructor<T> ctor = null;
148
149        try
150        {
151            // This just gets a "Constructor" using Reflection.  The main point is that the
152            // Constructor must have exactly one parameter - and that parameter must have a
153            // type "JsonObject"  (basic java.lang.reflect stuff)
154            //
155            //System.out.println("c.getName:():" + c.getName());
156
157            ctor = c.getDeclaredConstructor(JsonObject.class);
158        }
159        catch (Exception e)
160        {
161            if (c.getEnclosingClass() != null)
162            {
163                int modifiers = c.getModifiers();
164
165                if ((! Modifier.isStatic(modifiers)) ||  (! Modifier.isPublic(modifiers)))
166
167                    throw new JsonException(
168                        "Unable to retrieve POJO Constructor for class: " +
169                        "[" + c.getName() + "]\n" +
170                        "Your class appears to be a Nested-Class, however it has not been " +
171                        "declared public and static.  There is no way to retrieve a " +
172                        "1-Argument JsonObject Constructor for Nested-Type's unless the " +
173                        "type has been declared BOTH static AND public.\n" +
174                        "See Exception.getCause() for details.",
175                        e
176                    );
177            }
178
179            else throw new JsonException(
180                "Unable to retrieve POJO Constructor for class: [" + c.getName() + "]\n" +
181                "Do you have a one-argument, public, constructor for this class?\n" +
182                "Does it accept a JsonObject in its parameter list?\n" +
183                "See Exception.getCause() for details.",
184                e
185            );
186        }
187
188        // If the user has requested a class that doesn't have that kind of constructor, then
189        // there is no way to build the object.  Throw an exception.
190
191        if (ctor == null) throw new JsonException(
192            "The class which was passed to parameter 'c' [" + c.getName() + "] does not " +
193            "appear to have a constructor with precisely one parameter of type JsonObject."
194        );
195
196        // Call that constructor using the parameter 'jo', and return that instance / object
197        try
198            { return ctor.newInstance(jo); }
199
200        // NOTE: There are *MANY* possible Exception's that may be thrown by reflective
201        //       operations like those above.  Furthermore, they are *ALL* checked exceptions.
202        //       The code below wraps those exceptions into an UN-CHECKED / RuntimeException
203        //       (JsonException)
204
205        catch (Exception e)
206        {
207            throw new JsonException(
208                "Unable to instantiate class: [" + c.getName() + "] using provided " +
209                    "Java-Object\n" +
210                "See Exception.getCause() for details.",
211                e
212            );
213        }
214    }
215
216
217    // ********************************************************************************************
218    // ********************************************************************************************
219    // String
220    // ********************************************************************************************
221    // ********************************************************************************************
222
223
224    /**
225     * Retrieve a {@link JsonArray} element, and transform it to a {@code java.lang.String}.
226     * <EMBED CLASS=defs DATA-JTYPE=JsonString DATA-TYPE='java.lang.String'>
227     * 
228     * @param ja            Any instance of {@link JsonArray}
229     * @param index         <EMBED CLASS='external-html' DATA-FILE-ID=JR_INDEX_JA>
230     * @param throwOnNull   <EMBED CLASS='external-html' DATA-FILE-ID=JR_TON_JA>
231     * @return              <EMBED CLASS='external-html' DATA-FILE-ID=JR_RET_TON_JA>
232     * 
233     * @throws IndexOutOfBoundsException If {@code 'index'} is out of the bounds of {@code 'ja'}
234     * @throws JsonTypeArrException      <EMBED CLASS='external-html' DATA-FILE-ID=JR_JTAEX>
235     * @throws JsonNullArrException      <EMBED CLASS='external-html' DATA-FILE-ID=JR_JNAEX>
236     * 
237     * @see JsonValue#getValueType()
238     * @see JsonValue.ValueType#STRING
239     */
240    public static String getString(JsonArray ja, int index, boolean throwOnNull)
241    {
242        // This will throw an IndexOutOfBoundsException if the index is out of bounds.
243        JsonValue jv = ja.get(index);
244
245        switch (jv.getValueType())
246        {
247            case NULL:
248
249                // This is simple-stuff (not rocket-science).  "Type Mapping" Code has to worry
250                // about what the meaning of "null" should be.
251
252                if (throwOnNull) throw new JsonNullArrException(ja, index, STRING, String.class);
253                else return null;
254
255            case STRING: return ((JsonString) jv).getString();
256
257            // The JsonValue at the specified array-index does not contain a JsonString.
258            default: throw new JsonTypeArrException(ja, index, STRING, jv, String.class);
259        }
260    }
261
262    /**
263     * Extract a {@link JsonObject} property, and transform it to a {@code java.lang.String}.
264     * <EMBED CLASS=defs DATA-TYPE='java.lang.String' DATA-JTYPE=JsonString>
265     * 
266     * @param jo            Any instance of {@link JsonObject}.
267     * @param propertyName  <EMBED CLASS='external-html' DATA-FILE-ID=JR_PN_JO>
268     * @param isOptional    <EMBED CLASS='external-html' DATA-FILE-ID=JR_ISOPT_JO>
269     * @param throwOnNull   <EMBED CLASS='external-html' DATA-FILE-ID=JR_TON_JO>
270     * @return              <EMBED CLASS='external-html' DATA-FILE-ID=JR_RET_TON_JO>
271     * 
272     * @throws JsonPropMissingException <EMBED CLASS='external-html' DATA-FILE-ID=JR_JPMEX>
273     * @throws JsonTypeObjException     <EMBED CLASS='external-html' DATA-FILE-ID=JR_JTOEX>
274     * @throws JsonNullObjException     <EMBED CLASS='external-html' DATA-FILE-ID=JR_JNOEX>
275     * 
276     * @see JsonValue#getValueType()
277     * @see JsonValue.ValueType#STRING
278     */
279    public static String getString
280        (JsonObject jo, String propertyName, boolean isOptional, boolean throwOnNull)
281    {
282        if (! jo.containsKey(propertyName))
283        {
284            if (isOptional) return null;
285
286            throw new JsonPropMissingException(jo, propertyName, STRING, String.class);
287        }
288
289        JsonValue jv = jo.get(propertyName);
290
291        switch (jv.getValueType())
292        {
293            case NULL:
294
295                // This is simple-stuff (not rocket-science).  "Type Mapping" Code has to worry
296                // about what the meaning of "null" should be.
297
298                if (throwOnNull) throw new JsonNullObjException
299                    (jo, propertyName, STRING, String.class);
300
301                else return null;
302
303            case STRING: return ((JsonString) jv).getString();
304
305            // The JsonObject propertydoes not contain a JsonString.
306            default: throw new JsonTypeObjException(jo, propertyName, STRING, jv, String.class);
307        }
308    }
309}