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
404
405
406
407
408
409
410
411
412
413
414
package Torello.Java.Additional;

import Torello.Java.*;

/**
 * This is a parent class of the 'Multiple Return Type' classes. ({@code Ret0 ... Ret8})  This
 * class provides some basic functionality to its descendants, namely: {@link #toString()}, 
 * {@link #hashCode()} and {@link #equals(Object)}.
 */
public abstract class MultiType
    implements java.io.Serializable, Cloneable
{
    /** <EMBED CLASS='external-html' DATA-FILE-ID=SVUID>  */
    protected static final long serialVersionUID = 1;

    MultiType() { }

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

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

    // This, admittedly is a little obnoxious, but it allows there to be a "parent" super-type of
    // both RetN and TupleN.  Since only one of these is Read-Write, their "get-array" method is
    // not completely identical.  The Immutable-ReadOnly one can cache the array, while the
    // Read-Write version has to recreate the array everytime.

    abstract Object[] asArrayInternal();
    abstract Object[] asArrayMain();

    /**
     * All implementations of this {@code abstract} can indicate which of the {@link RetN} or
     * {@link TupleN} 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.  So for example, an instance of {@link Tuple5} would produce {@code '5'}, if this
     * method were called; and {@link Ret3} would produce {@code '3'}, and so on and so forth.
     */
    public abstract int n();

    /**
     * Retrieve the contents of the instance-descendant class, as an array.
     * 
     * @return Returns the {@code 'this'} instance' fields {@code 'a', 'b', 'c'} etc... as an
     * {@code Object[]} array.
     */
    public Object[] asArray()
    { return asArrayMain(); }

    /**
     * This will return an instance of {@code Object} which represents the
     * <CODE>i<SUP>th</SUP></CODE> instance-field from whatever {@link RetN} or {@link TupleN}
     * implementation this method has been invoked.
     * 
     * <BR /><BR />If {@code 'this'} instance were a {@link Ret5}, and {@code '3'} were passed to
     * parameter {@code 'i'}, then the value in {@link Ret5#c} would be returned.
     * 
     * <BR /><BR />If a call to {@code 'get'} is made from {@link Tuple2}, and {@code '3'} were
     * passed to {@code 'i'}, then an {@code IndexOutOfBoundsException} would throw.
     * 
     * @param i This specifies which field of the instance 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 this instance.  
     * 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 {@link RetN} or {@link TupleN} class is being used.
     * 
     * <BR /><BR />If {@code 'this'} is an instance of {@link Tuple5}, then a value of 6 or greater
     * would force this exception to throw.
     * 
     * @see #get(int, Class)
     * @see #GET(int)
     */
    public abstract Object get(int i);

    // This does a check for the above method that is needed by both TupleN and RetN
    void CHECK_GET(int i)
    {
        if (i < 1)
        {
            // This is actually unreachable code, because Ret0's & Tuple0's "get" check for this
            // case, and throw the exception themselves.  I'm leaving this here anyways, as a
            // reminder to what is going on.  (By "unreachable" - I'm talking about the first 'if'
            // branch, not the 'else')

            if (n() == 0) throw new IndexOutOfBoundsException 
                ("You may not invoke 'get' on a Tuple or Ret of size 0");

            else throw new IndexOutOfBoundsException(
                "You have passed " + i + " to parameter i.  This number must be " +
                ((n() > 1)
                    ? ("between 1 and " + n())
                    : "must be exactly 1")
            );
        }

        else if (i > n())
        {
            // Same as above, this 'if' will never execute, because Ret0.get() checks for this
            // already itselve (as does Tuple0.get).  The following 'else' would execute if the
            // user screwed up, and called "get(i)", with an 'i' that was out of range.

            if (n() == 0) throw new IndexOutOfBoundsException 
                ("You may not invoke 'get' on a Tuple or Ret of size 0");

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

    /**
     * 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 CLASS=JDDescLabel>Type-Inferencing:</B>
     * 
     * <BR />Remember that the Java Compiler will output a Compile-Time Error if it appears that it
     * cannot infer type-parameter {@code 'T'}.
     * 
     * <BR /><BR /><B CLASS=JDDescLabel>Run-Time Casting:</B>
     * 
     * <BR />This method will throw a Run-Time {@code 'ClassCastException'}, if the cast fails.
     *
     * @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 Java Object for equality.
     * @param other Any Java Object.
     * @return If {@code 'this'} instance is equal to the {@code 'other'} instance.
     */
    public boolean equals(Object other)
    {
        if (this == other) return true;

        if (! (other instanceof MultiType)) return false;

        MultiType o = (MultiType) other;

        if (o.n() != this.n()) return false;

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

        // Keep this in mind - Three lines ago, the value of n() was already checked!
        // if (theseFields.length != thoseFields.length) throw new UnreachableError();

        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 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()
    {
        // All MultiType / Tuple implementations return their fields as an array.
        Object[] fields = asArray();

        // whatever subclass this instance is, this is actually just the number of fields
        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();
    }

    /**
     * Clones the contents of {@code 'this'} instance of.
     * @return A clone copy of {@code 'this'} object.
     */
    public abstract Object clone();

    // 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";
    }
}