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