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        final Constructor<T> ctor;
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
160        catch (Exception e)
161        {
162            if (c.getEnclosingClass() != null)
163            {
164                int modifiers = c.getModifiers();
165
166                if ((! Modifier.isStatic(modifiers)) ||  (! Modifier.isPublic(modifiers)))
167
168                    throw new JsonException(
169                        "Unable to retrieve POJO Constructor for class: " +
170                        "[" + c.getName() + "]\n" +
171                        "Your class appears to be a Nested-Class, however it has not been " +
172                        "declared public and static.  There is no way to retrieve a " +
173                        "1-Argument JsonObject Constructor for Nested-Type's unless the " +
174                        "type has been declared BOTH static AND public.\n" +
175                        "See Exception.getCause() for details.",
176                        e
177                    );
178
179                else throw new JsonException(
180                    "There was a problem retrieving a one-argument, public, constructor for the " +
181                    "class you wish to instantiate.  See Exception.getCause() for details.",
182                    e
183                );
184            }
185
186            else throw new JsonException(
187                "Unable to retrieve POJO Constructor for class: [" + c.getName() + "]\n" +
188                "Do you have a one-argument, public, constructor for this class?\n" +
189                "Does it accept a JsonObject in its parameter list?\n" +
190                "See Exception.getCause() for details.",
191                e
192            );
193        }
194
195
196        // If the user has requested a class that doesn't have that kind of constructor, then
197        // there is no way to build the object.  Throw an exception.
198
199        if (ctor == null) throw new JsonException(
200            "The class which was passed to parameter 'c' [" + c.getName() + "] does not " +
201            "appear to have a constructor with precisely one parameter of type JsonObject."
202        );
203
204        // Call that constructor using the parameter 'jo', and return that instance / object
205        try
206            { return ctor.newInstance(jo); }
207
208        // NOTE: There are *MANY* possible Exception's that may be thrown by reflective
209        //       operations like those above.  Furthermore, they are *ALL* checked exceptions.
210        //       The code below wraps those exceptions into an UN-CHECKED / RuntimeException
211        //       (JsonException)
212
213        catch (Exception e)
214        {
215            throw new JsonException(
216                "Unable to instantiate class: [" + c.getName() + "] using provided " +
217                    "Java-Object\n" +
218                "See Exception.getCause() for details.",
219                e
220            );
221        }
222    }
223
224
225    // ********************************************************************************************
226    // ********************************************************************************************
227    // String
228    // ********************************************************************************************
229    // ********************************************************************************************
230
231
232    /**
233     * Retrieve a {@link JsonArray} element, and transform it to a {@code java.lang.String}.
234     * <EMBED CLASS=defs DATA-JTYPE=JsonString DATA-TYPE='java.lang.String'>
235     * 
236     * @param ja            Any instance of {@link JsonArray}
237     * @param index         <EMBED CLASS='external-html' DATA-FILE-ID=JR_INDEX_JA>
238     * @param throwOnNull   <EMBED CLASS='external-html' DATA-FILE-ID=JR_TON_JA>
239     * @return              <EMBED CLASS='external-html' DATA-FILE-ID=JR_RET_TON_JA>
240     * 
241     * @throws IndexOutOfBoundsException If {@code 'index'} is out of the bounds of {@code 'ja'}
242     * @throws JsonTypeArrException      <EMBED CLASS='external-html' DATA-FILE-ID=JR_JTAEX>
243     * @throws JsonNullArrException      <EMBED CLASS='external-html' DATA-FILE-ID=JR_JNAEX>
244     * 
245     * @see JsonValue#getValueType()
246     * @see JsonValue.ValueType#STRING
247     */
248    public static String getString(JsonArray ja, int index, boolean throwOnNull)
249    {
250        // This will throw an IndexOutOfBoundsException if the index is out of bounds.
251        JsonValue jv = ja.get(index);
252
253        switch (jv.getValueType())
254        {
255            case NULL:
256
257                // This is simple-stuff (not rocket-science).  "Type Mapping" Code has to worry
258                // about what the meaning of "null" should be.
259
260                if (throwOnNull) throw new JsonNullArrException(ja, index, STRING, String.class);
261                else return null;
262
263            case STRING: return ((JsonString) jv).getString();
264
265            // The JsonValue at the specified array-index does not contain a JsonString.
266            default: throw new JsonTypeArrException(ja, index, STRING, jv, String.class);
267        }
268    }
269
270    /**
271     * Extract a {@link JsonObject} property, and transform it to a {@code java.lang.String}.
272     * <EMBED CLASS=defs DATA-TYPE='java.lang.String' DATA-JTYPE=JsonString>
273     * 
274     * @param jo            Any instance of {@link JsonObject}.
275     * @param propertyName  <EMBED CLASS='external-html' DATA-FILE-ID=JR_PN_JO>
276     * @param isOptional    <EMBED CLASS='external-html' DATA-FILE-ID=JR_ISOPT_JO>
277     * @param throwOnNull   <EMBED CLASS='external-html' DATA-FILE-ID=JR_TON_JO>
278     * @return              <EMBED CLASS='external-html' DATA-FILE-ID=JR_RET_TON_JO>
279     * 
280     * @throws JsonPropMissingException <EMBED CLASS='external-html' DATA-FILE-ID=JR_JPMEX>
281     * @throws JsonTypeObjException     <EMBED CLASS='external-html' DATA-FILE-ID=JR_JTOEX>
282     * @throws JsonNullObjException     <EMBED CLASS='external-html' DATA-FILE-ID=JR_JNOEX>
283     * 
284     * @see JsonValue#getValueType()
285     * @see JsonValue.ValueType#STRING
286     */
287    public static String getString
288        (JsonObject jo, String propertyName, boolean isOptional, boolean throwOnNull)
289    {
290        if (! jo.containsKey(propertyName))
291        {
292            if (isOptional) return null;
293
294            throw new JsonPropMissingException(jo, propertyName, STRING, String.class);
295        }
296
297        JsonValue jv = jo.get(propertyName);
298
299        switch (jv.getValueType())
300        {
301            case NULL:
302
303                // This is simple-stuff (not rocket-science).  "Type Mapping" Code has to worry
304                // about what the meaning of "null" should be.
305
306                if (throwOnNull) throw new JsonNullObjException
307                    (jo, propertyName, STRING, String.class);
308
309                else return null;
310
311            case STRING: return ((JsonString) jv).getString();
312
313            // The JsonObject propertydoes not contain a JsonString.
314            default: throw new JsonTypeObjException(jo, propertyName, STRING, jv, String.class);
315        }
316    }
317}