001package Torello.Java.Additional;
002
003import Torello.Java.*;
004
005/**
006 * This is a parent class of the 'Multiple Return Type' classes. ({@code Ret2 ... Ret8})  This
007 * class provides some basic functionality to its descendants, namely: {@link #toString()}, 
008 * {@link #hashCode()} and {@link #equals(RetN)}.
009 */
010public abstract class RetN
011{
012    RetN() { } // Cannot be private
013
014    private static final String[] fieldNames2To5 =
015    { "a", "b", "c", "d", "e" };
016
017    private static final String[] fieldNames6To8 =
018    { "a1", "b2", "c3", "d4", "e5", "f6", "g7", "h8" };
019
020    abstract Object[] asArrayInternal();
021
022    /**
023     * All implementations of this {@code abstract} can indicate which of the {@code RetN}
024     * instances they are, by returning the number of fields they hold using this method.
025     * 
026     * @return The number of fields contained by the class that is implementing {@code 'this'}
027     * instance of {@code RetN}.  So for example, an instance of {@code Ret5} would produce
028     * {@code '5'}, if this method were called; and {@code Ret3} would produce {@code '3'}, and
029     * so on and so forth.
030     */
031    public abstract int n();
032
033    /**
034     * Optimization: This is saved, so that <I>it is only computed once by the descendant class</I>
035     * 
036     * <BR /><BR /><B>NOTE:</B> The {@code 'transient'} modifier implies that if an instance class
037     * of, say {@code Ret3}, is peristed using {@code Object}-serialization, this field will not be
038     * saved.
039     * 
040     * <BR /><BR /><B>ALSO:</B> If a class which inherits any of the {@code RetN} implementations
041     * were to modify this array, the {@link toString} method would return a {@code String} which
042     * is not consistent with the data inside the the instance.  Java does not really support "read
043     * only" arrays, but if it did, this would be a candidate for such a feature.
044     */
045    transient Object[] objArr = null;
046
047    /**
048     * Retrieve the contents of the instance-descendant class, as an array.
049     * @return Returns the instance fields {@code 'a', 'b', 'c'} ... of the class that has
050     * extended this class as an {@code Object[]} array.
051     */
052    public final Object[] asArray()
053    {
054        // All classes that inherit RetN implement the 'asArrayInternal()' method.
055        // This method simply places the 'a', 'b', 'c' etc... fields into an Object[] array.
056        if (this.objArr == null) this.objArr = asArrayInternal();
057
058        return this.objArr;        
059    }
060
061    /**
062     * This will return an instance of {@code Object} which represents the
063     * <CODE>i<SUP>th</SUP></CODE> instance-field from whatever {@code RetN} implementation this
064     * has been invoked.
065     * 
066     * <BR /><BR />If the implementing class were {@code Ret5}, and {@code '3'} were passed to
067     * parameter {@code 'i'}, then the value in {@code Ret5.c} would be returned.
068     * 
069     * <BR /><BR />If a call to {@code 'get'} is made from {@code Ret2}, and {@code '3'} were
070     * passed to {@code 'i'}, an {@code IndexOutOfBoundsException} is thrown.
071     * 
072     * <EMBED CLASS='external-html' DATA-FILE-ID=RETNEX>
073     * 
074     * @param i This specifies which field of the implementing {@code RetN} is being requested.
075     *
076     * <BR /><BR /><B STYLE='color:red;'>IMPORTANT:</B> Unlike a Java array, when a {@code '1'}
077     * is passed to this parameter, it is requesting the first field in the {@code RetN}.  
078     * Passing a value of '0' shall cause an {@code IndexOutOfBoundsException} throw.
079     * 
080     * @return This returns the <CODE>i<SUP>th</SUP></CODE> field of this class.
081     * 
082     * @throws IndexOutOfBoundsException if {@code 'i'} is zero or negative, or greater than
083     * the value of {@code 'N'} for whichever {@code RetN} class is being used.  If
084     * {@code 'this'} is an instance of {@code Ret5}, then a value of 6 or greater will cause this
085     * exception to throw.
086     * 
087     * @see #asArray()
088     * @see #get(int, Class)
089     * @see #GET(int)
090     */
091    public final Object get(int i)
092    {
093        Object[] objArr = asArray();
094
095        if (i < 1) throw new IndexOutOfBoundsException(
096            "You have passed " + i + " to parameter i.  This number must be " +
097            ((objArr.length > 1)
098                ? ("between 1 and " + objArr.length)
099                : "must be exactly 1.")
100        );
101
102        else if (i > objArr.length) throw new IndexOutOfBoundsException(
103            "You have requested the " + StringParse.ordinalIndicator(i) + " field of " +
104            "this class instance-fields, but unfortunately there " + 
105            ((objArr.length > 1)
106                ? ("are only " + objArr.length + " fields.")
107                : ("is only 1 field.")
108            )
109        );
110
111        return objArr[i-1]; // minus one, arrays start at index 0.
112    }
113
114    /**
115     * This provides a quick way to cast the field to the requested class.  This may be quicker
116     * typing than using an actual cast, because it will not generate a compiler warning about
117     * unchecked casts.  If the cast fails, it will throw the usual {@code ClassCastException}.
118     * 
119     * @param i This specifies which field of the implementing {@code RetN} is being requested.
120     *
121     * <BR /><BR /><B STYLE='color:red;'>IMPORTANT:</B> Unlike a Java array, when a {@code '1'}
122     * is passed to this parameter, it is requesting the first field in the {@code RetN}.  
123     * Passing a value of '0' shall case an {@code IndexOutOfBoundsException} exception.
124     * 
125     * @return This returns the <CODE>i<SUP>th</SUP></CODE> field of this class.
126     * 
127     * @throws IndexOutOfBoundsException if {@code 'i'} is zero or negative, or greater than
128     * the value of {@code 'N'} for whichever {@code RetN} class is being used.  If
129     * {@code 'this'} is an instance of {@code Ret5}, then a value of 6 or greater will cause this
130     * exception to throw.
131     * 
132     * @throws ClassCastException If the field cannot be cast to the class provided.
133     * 
134     * @see #get(int)
135     * @see #GET(int)
136     */
137    public final <T> T get(int i, Class<T> c) { return c.cast(get(i)); }
138
139    /**
140     * This is <I><B STYLE='color:red;'>more magic</B></I> with Java's Type-Inferencing being
141     * applied to a Generic-Cast.  Note that this method works a lot like (but not identical to)
142     * the later Java {@code 'var'} syntax.  Here, Java's Type-Inference Mechanism (inside the
143     * compile-time, not run-time, logic) will cast the result of this method to whatever type is
144     * on the left hand-side of the assignment.
145     * 
146     * <BR /><BR /><B STYLE='color:red'>NOTE:</B> The Java Compiler will output a Compile-Time
147     * Error if it appears that it cannot infer type-parameter {@code 'T'}.
148     * 
149     * <BR /><BR /><B STYLE='color:red'>ALSO:</B> This method will throw a Run-Time Exception if
150     * the cast fails (a {@code 'ClassCastException'})
151     *
152     * @param <T> This Type-Parameter is inferred by whatever assignment is taking place, or however
153     * the result of this method is being applied, programatically.  More can be read about Java's
154     * Compile-Time Type-Inferencing Mechanism on the Oracle Website.
155     * 
156     * @param i This specifies which field of the implementing {@code RetN} is being requested.
157     *
158     * <BR /><BR /><B STYLE='color:red;'>IMPORTANT:</B> Unlike a Java array, when a {@code '1'}
159     * is passed to this parameter, it is requesting the first field in the {@code RetN}.  
160     * Passing a value of '0' shall case an {@code IndexOutOfBoundsException} exception.
161     * 
162     * @return This returns the <CODE>i<SUP>th</SUP></CODE> field of this class.
163     * 
164     * @throws IndexOutOfBoundsException if {@code 'i'} is zero or negative, or greater than
165     * the value of {@code 'N'} for whichever {@code RetN} class is being used.  If
166     * {@code 'this'} is an instance of {@code Ret5}, then a value of 6 or greater will cause this
167     * exception to throw.
168     * 
169     * @throws ClassCastException If the <CODE>i<SUP>th</SUP></CODE> field of this class cannot be
170     * cast to <I>the type that is inferred for Type-Parameter {@code 'T'}.</I>
171     */
172    @SuppressWarnings("unchecked")
173    public final <T> T GET(int i) { return (T) get(i); }
174
175    /**
176     * Compares {@code 'this'} with another instance of {@code RetN}.
177     * @param other Another instance of {@code Ret8}.
178     * @return If {@code 'this'} instance of {@code Ret8} is equal to the {@code 'other'} instance
179     * of {@code Ret8}.  
180     */
181    public boolean equals(RetN other)
182    {
183        if (this == other) return true;
184
185        Object[] theseFields = asArray();
186        Object[] thoseFields = other.asArray();
187
188        // This should never happen, because the RetN that calls this only accepts a RetN
189        // of the same type.  Leave this here, anyway, you will forget that, and try to type
190        // this again.
191        if (theseFields.length != thoseFields.length) return false;
192
193        for (int i=0; i < theseFields.length; i++)
194
195            if (theseFields[i] == thoseFields[i])
196                continue;
197
198            else if (
199                        ((theseFields[i] != null) && (thoseFields[i] != null))
200                        &&
201                        (theseFields[i].getClass() == thoseFields[i].getClass())
202                        &&
203                        theseFields[i].equals(thoseFields[i])
204                    )
205                continue;
206
207            else
208                return false;
209
210        return true;
211    }
212
213    /**
214     * Builds a hash-code to fulfill Java's {@code java.lang.Object} requirement.  This variant of
215     * a hash function simply computes a hashcode for the first two non-null fields of this 
216     * instance, and returns their sum.
217     * 
218     * <BR /><BR />If there aren't at least two non-null fields in this {@code RetN} instance,
219     * then the hashcode for however many have been computed (READ: either 0, or 1) is returned.
220     * 
221     * @return a hash-code that may be used for sets and maps like {@code java.util.Hashtable} and
222     * {@code java.util.HashSet}.
223     */
224    @Override
225    public int hashCode()
226    {
227        // This is the value returned.
228        int hashCode    = 0;
229        int count       = 0;
230
231        for (Object field : asArray())
232
233            if (field != null)
234            {
235                hashCode += field.hashCode();
236
237                // Once two Hash Code's have been computed return the 'SUM' of them.
238                if (++count == 2) return hashCode;
239            }
240
241        return hashCode;
242    }
243
244    /**
245     * Converts this instance of the implementing {@code RetN} to a {@code String}.
246     * 
247     * @return This instance-object as a {@code String}.
248     */
249    @Override
250    @SuppressWarnings("rawtypes")
251    public String toString()
252    {
253        // When a field is set to null, it is more difficult to retrieve the type/class of that
254        // field.  The class is printed as part of the output of the 'toString' method.  It does
255        // make the string look nicer, and likely will help the user.
256        //
257        // NOTE: If none of the fields in whatever instance 'this' is a instance of, then variable
258        //       'thisClass' is never actually used.
259
260        Class thisClass = this.getClass();
261
262        // All RetN implementations return their fields as an array.  This array is only created
263        // once (in a transietn field), defined above.
264        Object[] fields = asArray();
265
266        // whatever 'RetN' implentation this instance is, this is actually just the value of 'N'
267        int n = n();
268
269        // Tells the output-printing mechanism when/if one of the fields is an array.
270        boolean[] isArray = new boolean[n];
271
272        // These will hold the "Simple Name's" of each class/type in the previous array.
273        String[] types = new String[n];
274
275        // The fields are named 'a ... e', unless N is 6, 7, or 8.  In that case the fields are
276        // named 'a1 ... h8'
277
278        String[] FIELD_NAMES = (n < 6) ? fieldNames2To5 : fieldNames6To8;
279
280        // This will hold the returned java.lang.String that is provided by this method call.
281        StringBuilder sb = new StringBuilder();
282
283        // Simple Loop Variables
284        int i=0, maxLen=0;
285
286
287        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
288        // Loop merely retrieves the TYPE/CLASS of each field as a STRING (and if it is an array)
289        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
290
291        for (Object field : fields)
292        {
293            // If the field is non-null, retrieving the class is easy, otherwise, it isn't-possible
294            // because of GENERIC-ERASURE
295
296            if (field != null)
297            {
298                Class c     = field.getClass();
299                types[i]    = c.getSimpleName();
300                isArray[i]  = c.isArray();
301            }
302            else
303            {
304                types[i]    = "<GENERIC-ERASURE>";  // Smoke 'em if you got 'em
305                isArray[i]  = false;                // DUMMY-VALUE
306            }
307
308
309            // These String's are pretty-printed with right-space-pad.  This computes the padding.
310            if (types[i].length() > maxLen) maxLen = types[i].length();
311
312            i++;
313        }
314
315        // Formatting: the '+2' adds two space-characters to the output.  These spaces occur
316        // *AFTER* the Type/Class is printed to the output.
317
318        maxLen += 2;
319
320        i=0;
321        for (Object field : fields)
322        {
323            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
324            // This print's the NAME & TYPE of the Field - For Example: "Ret6.a1: String"
325            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
326
327            String line = 
328                "Ret" + n + "." + FIELD_NAMES[i] + ":  " +
329                StringParse.rightSpacePad(types[i], maxLen);
330
331            sb.append(line);
332
333
334            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
335            // This simply prints the VALUE of the Field.  For arrays, "extra-care is provided"
336            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
337
338            if (field != null)
339            {
340                String s = isArray[i]
341                    ? toArrayString(field, types[i])
342                    : field.toString();
343
344                if (s.indexOf('\n') != -1)
345                    sb.append('\n' + StrIndent.indent(s, 4));
346                else if (line.length() + s.length() > 70)
347                    sb.append('\n' + StrIndent.indent(s, 4));
348                else
349                    sb.append(s);
350            }
351            else
352                sb.append("null");
353
354            sb.append('\n');
355            i++;
356        }
357
358        return sb.toString();
359    }
360
361    // This is a big thing that prints array on a "Best Efforts" case.
362    // This is both "The Whole Value" of the 'toString' method, and also the problem-issue
363
364    private static String toArrayString(Object o, String classAsStr)
365    {
366        int pos = classAsStr.indexOf("[");
367        int c   = StringParse.countCharacters(classAsStr, '[');
368
369        classAsStr = classAsStr.substring(0, pos);
370
371        if (c == 1)
372            switch (classAsStr)
373            {
374                case "byte"     : return StrCSV.toCSV((byte[])      o, null, 70);
375                case "short"    : return StrCSV.toCSV((short[])     o, null, 70);
376                case "int"      : return StrCSV.toCSV((int[])       o, null, 70);
377                case "long"     : return StrCSV.toCSV((long[])      o, null, 70);
378                case "float"    : return StrCSV.toCSV((float[])     o, null, 70);
379                case "double"   : return StrCSV.toCSV((double[])    o, null, 70);
380                case "char"     : return StrCSV.toCSV((char[])      o, null, 70);
381                case "boolean"  : return StrCSV.toCSV((boolean[])   o, null, 70);
382                default         :
383                    return StrCSV.toCSV((Object[]) o, false, true, 70);
384            }
385
386        if (c == 2)
387            switch (classAsStr)
388            {
389                case "byte"     : return StrCSV.toCSV((byte[][])    o, null, null, true, 70, 4);
390                case "short"    : return StrCSV.toCSV((short[][])   o, null, null, true, 70, 4);
391                case "int"      : return StrCSV.toCSV((int[][])     o, null, null, true, 70, 4);
392                case "long"     : return StrCSV.toCSV((long[][])    o, null, null, true, 70, 4);
393                case "float"    : return StrCSV.toCSV((float[][])   o, null, null, true, 70, 4);
394                case "double"   : return StrCSV.toCSV((double[][])  o, null, null, true, 70, 4);
395                case "char"     : return StrCSV.toCSV((char[][])    o, null, null, true, 70, 4);
396                case "boolean"  : return StrCSV.toCSV((boolean[][]) o, null, null, true, 70, 4);
397                default         :
398                    return StrCSV.toCSV((Object[][])  o, null, null, true, 70, 4);
399            }
400
401        return "<" + c + "> dimensional array, toString not provided";
402    }
403}