001package Torello.JSON;
002
003import javax.json.*;
004import Torello.Java.StrPrint;
005
006/**
007 * The parent class of all Json Exceptions in this package.  This class contains several
008 * convenience fields that will be auto-populated by any method inside Java HTML that would throw
009 * this exception, or any of this exception's descendant classes.
010 * 
011 * <BR /><BR /><B>NOTE:</B> This class is abstract, and cannot be instantiated.
012 * 
013 * <EMBED CLASS=globalDefs DATA-STRUCT=JsonArray>
014 * <EMBED CLASS='external-html' DATA-FILE-ID=JE_FIELD_BIND>
015 */
016@Torello.JavaDoc.Annotations.CSSLinks(FileNames="JSONExceptions.css")
017public abstract class JsonBindingException extends JsonException
018{
019    /** <EMBED CLASS='external-html' DATA-FILE-ID=SVUIDEX>  */
020    public static final long serialVersionUID = 1;
021
022    /**
023     * <EMBED CLASS='external-html' DATA-FILE-ID=EXPF>
024     * 
025     * <BR /><BR />This contains the actual {@link JsonStructure} (which should either be an
026     * instance of <B>{@link JsonObject}</B> or <B>{@link JsonArray}</B>) which contained the
027     * array-element or object-property that has caused this exception throw.
028     */
029    public final JsonStructure errorSourceJsonStruct;
030
031    /**
032     * <EMBED CLASS='external-html' DATA-FILE-ID=EXPF>
033     * 
034     * <BR /><BR />This contains the <B STYLE='color: red;'>expected type</B> which ought to have
035     * been found at the user-specified <B>{@link JsonArray}</B> or <B>{@link JsonObject}</B>
036     * location.  Since both of these kinds of <B>{@link JsonStructure}</B> are only allowed to
037     * have <B STYLE='color: red;'>Json-Type's</B>; therefore this field's declared type is 
038     * <B>{@link JsonValue.ValueType}</B>.
039     * 
040     * <BR /><BR /><B STYLE='color: red;'>ASIDE:</B> The enum <B>{@link JsonValue.ValueType}</B>
041     * has two enum-constants which represent the <B STYLE='color: red'>Json-Type</B>
042     * 'Boolean'.  As a result, there are two possible values to which this field could be
043     * assigned to symbolize a <B>Json Boolean Type</B> ({@link JsonValue.ValueType#FALSE} and
044     * {@link JsonValue.ValueType#TRUE}).  However, in Java HTML, a Json-Boolean Type will always
045     * be represented using the {@code TRUE} enum-constant.
046     */
047    public final JsonValue.ValueType expectedJsonType;
048
049    /**
050     * <EMBED CLASS='external-html' DATA-FILE-ID=EXPF>
051     * 
052     * <BR /><BR />This is the value retrieved from the <B>{@link JsonArray}</B> or
053     * <B>{@link JsonObject}</B>.  If the value was not present or unavailable, then this parameter
054     * will, indeed, evaulated to <B STYLE='color: red;'>Java-Null</B>.
055     * If <B STYLE='color: red;'>Json-Null</B> was retrieved, then this parameter will contain
056     * <B>{@link JsonValue#NULL}</B>.
057     */
058    public final JsonValue valueRetrieved;
059
060    /**
061     * <EMBED CLASS='external-html' DATA-FILE-ID=EXPF>
062     * 
063     * <BR /><BR />This specifies <B STYLE='color: red;'>return type</B> (as an instance of
064     * <B>{@code java.lang.Class<T>}</B> that is used by the method which has thrown this
065     * exception.  This is the <B STYLE='color:red'>Java-Type</B> to which the 
066     * <B STYLE='color:red'>Json-Type</B> was going to be assigned.
067     */
068    public final Class<?> methodReturnJavaType;
069
070    /**
071     * Constructs a {@code JsonBindingException} with no specified detail messsage,
072     * and the user-provided convenience-field values.
073     * 
074     * @param errorSourceJsonStruct <EMBED CLASS='external-html' DATA-FILE-ID=JBEX_ESJS>
075     * @param expectedJsonType      <EMBED CLASS='external-html' DATA-FILE-ID=JBEX_EJT>
076     * @param valueRetrieved        <EMBED CLASS='external-html' DATA-FILE-ID=JBEX_VR>
077     * @param methodReturnJavaType  <EMBED CLASS='external-html' DATA-FILE-ID=JBEX_MRJT>
078     */
079    /** Constructs a {@code JsonBindingException} with no detail message. */
080    public JsonBindingException(
081            JsonStructure       errorSourceJsonStruct,
082            JsonValue.ValueType expectedJsonType,
083            JsonValue           valueRetrieved,
084            Class<?>            methodReturnJavaType
085        )
086    {
087        super(
088            BASE_MESSAGE(
089                errorSourceJsonStruct, expectedJsonType, valueRetrieved,
090                methodReturnJavaType
091            ));
092
093        this.errorSourceJsonStruct  = errorSourceJsonStruct;
094        this.expectedJsonType       = expectedJsonType;
095        this.valueRetrieved         = valueRetrieved;
096        this.methodReturnJavaType   = methodReturnJavaType;
097    }
098
099    /**
100     * Constructs a {@code JsonBindingException} with the specified detail message, and
101     * user-provided convenience-field values.
102     * 
103     * @param message the detail message.
104     * @param errorSourceJsonStruct <EMBED CLASS='external-html' DATA-FILE-ID=JBEX_ESJS>
105     * @param expectedJsonType      <EMBED CLASS='external-html' DATA-FILE-ID=JBEX_EJT>
106     * @param valueRetrieved        <EMBED CLASS='external-html' DATA-FILE-ID=JBEX_VR>
107     * @param methodReturnJavaType  <EMBED CLASS='external-html' DATA-FILE-ID=JBEX_MRJT>
108     */
109    public JsonBindingException(
110            String              message,
111            JsonStructure       errorSourceJsonStruct,
112            JsonValue.ValueType expectedJsonType,
113            JsonValue           valueRetrieved,
114            Class<?>            methodReturnJavaType
115        )
116    {
117        super(message);
118
119        this.errorSourceJsonStruct  = errorSourceJsonStruct;
120        this.expectedJsonType       = expectedJsonType;
121        this.valueRetrieved         = valueRetrieved;
122        this.methodReturnJavaType   = methodReturnJavaType;
123    }
124
125    /**
126     * Constructs a new {@code JsonBindingException} with the specified detail message and cause.
127     * 
128     * <BR /><BR /><B CLASS=JDDescLabel>NOTE:</B>
129     * 
130     * <BR /><BR />The detail message associated with cause is not automatically incorporated into
131     * this exception's detail message.
132     * 
133     * @param message The detail message (which is saved for later retrieval by the
134     * {@code Throwable.getMessage()} method).
135     * 
136     * @param cause the cause (which is saved for later retrieval by the
137     * {@code Throwable.getCause()} method).  (A null value is permitted, and indicates that the
138     * cause is nonexistent or unknown.)
139     * 
140     * @param errorSourceJsonStruct <EMBED CLASS='external-html' DATA-FILE-ID=JBEX_ESJS>
141     * @param expectedJsonType      <EMBED CLASS='external-html' DATA-FILE-ID=JBEX_EJT>
142     * @param valueRetrieved        <EMBED CLASS='external-html' DATA-FILE-ID=JBEX_VR>
143     * @param methodReturnJavaType  <EMBED CLASS='external-html' DATA-FILE-ID=JBEX_MRJT>
144     */
145    public JsonBindingException(
146            String              message,
147            Throwable           cause,
148            JsonStructure       errorSourceJsonStruct,
149            JsonValue.ValueType expectedJsonType,
150            JsonValue           valueRetrieved,
151            Class<?>            methodReturnJavaType
152        )
153    {
154        super(message, cause);
155
156        this.errorSourceJsonStruct  = errorSourceJsonStruct;
157        this.expectedJsonType       = expectedJsonType;
158        this.valueRetrieved         = valueRetrieved;
159        this.methodReturnJavaType   = methodReturnJavaType;
160    }
161
162    /**
163     * Constructs a new {@code JsonBindingException} with the specified cause and a detail message of
164     * {@code (cause==null ? null : cause.toString())} (which typically contains the class and
165     * detail message of cause).
166     * 
167     * <BR /><BR />This constructor is useful for exceptions that are little more than wrappers for
168     * other throwables.
169     * 
170     * @param cause The cause (which is saved for later retrieval by the
171     * {@code Throwable.getCause()} method).  (A null value is permitted, and indicates that the
172     * cause is nonexistent or unknown.)
173     * 
174     * @param errorSourceJsonStruct <EMBED CLASS='external-html' DATA-FILE-ID=JBEX_ESJS>
175     * @param expectedJsonType      <EMBED CLASS='external-html' DATA-FILE-ID=JBEX_EJT>
176     * @param valueRetrieved        <EMBED CLASS='external-html' DATA-FILE-ID=JBEX_VR>
177     * @param methodReturnJavaType  <EMBED CLASS='external-html' DATA-FILE-ID=JBEX_MRJT>
178     */
179    public JsonBindingException(
180            Throwable           cause,
181            JsonStructure       errorSourceJsonStruct,
182            JsonValue.ValueType expectedJsonType,
183            JsonValue           valueRetrieved,
184            Class<?>            methodReturnJavaType
185        )
186    {
187        super(
188            BASE_MESSAGE(
189                errorSourceJsonStruct, expectedJsonType, valueRetrieved,
190                methodReturnJavaType, cause
191            ),
192            cause
193        );
194
195        this.errorSourceJsonStruct  = errorSourceJsonStruct;
196        this.expectedJsonType       = expectedJsonType;
197        this.valueRetrieved         = valueRetrieved;
198        this.methodReturnJavaType   = methodReturnJavaType;
199    }
200
201    // Package Visible Exception Message Helper
202    static String ABBREV_STRUCT(JsonStructure js)
203    {
204        return (js != null) 
205            ? StrPrint.abbrevEnd(js.toString().trim(), true, MAX_STR_LEN)
206            : "<Null Was Passed to Source JSON Ctor-Param>";
207    }
208
209    private static final String JVVT_NAME   = "JsonValue.ValueType.";
210    private static final int    MAX_STR_LEN = 70;
211
212    // Package Visible Exception Message Helper
213    static String JT_STR(JsonValue.ValueType jt)
214    {
215        if (jt == null) return "Json-Type Not Available";
216
217        switch (jt)
218        {
219            case ARRAY:     return JVVT_NAME + "ARRAY";
220            case FALSE:     return JVVT_NAME + "FALSE";
221            case NULL:      return JVVT_NAME + "NULL";
222            case NUMBER:    return JVVT_NAME + "NUMBER";
223            case OBJECT:    return JVVT_NAME + "OBJECT";
224            case STRING:    return JVVT_NAME + "STRING";
225            case TRUE:      return JVVT_NAME + "TRUE";
226            default:        throw new Torello.Java.UnreachableError();
227        }
228    }
229
230    // Package Visible Exception Message Helper
231    static String ABBREV_VAL(JsonValue valueRetrieved)
232    {
233        if (valueRetrieved == null) return "Java-Null (Not Present)";
234
235        if (valueRetrieved.getValueType() == JsonValue.ValueType.NULL)
236            return "JsonValue.NULL (Json-Null)";
237
238        return StrPrint.abbrevEnd(valueRetrieved.toString().trim(), true, MAX_STR_LEN);
239    }
240
241    static String CAUSE_MESSAGE(Throwable[] causes)
242    {
243        if ((causes == null) || (causes.length == 0)) return "";
244
245        final String raw    = causes[0].getMessage();
246        final String temp   = (raw != null) ? raw.trim() : "<null>";
247
248        final String seeDetailsMessage =
249            (   (temp.length() > MAX_STR_LEN)
250            ||  (temp.indexOf('\n') != -1)
251            )
252                ? "\tSee Throwable.cause():    [The message has been abbreviated]\n"
253                : "";
254
255        return
256            "\tCause-Exception Class:    " + causes[0].getClass().getName() + "\n" +
257            "\tCause-Exception Message:  " + StrPrint.abbrevEnd(temp, true, MAX_STR_LEN) + "\n" +
258            seeDetailsMessage;
259    }
260
261    static String RET_TYPE_STR(final Class<?> methodReturnJavaType)
262    {
263        final String classNameStr = (methodReturnJavaType == null)
264            ? null
265            : (methodReturnJavaType.getCanonicalName() != null
266                ? methodReturnJavaType.getCanonicalName()
267                : methodReturnJavaType.getName());
268
269        if (methodReturnJavaType == null)
270            return "\tConverting To Java-Type:  Java-Type Unknown";
271
272        else if (methodReturnJavaType == JsonObject.class)
273            return "\tReturning Java Json-Type: JsonObject";
274
275        else if (methodReturnJavaType == JsonArray.class)
276            return "\tReturning Java Json-Type: JsonArray";
277
278        else
279            return "\tConverting To Java-Type:  " + classNameStr;
280
281    }
282
283    /**
284     * A simple helper method for printing consistent error messages using the input-data
285     * convenience fields.
286     * 
287     * @param errorSourceJsonStruct <EMBED CLASS='external-html' DATA-FILE-ID=JBEX_ESJS>
288     * @param expectedJsonType      <EMBED CLASS='external-html' DATA-FILE-ID=JBEX_EJT>
289     * @param valueRetrieved        <EMBED CLASS='external-html' DATA-FILE-ID=JBEX_VR>
290     * @param methodReturnJavaType  <EMBED CLASS='external-html' DATA-FILE-ID=JBEX_MRJT>
291     * @param causes                Optional Parameter.  At most 1 cause is printed.
292     * 
293     * @return The error message {@code String}.
294     */
295    protected static String BASE_MESSAGE(
296            JsonStructure       errorSourceJsonStruct,
297            JsonValue.ValueType expectedJsonType,
298            JsonValue           valueRetrieved,
299            Class<?>            methodReturnJavaType,
300            Throwable...        causes
301        )
302    {
303        // After much argument with Chat-GPT, I have decided to encode the following line
304        // right here
305
306        final String srcJsonAsStr = (errorSourceJsonStruct == null)
307
308            // This means that the user's null JO or JA has trickled all the way here, before
309            // and NPE has thrown.  The NPE should have thrown the minute that ReadJSON attempts
310            // to extract an element from an array or object that is null.
311            // 
312            // I don't use "Assert Statements", and I don't know why, but that's how this works.
313            // This is just a glorified "Assert Statement".  This should never happen.  The value
314            // passed to "errorSourceJsonStruct" is just the JsonArray or JsonObject that the user
315            // requested for data extraction.  If that were null, an actual NPE should throw by the
316            // second or third line of the method.
317    
318            ? (
319                '\n' +
320                "\t*** ERROR: The Source Json-Structure you have passed is, itself, null\n" +
321                "\t*** HOWEVER: My code should have already thrown an NPE, the moment you passed " +
322                    "null\n"+
323                "\t*** SORRY!"
324                )
325
326            : errorSourceJsonStruct.getClass().getSimpleName();
327
328        return
329            "Problems Binding Json\n" +
330            CAUSE_MESSAGE(causes) +
331            "\tFound In JsonStructure:   " + ABBREV_STRUCT(errorSourceJsonStruct) + "\n" +
332            "\tJsonStructure SubClass:   " + srcJsonAsStr + "\n" +
333            "\tExpected Json-Type:       " + JT_STR(expectedJsonType) + "\n" +
334            "\tContained JsonValue:      " + ABBREV_VAL(valueRetrieved) + "\n" +
335            "\tHaving Actual Json-Type:  " + JT_STR((valueRetrieved != null)
336                    ? valueRetrieved.getValueType()
337                    : null
338                ) + "\n" +
339            RET_TYPE_STR(methodReturnJavaType) + '\n';
340    }
341
342}