001package Torello.Java.JSON;
002
003import Torello.Java.*;
004
005import Torello.Java.Function.IntTFunction;
006import Torello.Java.Function.IntIntTFunc;
007
008import javax.json.*;
009
010import javax.json.stream.JsonGenerator;
011import java.util.Vector;
012import java.lang.reflect.Field;
013import java.lang.reflect.Modifier;
014import java.io.StringWriter;
015
016/**
017 * This class may be used to help serialize Java Objects into Json Objects.
018 * 
019 * <BR /><BR />Currently, the primary use of this class is to serialize any Object-Data that is
020 * generated by user-request into a JSON-Object Request.  This class is then sent to Chrome, or any
021 * RDP-Enabled Browser, so that user-requests may be processed.
022 */
023@Torello.JavaDoc.JDHeaderBackgroundImg(EmbedTagFileID="JSON_SERIALIZER_JDHBI")
024public abstract class BaseType
025{
026    /** Do Nothing Constructor */ 
027    protected BaseType () { }
028
029    /**
030     * Implementing this method allows sub-classes to specify which JSON Properties may be absent
031     * or null.  When binding a {@link JsonObject} to a Java-Object, if some of the expected 
032     * fields for the Java-Object map to Properties which might be left-out or omitted, then that
033     * may be indicated by setting that fields array position {@code TRUE}.
034     * 
035     * <BR /><BR /><B>NOTE:</B> This array should have a length equal to the number of fields 
036     * contained by the Java Object.  The first boolean in the array should specify whether the
037     * first Object Field may by absent.  The second boolean should specify whether the second
038     * Object Field is optional in the JSON - <I>and so on and so forth...</I>
039     * 
040     * @return A {@code boolean[]} array whose length is precisely equal to the number of fields
041     * in the Java Object.
042     */
043    public abstract boolean[] optionals();
044
045    // Helper Functions for StrReplace
046    static final IntTFunction<String, String> STR_QUOTES =
047        (int a, String s) -> '\"' + s + '\"';
048
049    static final IntIntTFunc<String, String> STR_QUOTES2 =
050        (int a, int b, String s) -> '\"' + s + '\"';
051
052
053    // ********************************************************************************************
054    // ********************************************************************************************
055    // toString()
056    // ********************************************************************************************
057    // ********************************************************************************************
058
059
060    /**
061     * This method uses Java Reflection to convert the inheriting object into a {@code String}.
062     * @return A {@code java.lang.String} representation of {@code 'this'} object.
063     */
064    public String toString() { return toString(0); }
065
066    // The actual implementation of the toString
067    private final String toString(int PREVIOUS_INDENT)
068    {
069        boolean[]       optArr      = optionals();
070        Field[]         fArr        = this.getClass().getFields();
071        StringBuilder   sb          = new StringBuilder();
072        int             maxNameLen  = 0;
073        int             maxTypeLen  = 0;
074        int             maxValLen   = 0;
075
076        ParallelArrayException.check(fArr, "fArr", true, optArr, "optArr");
077
078        // NOTE: This was "added in" later, since sometimes there are "empty types"
079        if (fArr.length == 0)
080            return '[' + this.getClass().getName() + "] => Empty Marker Type\n";
081
082        // This loop is just used for determining the "Spacing" values.
083        for (Field f : fArr)
084        {
085            String      name    = f.getName();
086            Class<?>    c       = f.getType();
087            String      cStr    = c.getSimpleName();
088
089            if (name.length() > maxNameLen) maxNameLen = name.length();
090            if (cStr.length() > maxTypeLen) maxTypeLen = cStr.length();
091
092            // System.out.print(cStr + ", ");
093        }
094
095        // System.out.println();
096
097        // Finish setting the "Spacing Variables" - they determine how much white-space to add to
098        // each line of the returned string.
099
100        maxNameLen += 2;
101        maxTypeLen += 2;
102        maxValLen = 80 - maxNameLen - maxTypeLen - PREVIOUS_INDENT;
103
104        for (int i=0; i < fArr.length; i++)
105        {
106            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
107            // Get the Parameters Ready for the Giant If-Then-Switch-Statement
108            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
109
110            // When I die, and they lay me to rest, I'm going to go to the place that's the best
111            // I'm going to the spirit in the sky.  ALSO: StringParse.rightSpacePad
112            // I've never been a sinner.  I Never sinned.  I've got a friend in Jesus
113            // He's going to set me up with the Spirit in the Sky
114
115            Field       f           = fArr[i];
116            boolean     optional    = optArr[i];
117            Class<?>    c           = f.getType();
118            Object      o           = null;
119
120            // Always output the name, and the type
121            sb.append(
122                StringParse.rightSpacePad(c.getSimpleName(), maxTypeLen) +
123                StringParse.rightSpacePad(f.getName(), maxNameLen)
124            );
125
126            // This exception should never be thrown, since the Class.getFields() method shold
127            // only return "public" fields.  Thusly, the "IllegalAccessException" should be
128            // impossible.
129
130            try
131                { o = f.get(this); }
132
133            catch (IllegalAccessException e)
134                { throw new UnreachableError(); }
135
136
137            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
138            // Primitive Types
139            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
140            // 
141            // Any Primitive Type would never be one of the "Optional" values, and therefore would
142            // always have a valid-value.  Furthermore, all Primitive Field's 'toString' methods
143            // should always print on a single-line, and no "spacing magic" needs to be performed.
144
145            if (c.isPrimitive()) sb.append(o.toString() + '\n');
146
147
148            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
149            // 'null' Case - Since it has been established this is not a primitive!
150            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
151
152            // If it is not a Primitive, then it may be null.  If it is null, print 'null'
153            else if (o == null) sb.append
154                ("null" + (optional ? " (declared optional)\n" : "(not declared optional\n"));
155
156
157            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
158            // Some Simple Java Types
159            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
160            // 
161            // Do the "Sort-of Simple Types."  The easiest way to do this is just to abbreviate
162            // strings that are too long.
163
164            else if (   Number.class.isAssignableFrom(c)
165                    ||  Boolean.class.isAssignableFrom(c)
166            )
167                sb.append(StrPrint.abbrevEnd(o.toString(), true, maxValLen) + '\n');
168
169            // This is a "Separate Case" from the above if-statement, because the String's look
170            // better when they are wrapped in quotations.
171
172            else if (String.class.isAssignableFrom(c))
173                sb.append('\"' + StrPrint.abbrevEnd(o.toString(), true, maxValLen-2) + "\"\n");
174
175
176            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
177            // Handle the case that one of the Field is also a "Base-Type" (very common)
178            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
179
180            else if (BaseType.class.isAssignableFrom(c))            
181                sb.append('\n' + StrIndent.indent(((BaseType) o).toString(PREVIOUS_INDENT + 4), 4));
182
183
184            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
185            // An *ARRAY* of one of these "BaseTypes" (a.k.a. all the classes that are built)
186            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
187
188            else if (c.isArray() && BaseType.class.isAssignableFrom(c.getComponentType()))
189            {
190                if (StringParse.countCharacters(c.getSimpleName(), '[') > 1)
191                    throw new Error("BaseType had an array dimension greater than 1!");
192
193                sb.append('\n');
194
195                for (BaseType bt : (BaseType[]) o) sb.append(
196                    "    arr[" + (i++) + "]:\n" +
197                    StrIndent.indent(bt.toString(PREVIOUS_INDENT + 4), 4)
198                );
199            }
200
201
202            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
203            // Handle all of the Array Cases
204            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
205
206            else switch (c.getSimpleName())
207            {
208                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
209                // Simple Primitive Arrays
210                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
211
212                case "int[]":
213                    sb.append('[' + StrCSV.toCSV((int[]) o, null, maxValLen-2) + "]\n");
214                    break;
215
216                case "boolean[]":
217                    sb.append('[' + StrCSV.toCSV((boolean[]) o, null, maxValLen-2) + "]\n");
218                    break;
219
220
221                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
222                // Simple 2-D Primitive Arrays
223                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
224
225                case "int[][]": sb.append(
226                    StrIndent.indentAfter2ndLine(
227                        StrCSV.toCSV((int[][]) o, null, null, true, maxValLen, 5) + '\n',
228                        4, true, true
229                    ));
230                    break;
231
232                case "boolean[][]": sb.append(
233                    StrIndent.indentAfter2ndLine(
234                        StrCSV.toCSV((boolean[][]) o, null, null, true, maxValLen, 5) + '\n',
235                        4, true, true
236                    ));
237                    break;
238
239
240                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
241                // Simple Java-Object Arrays
242                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
243
244                case "Integer[]" :
245                case "Number[]"  :
246                case "Boolean[]" :
247
248                    sb.append('[' + StrCSV.toCSV((Object[]) o, true, true, maxValLen) + "]\n");
249                    break;
250
251                case "String[]":
252                    sb.append('[' + StrCSV.toCSV((String[]) o, STR_QUOTES, true, maxValLen) + "]\n");
253                    break;
254
255
256                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
257                //  Simple Java 2-D Object Arrays
258                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
259
260                case "Integer[][]" :
261                case "Number[][]"  :
262                case "Boolean[][]" : sb.append(
263                    StrIndent.indentAfter2ndLine(
264                        StrCSV.toCSV((Object[][]) o, null, null, true, maxValLen, 5) + '\n',
265                        4, true, true
266                    ));
267                    break;
268
269                case "String[][]": sb.append(
270                    StrIndent.indentAfter2ndLine(
271                        StrCSV.toCSV((String[][]) o, STR_QUOTES2, null, true, maxValLen, 5) +
272                        '\n',
273                        4, true, true
274                    ));
275                    break;
276
277                case "JsonValue": sb.append(
278                    StrPrint.abbrev(o.toString(), 50, true, null, 100)
279                );
280                break;
281
282                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
283                //  Simple Java 2-D Object Arrays
284                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
285    
286                default: throw new Error(
287                    "This Build Has not allowed for: " + c.getSimpleName() + '\n' +
288                    "Somethign has changed!"
289                );
290            }
291        }
292
293        return sb.toString();
294    }
295
296
297    // ********************************************************************************************
298    // ********************************************************************************************
299    // toJSON()
300    // ********************************************************************************************
301    // ********************************************************************************************
302
303
304    /**
305     * Serializes {@code 'this'} object into JSON.
306     * 
307     * <BR /><BR /><OL CLASS=JDOL>
308     * <LI>It is OK if 'name' is null.</LI>
309     * <LI>It is NOT-OK if 'jGen' is null.</LI>
310     * </OL>
311     * 
312     * @param name The name being assigned to this {@link JsonObject} Property.  This name may be
313     * null.  If null is passed here, the {@link JsonGenerator} will treat this Object as the top
314     * level JSON Object, not a property of a larger object.
315     * 
316     * @param jGen This is the generator instance.  Note that often, when serializing Java Objects
317     * to JSON (<I>and vice-versa!</I>), one object may be a property or field of another object.
318     * When serializing an object to JSON, if there is a field that also needs to be serialized as
319     * a {@code JsonObject}, just pass the name and this generator instance in order to include
320     * that field as a sub-property.
321     */
322    public void toJSON(String name, JsonGenerator jGen)
323    {
324        boolean[]           optArr  = optionals();
325        Field[]             fArr    = this.getClass().getFields();
326        Vector<Class<?>>    cVec    = new Vector<>();
327        Vector<String>      nVec    = new Vector<>();
328        Vector<Object>      oVec    = new Vector<>();
329
330        if (fArr.length == 0) throw new JsonException(
331            "Likely, this is an error, but not necessarily.  You are attempting to serialize a " +
332            "class that has 0 public fields, into JSON"
333        );
334
335        // Retrieve all of the Fields Available to this class.
336        // These Fields should all be public and final
337
338        try
339        {
340            for (Field f : fArr) if (! Modifier.isStatic(f.getModifiers()))
341            {
342                cVec.add(f.getType());
343                nVec.add(f.getName());
344                oVec.add(f.get(this));
345            }
346        }
347
348        catch (IllegalAccessException e)
349        {
350            System.out.println("Unable to Access the Field.");
351            System.err.println(EXCC.toString(e));
352            throw new UnreachableError();
353        }
354
355        // Write the value of these fields to the Json-Generator.  If no 'name' was provided,
356        // then write the "Top-Level Json-Object"
357
358        if (name == null)   jGen.writeStartObject();
359        else                jGen.writeStartObject(name);
360
361        WriteJSON.get(jGen, cVec, nVec, oVec, optArr);
362
363        // Close the writing of 'this' object.
364        jGen.writeEnd();
365    }
366}