import java.util.*; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.lang.reflect.Constructor; /* To do: * - toString -- check for its arg being a list or an array? * - have assertEquals print the act/expected with quotes etc. * - fill out all the javadoc (so that when students look at * the docs for their class, they see it) * - blueJ: figure out how to set javadoc flag to not * show all the superclass info? * - Improve `assertEquals` err-msg with reflection? * (Not having them write it, since it uses `Object`, * and perhaps a private static field for count, * and private named-constants.) */ /* A base class for ITEC120 (CS1) that provides two things: * - Before introducing objects: a set of utility methods * (mostly, static versions of String & Scanner methods) * - When first introducing object, extending Object120 will auto-provide * a constructor, equals, toString (and, hashcode). * The constructor takes in one argument per field; * the toString returns a String which looks the same as a constructor call; * the equals does a deep comparison of each fields. * @author Ian Barland * @version 2019.Oct.02 */ public class Object120 { /** Return the version number of this library * (since it might get updated through the semester). */ public static String getVersion() { return "class Object120: v1.13 (kiwi), 2019-Oct-14"; } /** Print version information to the console window. */ public static void printVersion() { System.out.println( Object120.getVersion() ); } //static { Object120.printVersion(); } private static final Random RNG = new Random(); /** Return a random integer in [0,n). * @param n the top end of the range. * @return a pseudorandom integer in [0,n). * E.g. randomInt(100) might return 0 or 1 or 99, but never 100. * @see Random#nextInt(int) */ public static int randomInt(int n) { return RNG.nextInt(n); } // Should I name this `randomNextInt` instead? public static int randomInt() { return randomInt( Integer.MAX_VALUE ); } // Should I name this `randomNextInt` instead? /** (Re)set the random-number generator to a specific seed (used by Object120#randomInt). * (Resetting the seed will cause the sequence of random numbers to repeat, which can * be helpful for testing and for reproducing bugs). * @param s The new seed for the random-number-generator. */ public static void setSeed( long s ) { RNG.setSeed(s); } /** Does a string start with a vowel? (Helpful for determining "a(n) ___".) * @param s Any string. Must be non-null. * @return whether or not `s` has a first letter which is a,e,i,o,u,y (either case). * For example:
* startsWithVowel( "ho" ) = false * startsWithVowel( "oh" ) = true * startsWithVowel( "i" ) = true * startsWithVowel( "" ) = false **/ private static boolean startsWithVowel( String s ) { return (s != null && s.length() > 0 && "aeiouy".contains(s.substring(0,1).toLowerCase())); } private static boolean hasPrefix( String s, String pre ) { return indexOf(s,pre) == 0; } private static String removePrefix( String s, String pre ) { //assert hasPrefix(s,pre); /* We remove the assertions so that the compiler doesn't generate * the field $assertionsDisabled, which goes on to clutter BlueJ's * object-inspector window with static fields the student never declared. */ return s.substring(pre.length()); } private static String removePrefixIfPresent( String s, String pre ) { return hasPrefix(s,pre) ? removePrefix(s,pre) : s; } /* ================================================================== */ /* ======== static versions of Object methods: toString, ... ======== */ /* ================================================================== */ /** Are two values equal? Works on any values (non-null). * A static version of Object.equals(Object), * for use in CS1 before we introduce objects. * @param _this the first value to compare. (Must be non-null.) * @param that the second value to compare. (Must be non-null.) * @return true iff `_this` equals `_that`. * For example:
* equals( "hello", "howdy" ) = false * equals( "hello", "hello" ) = true * equals( 23, 22+1 ) = true * equals( 'A', 65 ) = false * equals( new int[]{2,3}, new double[]{2.0,3.0} ) = true * @see Object#equals(Object) */ public static boolean equals( Object _this, Object that ) { checkForNull( _this, "equals", "Object" ); checkForNull( that, "equals", "Object" ); if (_this==that) { /* hot path */ return true; } else if (_this instanceof Number && that instanceof Number) { return equalsApprox( ((Number)_this).doubleValue(), ((Number)that).doubleValue() ); } else if (_this.getClass().isArray() && that.getClass().isArray()) { Object[] thisArr = castToArrayOfNonPrimitives(_this); Object[] thatArr = castToArrayOfNonPrimitives( that); if (thisArr.length != thatArr.length) { return false; } else { boolean noDiffSeen = true; for (int i=0; i* @see Object#toString() */ public static String toString( Object _this ) { if (!_this.getClass().isArray()) { return _this.toString(); } else { //throw new IllegalArgumentException( "Use Arrays.toString (or, Arrays.deepToString) in java.util, to convert an array to a string." ); return Arrays.deepToString((Object[])castToArrayOfNonPrimitives(_this)); } } /** Return an object's hashcode. * A static version of Object.hashCode(), for use in CS1 before we introduce objects. * @param _this The value to take the hashcode of. Cannot be null. * @return `_this`s hashCode. * For example:* toString(43) = "43" * toString(true) = "true" *
* hashCode("hello") == 99162322 * hashCode(true) == 1 ** @see Object#hashCode() */ public static int hashCode( Object o ) { return o.hashCode(); } /** Check whether one value is greater, equal, or lesser than another. * A static version of compareTo(Object), for use in CS1 before we introduce objects. * @param _this The first value to compare. * @param that The second value to compare. * @return a * positive number if `_this` is greater than `that`, * zero if `_this` is equal to `that`, * or a negative number if `_this` is less than `that`. * @see Comparable#compareTo(Object) * */ public static
* equalsIgnoreCase( "hi", "HI" ) = true * equalsIgnoreCase( "hi", "hi " ) = false * equalsIgnoreCase( "", "" ) = true ** @see String#equalsIgnoreCase(String) */ public static boolean equalsIgnoreCase( String a, String b ) { checkForNull( a, "equalsIgnoreCase", "String" ); checkForNull( b, "equalsIgnoreCase", "String" ); return a.equalsIgnoreCase(b); } /** Return the number of characters in a string. * A static version of String.length(), for use in CS1 before we introduce objects. * @param _this The string to find the length of. Cannot be null. * @return The number of characters in `_this`. * For example:
* length( "hi ho" ) = 5 * length( "z" ) = 1 * length( "" ) = 0 ** @see String#length() */ public static int length( String _this ) { checkForNull( _this, "length", "String" ); return _this.length(); } /** Return a substring of the given string, from index `from` up to but not including index `to`. * A static version of `String.substring(int,int)`, for use in CS1 before we introduce objects. * @param _this The `String` to take a substring from. Cannot be null. * @param start The index of the first character of the substring. * @param stop The index of the first character after the substring. * @return A String consisting from characters at indices [`start`,`stop`) from `_this`. * @see String#substring(int,int) */ public static String substring( String _this, int from, int to ) { checkForNull( _this, "substring", "String" ); return _this.substring(from,to); } /** Return a substring of the given string from index `from` up through the last character. * A static version of `String.substring(int)`, for use in CS1 before we introduce objects. * @param _this The `String` to take a substring from. Cannot be null. * @param start The index of the first character of the substring. * @return A String consisting from characters at indices [`start`,`length(_this)`) from `_this`. * @see String#substring(int) */ public static String substring( String _this, int from ) { checkForNull( _this, "substring", "String" ); return _this.substring(from); } /** Return where one string is contained inside another. * A static version of `String.indexOf(String)`, for use in CS1 before we introduce objects. * @param _this The `String` to look inside. Cannot be null. * @param target The String to try to find occuring inside `_this`. Cannot be null. * @return The index of `_this` where `target` starts, or -1. * That is: if int i=indexOf(s1,s2), then i==-1 || equals(s2,substring(s1,i,i+length(s2))) * (equivalently, i==-1 || s2.equals(s1.substring(i,i+s2.length()))) * * @see String#indexOf(String) */ public static int indexOf( String _this, String target ) { checkForNull( _this, "indexOf", "String" ); checkForNull( target, "indexOf", "String" ); return _this.indexOf(target); } /** Return a lower-case version of the given string. * A static version of `String.toLowerCase()`, for use in CS1 before we introduce objects. * @param _this The `String` to take a substring from. Cannot be null. * @return A lower-case version of `_this`. * @see String#toLowerCase() */ public static String toLowerCase( String _this ) { checkForNull( _this, "toLowerCase", "String" ); return _this.toLowerCase(); } /** Return an upper-case version of the given string. * A static version of `String.toUpperCase()`, for use in CS1 before we introduce objects. * @param _this The `String` to convert to upper case. Cannot be null. * @return An upper-case version of `_this`. * @see String#toUpperCase() */ public static String toUpperCase( String _this ) { checkForNull( _this, "toUpperCase", "String" ); return _this.toUpperCase(); } /** Split a string into substrings, dlimited by a regular expression. * @return An array of substrings of `src`, delimited by `delimiterPattern` (a regular expression). * @see String#split(String) */ public static String[] split( String src, String delimiterPattern ) { return src.split(delimiterPattern); } private static final Scanner scan = new Scanner(System.in); // System.in == keyboard (usually) /* WARNING -- Including these scanner methods has a pitfall, for learners: * *Later*, when learning/using Proper Java, * if a learner creates a Scanner but then leaves it off the method-call, * they'll unwittingly be calling these versions (if still extending this class). * ...I'll deem that not-particularly-harmful. */ /** Return the next token(word) of input from the keyboard. @see java.util.Scanner#next() */ public static String next() { return scan.next(); } /** Return the next int from the keyboard. @see java.util.Scanner#nextInt() */ public static int nextInt() { return scan.nextInt(); } /** Return the next double from the keyboard. @see java.util.Scanner#next() */ public static double nextDouble() { return scan.nextDouble(); } /** Return the next line of input from the keyboard. @see java.util.Scanner#nextLine() */ public static String nextLine() { return scan.nextLine(); } /** Return whether there another token is available from from the keyboard. @see java.util.Scanner#hasNext() */ public static boolean hasNext() { return scan.hasNext(); } /** Return whether the next token of input from the keyboard is an int. @see java.util.Scanner#hasNextInt() */ public static boolean hasNextInt() { return scan.hasNextInt(); } /** Return whether the next token of input from the keyboard is a double. @see java.util.Scanner#hasNextDouble() */ public static boolean hasNextDouble() { return scan.hasNextDouble(); } /** Return whether there is another line of input from the keyboard. @see java.util.Scanner#hasNextLine() */ public static boolean hasNextLine() { return scan.hasNextLine(); } /** Return the length of the array `data`. * @see also the field `.length` of array-objects. * @return `data.length`. */ public static
* toInt("2") = 2 * toInt("007") = 7 ** @throws NumberFormatException if `s` does not represent a valid double. * stringToDouble("2+3") throws NumberFormatException */ public static int toInt( String s ) { checkForNull( s, "toInt", "String" ); return Integer.parseInt(s); } /** Convert a String to a double. * @param s A string which is a valid representation of a double. * @return the double represented by `s`. * For example:
* toDouble("43.2") = 43.2 * toDouble("2") = 2.0 * toDouble("007") = 7.0 ** @throws NumberFormatException if `s` does not represent a valid double. * For example:
* toDouble("2+3") -> NumberFormatException **/ public static double toDouble( String s ) { checkForNull( s, "stringToDouble", "String" ); return Double.parseDouble(s); } /** Convert an int to a double. * (Same as casting to an int, but use the regular syntax for calling-a-method.) * @param n the int to convert. * @return n as a double. * For example:
* intToDouble(0) = 0.0 * intToDouble(-2) = -2.0 * intToDouble(2000000000) = 2e9 * intToDouble(2000000001) = 2.000000001e9 **/ public static double toDouble( int n ) { return Double.valueOf(n).intValue(); } /** Convert a double to an int (truncating towards zero). * (Same as casting to an int, but use the regular syntax for calling-a-method.) * @param x The double to convert to an int. * @return The int nearest to x, but between 0 and x. * For example:
* doubleToInt(0.0) = 0 * doubleToInt(3.1) = 3 * doubleToInt(3.9) = 3 * doubleToInt(-3.1) = -3.0 * doubleToInt(-3.9) = -3.0 ** @see Double#intValue() * @see Math#floor(double) * @see Math#ceil(double) * @see Math#round(double) */ public static int toInt( double x ) { return Double.valueOf(x).intValue(); } /** Return character corresponding to a particular the unicode value. * For "ordinary" characters, this is the ascii value ('A'=43,'B'=44','a'=97,...) * @param n The unicode(ascii) value to convert to a char. * Must be valid as a short -- in [0,65536). * @return the character corresponding to the unicode value `n`. * @see http://en.wikipedia.org/wiki/ASCII#ASCII_printable_characters */ public static char toChar( int n ) { return (char)n; } /** Return the unicode value of a character. * For "ordinary" characters, this is the ascii value ('A'=43,'B'=44','a'=97,...) * @param c The character to get the unicode value of. * @return the unicode value of `c`. * @see http://en.wikipedia.org/wiki/ASCII#ASCII_printable_characters */ public static int toInt( char c ) { return (int)c; } /** Convert a String-of-length-1 to a char. * @param s A string of length 1. * @return s as a char. * @see String#charAt(int) */ public static char toChar( String s ) { checkForNull( s, "stringToChar", "String" ); if (s.length() != 1) throw new IllegalArgumentException( "Object120.stringToChar: Can only convert strings of length 1; got: \"" + s + "\"." ); return s.charAt(0); } /** Select a single character from a string, by index (starting at 0). * A static version of `String.charAt(int)`, for use in CS1 before we introduce objects. * @param _this The `String` to select a character from. Cannot be null. * @param i The index of the character to select from s; 0 <= i < length(s). * @return The `i`th character of `_this`. * @see String#toLowerCase() */ public static char charAt( String _this, int i ) { checkForNull( _this, "charAt", "String" ); return _this.charAt(i); } /** Return whether or not one String contains another. * A static version of `String.contains(CharSequence)`, for use in CS1 before we introduce objects. * @param _this The `String` to look inside of. Cannot be null. * @param target The String to look for in `_this`. * @return whether or not `target` occurs in `_this`. * @see String#contains(CharSequence) * @see #indexOf(String,String) * @see String#indexOf(String) */ public static boolean contains( String _this, CharSequence target ) { checkForNull( _this, "charAt", "String" ); checkForNull( target, "charAt", "String (or CharSequence)" ); return _this.contains(target); } /** Return whether or not a String is the empty string "" (0 letters long). * A static version of `String.isEmpty()`, for use in CS1 before we introduce objects. * @param _this The String to compare to "". Cannot be null. * @return whether or not equals(_this,"") (equivalently, length(_this)==0). * @see String#isEmpty() */ public static boolean isEmpty( String _this ) { checkForNull( _this, "isEmpty", "String" ); return _this.isEmpty(); // Pre-Java1.6: return "".equals(_this); } /** Check that two things are `equals`; if not report it to System.err. * Intended for unit-testing (as an ersatz JUnit method). * @see printTestMsg(String) * @see printTestSummary() * @param expectedResult The desired/expected result of calling a function. * @param actualResult The actual result of calling a function. * * @see org.junit.jupiter.api.Assertions#assertEquals(Object,Object) */ static void assertEquals( Object expectedResult, Object actualResult ) { ++testCount; if (!equals(actualResult,expectedResult)) { ++failedTestCount; System.out.print("!"); System.err.println( "WHOA -- Test #" + toString(testCount) + " failed:\n" + "Desired " + toString(expectedResult) + "\n" + "but actually got " + toString(actualResult) ); } else { System.out.print("."); } testCharCount = testCharCount + 1; // We are assuming stderr != stdout (as in BlueJ). if ((testCharCount % ASSERT_RESULTS_LINE_WIDTH) == 0) { System.out.print("\n"); } else if (testCharCount % ASSERT_RESULTS_WORD_WIDTH == 0) { System.out.print(" "); } else { // nothing further to print. } } private static int testCount = 0; // For internal use by `assertEquals`: how many times `assertEquals` has been called. private static int failedTestCount = 0; // For internal use by `assertEquals`. private static int testCharCount = 0; // For internal use by `assertEquals`: how many "."/"!" chars have been printed. private static int ASSERT_RESULTS_LINE_WIDTH = 50; // # of test-result chars to print before '\n' private static int ASSERT_RESULTS_WORD_WIDTH = 5; // # of test-result chars to print before ' ' /** Print a message (about testing perhaps), * and make sure it doesn't abut any '.' characters printed by assertEquals. */ public static void printTestMsg( String msg ) { if (testCharCount != 0) System.out.println(""); // start msg on a new line System.out.println( msg ); testCharCount = 0; } /** Print a summary of how many tests ran & how many passed. * Intended to be called after testing is complete. * @see printTestMsg */ public static void printTestSummary() { int passedTestCount = (testCount - failedTestCount); double passRate = (double)passedTestCount/testCount; printTestMsg(String.format( "%2d / %2d tests passed (%3.0f%%).%s", passedTestCount, testCount, passRate*100, (failedTestCount==0) ? " Nice!" : "" )); } static final int DEFAULT_BITS_TOLERANCE = 10; // very generous?; 5 is more resonable? // 10bits slack => ~42bits precision => within ~4billion => values near 1.0 must be equal to ~12 decimal places /** @return whether `a` and `b` are approximately equal. * That is, whether they are the same up to the last `bitsTolerance` bits (default @value{DEFAULT_BITS_TOLERANCE}). * So `bitsTolerance`=0 is exactly-equal (aka `Double.equals`). * There are 52-bits of precision in a double, so `bitsTolerance`=52 always passes. * DISCLAIMER: this function is NOT exhaustively tested, and I'd actually be mildly surprised * if there were NOT weird cases where it fails. */ public static boolean equalsApprox( double a, double b, int bitsTolerance ) { return a==b // hotpath; also handles infinities and NaNs. || (Math.abs(a-b) < Math.max( Math.ulp(a), Math.ulp(b) ) * (0b1L << bitsTolerance)); } /** @return whether `a` and `b` are approximately equal. * Same as equalsApprox(a,b,@value{DEFAULT_BITS_TOLERANCE}). * @see equalsApprox(double,double,int) */ public static boolean equalsApprox( double a, double b ) { return equalsApprox(a,b,DEFAULT_BITS_TOLERANCE); } // See test-cases for the above at: https://github.com/ibarland/misc-java-ibarland/blob/master/UtilsIan.java `testEqualsApprox` /* ================================================================ */ /* ======== Constructor, equals, hashcode, toString, ... ======== */ /** Automated constructor: * Initialize each field of the subclass with the provided args, * in the order the fields are declared within the subclass' file. * * Remember that if a use writes their own constructor for a subclass, * this constructor will still get called first with 0 args, * unless the explicitly called `super` with more args. * Alas, we can't check here whether they actually * manage to initialize their fields correctly... */ public Object120( Object... args ) { String subclassName = this.getClass().getName(); // For use in diagnostic messages. List
* class Foo extends Object120 { int n; String s } * new Foo(7,"hi").toString(false) = "new Foo( 7, \"hi\" )" * new Foo(8,"ho").toString(true) = "new Foo( n=8, s=\"ho\" )" ** Bug: Depending on the Java compiler, the field names might not be * given in the same order they are declared in the class. * (This is because java.lang.reflect doesn't provide access to the declared order; * however, many implementations happen to to use that order.) */ public String toString( boolean includeFieldNames ) { StringBuilder str = new StringBuilder(); str.append("new " + this.getClass().getName() + "( "); boolean needComma = false; // Need to insert a comma before the next field? for (Field f : getDeclaredNonstaticFields(this)) { try { if (needComma) str.append( ", " ); Object theVal = f.get(this); if (includeFieldNames) str.append( f.getName().toString() ).append("="); str.append( quoteMark(theVal.getClass()) ); if (theVal.getClass().isInstance(Object120.class)) { str.append( Object120.class.cast(theVal).toString(includeFieldNames)); } else { str.append( theVal.toString()); } str.append( quoteMark(theVal.getClass()) ); needComma = true; } catch(IllegalAccessException e) { throw new RuntimeException( "Rats, I can't access the field through reflection. " + "Try overriding `" + this.getClass().getName() + "toString`.\n" + e.toString() ); } } str.append( " )" ); return str.toString(); } /* ======== Internal helper methods ======== */ /** Check whether a reference is null, and throw an error if it is. * @param ref The reference to check for being null. * @param methodName The name to put at the start of the (potential) error message. * @param type A string describing `ref`s type, for the (potential) error message. * @throws NullPointerException if `ref`==null. * For example:
* checkForNull( "lala", "myMethod", "String" ) -> no effect * checkForNull( null, "myMethod", "String" ) -> throws an exception with * a message like "Object120.myMethod: expected a String but got null." **/ private static void checkForNull( Object ref, String methodName, String type ) { if (ref == null) throw new NullPointerException( "Object120." + methodName + ": Expected a" + (startsWithVowel(type) ? "n" : "") + " " + type + " but got null. (Did you forget to initialize a variable or field?)" ); } /** Return the non-static fields in an object's *derived* class. * Intended for classes derived from Object120. * @param obj An object of the class to get the fields from. * @return the non-static fields in an object's *derived* class. */ private static List