1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
package Torello.Java.JSON;

import Torello.Java.*;

import Torello.Java.Function.IntTFunction;
import Torello.Java.Function.IntIntTFunc;

import javax.json.*;

import javax.json.stream.JsonGenerator;
import java.util.Vector;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.io.StringWriter;

/**
 * This class may be used to help serialize Java Objects into Json Objects.
 * 
 * <BR /><BR />Currently, the primary use of this class is to serialize any Object-Data that is
 * generated by user-request into a JSON-Object Request.  This class is then sent to Chrome, or any
 * RDP-Enabled Browser, so that user-requests may be processed.
 */
@Torello.JavaDoc.JDHeaderBackgroundImg(EmbedTagFileID="JSON_SERIALIZER_JDHBI")
public abstract class BaseType
{
    /** Do Nothing Constructor */ 
    protected BaseType () { }

    /**
     * Implementing this method allows sub-classes to specify which JSON Properties may be absent
     * or null.  When binding a {@link JsonObject} to a Java-Object, if some of the expected 
     * fields for the Java-Object map to Properties which might be left-out or omitted, then that
     * may be indicated by setting that fields array position {@code TRUE}.
     * 
     * <BR /><BR /><B>NOTE:</B> This array should have a length equal to the number of fields 
     * contained by the Java Object.  The first boolean in the array should specify whether the
     * first Object Field may by absent.  The second boolean should specify whether the second
     * Object Field is optional in the JSON - <I>and so on and so forth...</I>
     * 
     * @return A {@code boolean[]} array whose length is precisely equal to the number of fields
     * in the Java Object.
     */
    public abstract boolean[] optionals();

    // Helper Functions for StrReplace
    static final IntTFunction<String, String> STR_QUOTES =
        (int a, String s) -> '\"' + s + '\"';

    static final IntIntTFunc<String, String> STR_QUOTES2 =
        (int a, int b, String s) -> '\"' + s + '\"';


    // ********************************************************************************************
    // ********************************************************************************************
    // toString()
    // ********************************************************************************************
    // ********************************************************************************************


    /**
     * This method uses Java Reflection to convert the inheriting object into a {@code String}.
     * @return A {@code java.lang.String} representation of {@code 'this'} object.
     */
    public String toString() { return toString(0); }

    // The actual implementation of the toString
    private final String toString(int PREVIOUS_INDENT)
    {
        boolean[]       optArr      = optionals();
        Field[]         fArr        = this.getClass().getFields();
        StringBuilder   sb          = new StringBuilder();
        int             maxNameLen  = 0;
        int             maxTypeLen  = 0;
        int             maxValLen   = 0;

        ParallelArrayException.check(fArr, "fArr", true, optArr, "optArr");

        // NOTE: This was "added in" later, since sometimes there are "empty types"
        if (fArr.length == 0)
            return '[' + this.getClass().getName() + "] => Empty Marker Type\n";

        // This loop is just used for determining the "Spacing" values.
        for (Field f : fArr)
        {
            String      name    = f.getName();
            Class<?>    c       = f.getType();
            String      cStr    = c.getSimpleName();

            if (name.length() > maxNameLen) maxNameLen = name.length();
            if (cStr.length() > maxTypeLen) maxTypeLen = cStr.length();

            // System.out.print(cStr + ", ");
        }

        // System.out.println();

        // Finish setting the "Spacing Variables" - they determine how much white-space to add to
        // each line of the returned string.

        maxNameLen += 2;
        maxTypeLen += 2;
        maxValLen = 80 - maxNameLen - maxTypeLen - PREVIOUS_INDENT;

        for (int i=0; i < fArr.length; i++)
        {
            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
            // Get the Parameters Ready for the Giant If-Then-Switch-Statement
            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

            // When I die, and they lay me to rest, I'm going to go to the place that's the best
            // I'm going to the spirit in the sky.  ALSO: StringParse.rightSpacePad
            // I've never been a sinner.  I Never sinned.  I've got a friend in Jesus
            // He's going to set me up with the Spirit in the Sky

            Field       f           = fArr[i];
            boolean     optional    = optArr[i];
            Class<?>    c           = f.getType();
            Object      o           = null;

            // Always output the name, and the type
            sb.append(
                StringParse.rightSpacePad(c.getSimpleName(), maxTypeLen) +
                StringParse.rightSpacePad(f.getName(), maxNameLen)
            );

            // This exception should never be thrown, since the Class.getFields() method shold
            // only return "public" fields.  Thusly, the "IllegalAccessException" should be
            // impossible.

            try
                { o = f.get(this); }

            catch (IllegalAccessException e)
                { throw new UnreachableError(); }


            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
            // Primitive Types
            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
            // 
            // Any Primitive Type would never be one of the "Optional" values, and therefore would
            // always have a valid-value.  Furthermore, all Primitive Field's 'toString' methods
            // should always print on a single-line, and no "spacing magic" needs to be performed.

            if (c.isPrimitive()) sb.append(o.toString() + '\n');


            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
            // 'null' Case - Since it has been established this is not a primitive!
            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

            // If it is not a Primitive, then it may be null.  If it is null, print 'null'
            else if (o == null) sb.append
                ("null" + (optional ? " (declared optional)\n" : "(not declared optional\n"));


            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
            // Some Simple Java Types
            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
            // 
            // Do the "Sort-of Simple Types."  The easiest way to do this is just to abbreviate
            // strings that are too long.

            else if (   Number.class.isAssignableFrom(c)
                    ||  Boolean.class.isAssignableFrom(c)
            )
                sb.append(StrPrint.abbrevEnd(o.toString(), true, maxValLen) + '\n');

            // This is a "Separate Case" from the above if-statement, because the String's look
            // better when they are wrapped in quotations.

            else if (String.class.isAssignableFrom(c))
                sb.append('\"' + StrPrint.abbrevEnd(o.toString(), true, maxValLen-2) + "\"\n");


            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
            // Handle the case that one of the Field is also a "Base-Type" (very common)
            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

            else if (BaseType.class.isAssignableFrom(c))            
                sb.append('\n' + StrIndent.indent(((BaseType) o).toString(PREVIOUS_INDENT + 4), 4));


            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
            // An *ARRAY* of one of these "BaseTypes" (a.k.a. all the classes that are built)
            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

            else if (c.isArray() && BaseType.class.isAssignableFrom(c.getComponentType()))
            {
                if (StringParse.countCharacters(c.getSimpleName(), '[') > 1)
                    throw new Error("BaseType had an array dimension greater than 1!");

                sb.append('\n');

                for (BaseType bt : (BaseType[]) o) sb.append(
                    "    arr[" + (i++) + "]:\n" +
                    StrIndent.indent(bt.toString(PREVIOUS_INDENT + 4), 4)
                );
            }


            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
            // Handle all of the Array Cases
            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

            else switch (c.getSimpleName())
            {
                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
                // Simple Primitive Arrays
                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

                case "int[]":
                    sb.append('[' + StrCSV.toCSV((int[]) o, null, maxValLen-2) + "]\n");
                    break;

                case "boolean[]":
                    sb.append('[' + StrCSV.toCSV((boolean[]) o, null, maxValLen-2) + "]\n");
                    break;


                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
                // Simple 2-D Primitive Arrays
                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

                case "int[][]": sb.append(
                    StrIndent.indentAfter2ndLine(
                        StrCSV.toCSV((int[][]) o, null, null, true, maxValLen, 5) + '\n',
                        4, true, true
                    ));
                    break;

                case "boolean[][]": sb.append(
                    StrIndent.indentAfter2ndLine(
                        StrCSV.toCSV((boolean[][]) o, null, null, true, maxValLen, 5) + '\n',
                        4, true, true
                    ));
                    break;


                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
                // Simple Java-Object Arrays
                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

                case "Integer[]" :
                case "Number[]"  :
                case "Boolean[]" :

                    sb.append('[' + StrCSV.toCSV((Object[]) o, true, true, maxValLen) + "]\n");
                    break;

                case "String[]":
                    sb.append('[' + StrCSV.toCSV((String[]) o, STR_QUOTES, true, maxValLen) + "]\n");
                    break;


                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
                //  Simple Java 2-D Object Arrays
                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

                case "Integer[][]" :
                case "Number[][]"  :
                case "Boolean[][]" : sb.append(
                    StrIndent.indentAfter2ndLine(
                        StrCSV.toCSV((Object[][]) o, null, null, true, maxValLen, 5) + '\n',
                        4, true, true
                    ));
                    break;

                case "String[][]": sb.append(
                    StrIndent.indentAfter2ndLine(
                        StrCSV.toCSV((String[][]) o, STR_QUOTES2, null, true, maxValLen, 5) +
                        '\n',
                        4, true, true
                    ));
                    break;

                case "JsonValue": sb.append(
                    StrPrint.abbrev(o.toString(), 50, true, null, 100)
                );
                break;

                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
                //  Simple Java 2-D Object Arrays
                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
    
                default: throw new Error(
                    "This Build Has not allowed for: " + c.getSimpleName() + '\n' +
                    "Somethign has changed!"
                );
            }
        }

        return sb.toString();
    }


    // ********************************************************************************************
    // ********************************************************************************************
    // toJSON()
    // ********************************************************************************************
    // ********************************************************************************************


    /**
     * Serializes {@code 'this'} object into JSON.
     * 
     * <BR /><BR /><OL CLASS=JDOL>
     * <LI>It is OK if 'name' is null.</LI>
     * <LI>It is NOT-OK if 'jGen' is null.</LI>
     * </OL>
     * 
     * @param name The name being assigned to this {@link JsonObject} Property.  This name may be
     * null.  If null is passed here, the {@link JsonGenerator} will treat this Object as the top
     * level JSON Object, not a property of a larger object.
     * 
     * @param jGen This is the generator instance.  Note that often, when serializing Java Objects
     * to JSON (<I>and vice-versa!</I>), one object may be a property or field of another object.
     * When serializing an object to JSON, if there is a field that also needs to be serialized as
     * a {@code JsonObject}, just pass the name and this generator instance in order to include
     * that field as a sub-property.
     */
    public void toJSON(String name, JsonGenerator jGen)
    {
        boolean[]           optArr  = optionals();
        Field[]             fArr    = this.getClass().getFields();
        Vector<Class<?>>    cVec    = new Vector<>();
        Vector<String>      nVec    = new Vector<>();
        Vector<Object>      oVec    = new Vector<>();

        if (fArr.length == 0) throw new JsonException(
            "Likely, this is an error, but not necessarily.  You are attempting to serialize a " +
            "class that has 0 public fields, into JSON"
        );

        // Retrieve all of the Fields Available to this class.
        // These Fields should all be public and final

        try
        {
            for (Field f : fArr) if (! Modifier.isStatic(f.getModifiers()))
            {
                cVec.add(f.getType());
                nVec.add(f.getName());
                oVec.add(f.get(this));
            }
        }

        catch (IllegalAccessException e)
        {
            System.out.println("Unable to Access the Field.");
            System.err.println(EXCC.toString(e));
            throw new UnreachableError();
        }

        // Write the value of these fields to the Json-Generator.  If no 'name' was provided,
        // then write the "Top-Level Json-Object"

        if (name == null)   jGen.writeStartObject();
        else                jGen.writeStartObject(name);

        WriteJSON.get(jGen, cVec, nVec, oVec, optArr);

        // Close the writing of 'this' object.
        jGen.writeEnd();
    }
}