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
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
package Torello.Java.Additional;

import Torello.Java.*;

/**
 * This is a parent class of the 'Multiple Return Type' classes. ({@code Ret2 ... Ret8})  This
 * class provides some basic functionality to its descendants, namely: {@link #toString()}, 
 * {@link #hashCode()} and {@link #equals(RetN)}.
 */
public abstract class RetN
{
    RetN() { } // Cannot be private

    private static final String[] fieldNames2To5 =
    { "a", "b", "c", "d", "e" };

    private static final String[] fieldNames6To8 =
    { "a1", "b2", "c3", "d4", "e5", "f6", "g7", "h8" };

    abstract Object[] asArrayInternal();

    /**
     * All implementations of this {@code abstract} can indicate which of the {@code RetN}
     * instances they are, by returning the number of fields they hold using this method.
     * 
     * @return The number of fields contained by the class that is implementing {@code 'this'}
     * instance of {@code RetN}.  So for example, an instance of {@code Ret5} would produce
     * {@code '5'}, if this method were called; and {@code Ret3} would produce {@code '3'}, and
     * so on and so forth.
     */
    public abstract int n();

    /**
     * Optimization: This is saved, so that <I>it is only computed once by the descendant class</I>
     * 
     * <BR /><BR /><B>NOTE:</B> The {@code 'transient'} modifier implies that if an instance class
     * of, say {@code Ret3}, is peristed using {@code Object}-serialization, this field will not be
     * saved.
     * 
     * <BR /><BR /><B>ALSO:</B> If a class which inherits any of the {@code RetN} implementations
     * were to modify this array, the {@link toString} method would return a {@code String} which
     * is not consistent with the data inside the the instance.  Java does not really support "read
     * only" arrays, but if it did, this would be a candidate for such a feature.
     */
    transient Object[] objArr = null;

    /**
     * Retrieve the contents of the instance-descendant class, as an array.
     * @return Returns the instance fields {@code 'a', 'b', 'c'} ... of the class that has
     * extended this class as an {@code Object[]} array.
     */
    public final Object[] asArray()
    {
        // All classes that inherit RetN implement the 'asArrayInternal()' method.
        // This method simply places the 'a', 'b', 'c' etc... fields into an Object[] array.
        if (this.objArr == null) this.objArr = asArrayInternal();

        return this.objArr;        
    }

    /**
     * This will return an instance of {@code Object} which represents the
     * <CODE>i<SUP>th</SUP></CODE> instance-field from whatever {@code RetN} implementation this
     * has been invoked.
     * 
     * <BR /><BR />If the implementing class were {@code Ret5}, and {@code '3'} were passed to
     * parameter {@code 'i'}, then the value in {@code Ret5.c} would be returned.
     * 
     * <BR /><BR />If a call to {@code 'get'} is made from {@code Ret2}, and {@code '3'} were
     * passed to {@code 'i'}, an {@code IndexOutOfBoundsException} is thrown.
     * 
     * <EMBED CLASS='external-html' DATA-FILE-ID=RETNEX>
     * 
     * @param i This specifies which field of the implementing {@code RetN} is being requested.
     *
     * <BR /><BR /><B STYLE='color:red;'>IMPORTANT:</B> Unlike a Java array, when a {@code '1'}
     * is passed to this parameter, it is requesting the first field in the {@code RetN}.  
     * Passing a value of '0' shall cause an {@code IndexOutOfBoundsException} throw.
     * 
     * @return This returns the <CODE>i<SUP>th</SUP></CODE> field of this class.
     * 
     * @throws IndexOutOfBoundsException if {@code 'i'} is zero or negative, or greater than
     * the value of {@code 'N'} for whichever {@code RetN} class is being used.  If
     * {@code 'this'} is an instance of {@code Ret5}, then a value of 6 or greater will cause this
     * exception to throw.
     * 
     * @see #asArray()
     * @see #get(int, Class)
     * @see #GET(int)
     */
    public final Object get(int i)
    {
        Object[] objArr = asArray();

        if (i < 1) throw new IndexOutOfBoundsException(
            "You have passed " + i + " to parameter i.  This number must be " +
            ((objArr.length > 1)
                ? ("between 1 and " + objArr.length)
                : "must be exactly 1.")
        );

        else if (i > objArr.length) throw new IndexOutOfBoundsException(
            "You have requested the " + StringParse.ordinalIndicator(i) + " field of " +
            "this class instance-fields, but unfortunately there " + 
            ((objArr.length > 1)
                ? ("are only " + objArr.length + " fields.")
                : ("is only 1 field.")
            )
        );

        return objArr[i-1]; // minus one, arrays start at index 0.
    }

    /**
     * This provides a quick way to cast the field to the requested class.  This may be quicker
     * typing than using an actual cast, because it will not generate a compiler warning about
     * unchecked casts.  If the cast fails, it will throw the usual {@code ClassCastException}.
     * 
     * @param i This specifies which field of the implementing {@code RetN} is being requested.
     *
     * <BR /><BR /><B STYLE='color:red;'>IMPORTANT:</B> Unlike a Java array, when a {@code '1'}
     * is passed to this parameter, it is requesting the first field in the {@code RetN}.  
     * Passing a value of '0' shall case an {@code IndexOutOfBoundsException} exception.
     * 
     * @return This returns the <CODE>i<SUP>th</SUP></CODE> field of this class.
     * 
     * @throws IndexOutOfBoundsException if {@code 'i'} is zero or negative, or greater than
     * the value of {@code 'N'} for whichever {@code RetN} class is being used.  If
     * {@code 'this'} is an instance of {@code Ret5}, then a value of 6 or greater will cause this
     * exception to throw.
     * 
     * @throws ClassCastException If the field cannot be cast to the class provided.
     * 
     * @see #get(int)
     * @see #GET(int)
     */
    public final <T> T get(int i, Class<T> c) { return c.cast(get(i)); }

    /**
     * This is <I><B STYLE='color:red;'>more magic</B></I> with Java's Type-Inferencing being
     * applied to a Generic-Cast.  Note that this method works a lot like (but not identical to)
     * the later Java {@code 'var'} syntax.  Here, Java's Type-Inference Mechanism (inside the
     * compile-time, not run-time, logic) will cast the result of this method to whatever type is
     * on the left hand-side of the assignment.
     * 
     * <BR /><BR /><B STYLE='color:red'>NOTE:</B> The Java Compiler will output a Compile-Time
     * Error if it appears that it cannot infer type-parameter {@code 'T'}.
     * 
     * <BR /><BR /><B STYLE='color:red'>ALSO:</B> This method will throw a Run-Time Exception if
     * the cast fails (a {@code 'ClassCastException'})
     *
     * @param <T> This Type-Parameter is inferred by whatever assignment is taking place, or however
     * the result of this method is being applied, programatically.  More can be read about Java's
     * Compile-Time Type-Inferencing Mechanism on the Oracle Website.
     * 
     * @param i This specifies which field of the implementing {@code RetN} is being requested.
     *
     * <BR /><BR /><B STYLE='color:red;'>IMPORTANT:</B> Unlike a Java array, when a {@code '1'}
     * is passed to this parameter, it is requesting the first field in the {@code RetN}.  
     * Passing a value of '0' shall case an {@code IndexOutOfBoundsException} exception.
     * 
     * @return This returns the <CODE>i<SUP>th</SUP></CODE> field of this class.
     * 
     * @throws IndexOutOfBoundsException if {@code 'i'} is zero or negative, or greater than
     * the value of {@code 'N'} for whichever {@code RetN} class is being used.  If
     * {@code 'this'} is an instance of {@code Ret5}, then a value of 6 or greater will cause this
     * exception to throw.
     * 
     * @throws ClassCastException If the <CODE>i<SUP>th</SUP></CODE> field of this class cannot be
     * cast to <I>the type that is inferred for Type-Parameter {@code 'T'}.</I>
     */
    @SuppressWarnings("unchecked")
    public final <T> T GET(int i) { return (T) get(i); }

    /**
     * Compares {@code 'this'} with another instance of {@code RetN}.
     * @param other Another instance of {@code Ret8}.
     * @return If {@code 'this'} instance of {@code Ret8} is equal to the {@code 'other'} instance
     * of {@code Ret8}.  
     */
    public boolean equals(RetN other)
    {
        if (this == other) return true;

        Object[] theseFields = asArray();
        Object[] thoseFields = other.asArray();

        // This should never happen, because the RetN that calls this only accepts a RetN
        // of the same type.  Leave this here, anyway, you will forget that, and try to type
        // this again.
        if (theseFields.length != thoseFields.length) return false;

        for (int i=0; i < theseFields.length; i++)

            if (theseFields[i] == thoseFields[i])
                continue;

            else if (
                        ((theseFields[i] != null) && (thoseFields[i] != null))
                        &&
                        (theseFields[i].getClass() == thoseFields[i].getClass())
                        &&
                        theseFields[i].equals(thoseFields[i])
                    )
                continue;

            else
                return false;

        return true;
    }

    /**
     * Builds a hash-code to fulfill Java's {@code java.lang.Object} requirement.  This variant of
     * a hash function simply computes a hashcode for the first two non-null fields of this 
     * instance, and returns their sum.
     * 
     * <BR /><BR />If there aren't at least two non-null fields in this {@code RetN} instance,
     * then the hashcode for however many have been computed (READ: either 0, or 1) is returned.
     * 
     * @return a hash-code that may be used for sets and maps like {@code java.util.Hashtable} and
     * {@code java.util.HashSet}.
     */
    @Override
    public int hashCode()
    {
        // This is the value returned.
        int hashCode    = 0;
        int count       = 0;

        for (Object field : asArray())

            if (field != null)
            {
                hashCode += field.hashCode();

                // Once two Hash Code's have been computed return the 'SUM' of them.
                if (++count == 2) return hashCode;
            }

        return hashCode;
    }

    /**
     * Converts this instance of the implementing {@code RetN} to a {@code String}.
     * 
     * @return This instance-object as a {@code String}.
     */
    @Override
    @SuppressWarnings("rawtypes")
    public String toString()
    {
        // When a field is set to null, it is more difficult to retrieve the type/class of that
        // field.  The class is printed as part of the output of the 'toString' method.  It does
        // make the string look nicer, and likely will help the user.
        //
        // NOTE: If none of the fields in whatever instance 'this' is a instance of, then variable
        //       'thisClass' is never actually used.

        Class thisClass = this.getClass();

        // All RetN implementations return their fields as an array.  This array is only created
        // once (in a transietn field), defined above.
        Object[] fields = asArray();

        // whatever 'RetN' implentation this instance is, this is actually just the value of 'N'
        int n = n();

        // Tells the output-printing mechanism when/if one of the fields is an array.
        boolean[] isArray = new boolean[n];

        // These will hold the "Simple Name's" of each class/type in the previous array.
        String[] types = new String[n];

        // The fields are named 'a ... e', unless N is 6, 7, or 8.  In that case the fields are
        // named 'a1 ... h8'

        String[] FIELD_NAMES = (n < 6) ? fieldNames2To5 : fieldNames6To8;

        // This will hold the returned java.lang.String that is provided by this method call.
        StringBuilder sb = new StringBuilder();

        // Simple Loop Variables
        int i=0, maxLen=0;


        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
        // Loop merely retrieves the TYPE/CLASS of each field as a STRING (and if it is an array)
        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

        for (Object field : fields)
        {
            // If the field is non-null, retrieving the class is easy, otherwise, it isn't-possible
            // because of GENERIC-ERASURE

            if (field != null)
            {
                Class c     = field.getClass();
                types[i]    = c.getSimpleName();
                isArray[i]  = c.isArray();
            }
            else
            {
                types[i]    = "<GENERIC-ERASURE>";  // Smoke 'em if you got 'em
                isArray[i]  = false;                // DUMMY-VALUE
            }


            // These String's are pretty-printed with right-space-pad.  This computes the padding.
            if (types[i].length() > maxLen) maxLen = types[i].length();

            i++;
        }

        // Formatting: the '+2' adds two space-characters to the output.  These spaces occur
        // *AFTER* the Type/Class is printed to the output.

        maxLen += 2;

        i=0;
        for (Object field : fields)
        {
            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
            // This print's the NAME & TYPE of the Field - For Example: "Ret6.a1: String"
            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

            String line = 
                "Ret" + n + "." + FIELD_NAMES[i] + ":  " +
                StringParse.rightSpacePad(types[i], maxLen);

            sb.append(line);


            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
            // This simply prints the VALUE of the Field.  For arrays, "extra-care is provided"
            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

            if (field != null)
            {
                String s = isArray[i]
                    ? toArrayString(field, types[i])
                    : field.toString();

                if (s.indexOf('\n') != -1)
                    sb.append('\n' + StrIndent.indent(s, 4));
                else if (line.length() + s.length() > 70)
                    sb.append('\n' + StrIndent.indent(s, 4));
                else
                    sb.append(s);
            }
            else
                sb.append("null");

            sb.append('\n');
            i++;
        }

        return sb.toString();
    }

    // This is a big thing that prints array on a "Best Efforts" case.
    // This is both "The Whole Value" of the 'toString' method, and also the problem-issue

    private static String toArrayString(Object o, String classAsStr)
    {
        int pos = classAsStr.indexOf("[");
        int c   = StringParse.countCharacters(classAsStr, '[');

        classAsStr = classAsStr.substring(0, pos);

        if (c == 1)
            switch (classAsStr)
            {
                case "byte"     : return StrCSV.toCSV((byte[])      o, null, 70);
                case "short"    : return StrCSV.toCSV((short[])     o, null, 70);
                case "int"      : return StrCSV.toCSV((int[])       o, null, 70);
                case "long"     : return StrCSV.toCSV((long[])      o, null, 70);
                case "float"    : return StrCSV.toCSV((float[])     o, null, 70);
                case "double"   : return StrCSV.toCSV((double[])    o, null, 70);
                case "char"     : return StrCSV.toCSV((char[])      o, null, 70);
                case "boolean"  : return StrCSV.toCSV((boolean[])   o, null, 70);
                default         :
                    return StrCSV.toCSV((Object[]) o, false, true, 70);
            }

        if (c == 2)
            switch (classAsStr)
            {
                case "byte"     : return StrCSV.toCSV((byte[][])    o, null, null, true, 70, 4);
                case "short"    : return StrCSV.toCSV((short[][])   o, null, null, true, 70, 4);
                case "int"      : return StrCSV.toCSV((int[][])     o, null, null, true, 70, 4);
                case "long"     : return StrCSV.toCSV((long[][])    o, null, null, true, 70, 4);
                case "float"    : return StrCSV.toCSV((float[][])   o, null, null, true, 70, 4);
                case "double"   : return StrCSV.toCSV((double[][])  o, null, null, true, 70, 4);
                case "char"     : return StrCSV.toCSV((char[][])    o, null, null, true, 70, 4);
                case "boolean"  : return StrCSV.toCSV((boolean[][]) o, null, null, true, 70, 4);
                default         :
                    return StrCSV.toCSV((Object[][])  o, null, null, true, 70, 4);
            }

        return "<" + c + "> dimensional array, toString not provided";
    }
}