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