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}