diff --git a/src/tester/Inspector.java b/src/tester/Inspector.java index 0cb57e7..17bf0ac 100644 --- a/src/tester/Inspector.java +++ b/src/tester/Inspector.java @@ -19,724 +19,728 @@ */ public class Inspector { - /** the tolerance for comparison of relative difference of inexact numbers */ - protected static double TOLERANCE = 0.001; - - /** current indentation level for pretty-printing */ - protected static String INDENT = " "; - - /** - * a hashmap of pairs of hashcodes for two objects that are being compared: - * if the same pair is compared again, the loop of comparisons stops and - * produces true - */ - private HashMap hashmap = new HashMap(); - - /** set to true if comparison involved inexact numbers */ - protected static boolean INEXACT_COMPARED = false; - - /** set to true if comparison of inexact numbers is expected */ - protected static boolean INEXACT_ALLOWED = false; - - /** - * Constructor: For the given instance get its Class and - * fields. - */ - public Inspector() { - } - - /** - * Has there been a violation -- was there inexact comparison done when it - * was not allowed? - * - * @return true if inexact values have been involved in the last comparison - * and the inexact comparison was not allowed - */ - protected boolean inexactViolation() { - return INEXACT_COMPARED && !INEXACT_ALLOWED; - } - - /** - *

- * Compare the two given objects for extensional equality. - *

- *

- * Consider inexact numbers (types double, float, - * Double, Float) to be the same if the relative - * difference is below TOLERANCE - *

- *

- * Use == for all other primitive types and their wrapper - * classes. - *

- *

- * Use the String equals method to compare two objects of the - * type String. - *

- * Traverse over Arrays, datasets that implement - * {@link Traversal Traversal} and datasets that implement - * Iterableinterface. - *

- *

- * For datasets that implement the Map interface compare their - * sizes, and entry sets for set equality of key-value pairs. - *

- *

- * For the instances of a class that implements the - * {@link ISame ISame} interface, use its same - * method. - *

- *

- * Special tests allow the user to provide the method to invoke on the given - * object with the given argument list - *

- *

- * Additionally, user may specify that a method invocation must throw a - * given Exception with the given message. - *

- * - * @param - * the type of the objects being compared - * @param obj1 - * @param obj2 - * @return true if the two given object are the same - */ - public boolean isSame(T obj1, T obj2) { - this.hashmap.clear(); - INEXACT_COMPARED = false; - return isSamePrivate(obj1, obj2); - } - - /** - * Set up the parameters for inexact test: report failure if tolerance is - * set below 0 - * - * @param tolerance - * the desired tolerance for the inexact tests - * @return false if tolerance is negative - */ - protected boolean inexactTest(double tolerance) { - INEXACT_COMPARED = false; - INEXACT_ALLOWED = true; - // check if the provided tolerance is > 0 - fail if not. - TOLERANCE = tolerance; - return (tolerance < 0); - } - - /** - * Set up the parameters for exact test: report failure if inexact - * comparisons are made - * - * @return true - */ - protected boolean exactTest() { - INEXACT_COMPARED = false; - INEXACT_ALLOWED = false; - return true; - } - - /** - * Compare two objects that implement the Iterable interface - * - * @param obj1 - * an Iterable object - * @param obj2 - * an Iterable object - * @return true if the two given Iterableobject are the same - */ - public boolean isSameIterable(Iterable obj1, Iterable obj2) { - this.hashmap.clear(); - INEXACT_ALLOWED = false; - INEXACT_COMPARED = false; - return isSameIterablePrivate(obj1, obj2); - } - - /** - * Compare two objects that implement the Set interface - * - * @param obj1 - * a Set object - * @param obj2 - * a Set object - * @return true if the two given object represent the same set - */ - public boolean isSameSet(Set obj1, Set obj2) { - this.hashmap.clear(); - INEXACT_ALLOWED = false; - INEXACT_COMPARED = false; - return isSameSetPrivate(obj1, obj2); - } - - /** - * Compare two objects that implement the Traversal interface - * - * @param obj1 - * a Traversal object - * @param obj2 - * a Traversal object - * @return true if the two given object represent the same - * Traversal - */ - public boolean isSameTraversal(Traversal obj1, Traversal obj2) { - this.hashmap.clear(); - INEXACT_ALLOWED = false; - INEXACT_COMPARED = false; - return isSameTraversalPrivate(obj1, obj2); - } - - /* ------------ THE METHODS USED TO COMPARE TWO OBJECTS ------------- */ - /** - *

- * Invoked by isSame method after the hashmap - * that records seen objects has been cleared. - *

- * - *

- * Compare the two given objects for extensional equality. - *

- *

- * Consider inexact numbers (types double, float, - * Double, Float) to be the same if the relative - * difference is below TOLERANCE - *

- *

- * Use == for all other primitive types and their wrapper - * classes. - *

- *

- * Use the String equals method to compare two objects of the - * type String. - *

- * Compare two objects in the same class that implements the - * Set interface by their size and by matching the elements - * using the equals method. - *

- *

- * Traverse over Arrays, datasets that implement - * {@link Traversal Traversal} and datasets that implement - * Iterableinterface. - *

- *

- * For datasets that implement the Map interface compare their - * sizes, and entry sets for set equality of key-value pairs. - *

- *

- * For the instances of a class that implements the - * {@link ISame ISame} interface, use its same - * method. - *

- *

- * Special tests allow the user to provide the method to invoke on the given - * object with the given argument list - *

- *

- * Additionally, user may specify that a method invocation must throw a - * given Exception with the given message. - *

- * - * @param obj1 - * typically the actual value - * @param obj2 - * typically the expected value - * @return true if the two given object are the same - */ - @SuppressWarnings("unchecked") - private boolean isSamePrivate(T obj1, T obj2) { - - /** make sure both objects are not null */ - if (obj1 == null) - return obj2 == null; // Returns true iff obj1 & obj2 are both null, - // otherwise returns false - if (obj2 == null) - return false; - if (obj1 == obj2) - return true; // obj1 and obj2 are the same object - - /** handle the world teachpack colors */ - if (this.checkIColors(obj1, obj2)) - return true; - - Reflector r1 = new Reflector(obj1); - Reflector r2 = new Reflector(obj2); - - boolean sameClass = r1.sampleClass.equals(r2.sampleClass); - - if (sameClass) { - String r1Name = r1.sampleClass.getName(); - String r2Name = r2.sampleClass.getName(); - - - /** handle String objects separately */ - if (r1Name.equals("java.lang.String")) { - return obj1.equals(obj2); - } - - /** handle the primitive types separately */ - if (r1.sampleClass.isPrimitive()) { - if (isDouble(r1Name)) - return isSameDouble((Double) obj1, (Double) obj2); - else if (isFloat(r1Name)) - return isSameFloat((Float) obj1, (Float) obj2); - else - return (obj1.equals(obj2)); - } - - /** handle the wrapper types separately */ - if (isWrapperClass(r1Name)) { - if (isDouble(r1Name)) - return isSameDouble((Double) obj1, (Double) obj2); - else if (isFloat(r1Name)) - return isSameFloat((Float) obj1, (Float) obj2); - else - return (obj1.equals(obj2)); - } - - /** handle the Canvas class in the draw teachpack */ - if (isOurCanvas(r1Name)) - return (obj1.equals(obj2)); - - /** handle the images in the WorldImage hierarchy */ - if (isWorldImage(r1Name)) - return obj1.equals(obj2); + /** the tolerance for comparison of relative difference of inexact numbers */ + protected static double TOLERANCE = 0.001; + + /** current indentation level for pretty-printing */ + protected static String INDENT = " "; + + /** + * a hashmap of pairs of hashcodes for two objects that are being compared: + * if the same pair is compared again, the loop of comparisons stops and + * produces true + */ + private HashMap hashmap = new HashMap(); + + /** set to true if comparison involved inexact numbers */ + protected static boolean INEXACT_COMPARED = false; + + /** set to true if comparison of inexact numbers is expected */ + protected static boolean INEXACT_ALLOWED = false; + + /** + * Constructor: For the given instance get its Class and + * fields. + */ + public Inspector() { + } + + /** + * Has there been a violation -- was there inexact comparison done when it + * was not allowed? + * + * @return true if inexact values have been involved in the last comparison + * and the inexact comparison was not allowed + */ + protected boolean inexactViolation() { + return INEXACT_COMPARED && !INEXACT_ALLOWED; + } + + /** + *

+ * Compare the two given objects for extensional equality. + *

+ *

+ * Consider inexact numbers (types double, float, + * Double, Float) to be the same if the relative + * difference is below TOLERANCE + *

+ *

+ * Use == for all other primitive types and their wrapper + * classes. + *

+ *

+ * Use the String equals method to compare two objects of the + * type String. + *

+ * Traverse over Arrays, datasets that implement + * {@link Traversal Traversal} and datasets that implement + * Iterableinterface. + *

+ *

+ * For datasets that implement the Map interface compare their + * sizes, and entry sets for set equality of key-value pairs. + *

+ *

+ * For the instances of a class that implements the + * {@link ISame ISame} interface, use its same + * method. + *

+ *

+ * Special tests allow the user to provide the method to invoke on the given + * object with the given argument list + *

+ *

+ * Additionally, user may specify that a method invocation must throw a + * given Exception with the given message. + *

+ * + * @param + * the type of the objects being compared + * @param obj1 + * @param obj2 + * @return true if the two given object are the same + */ + public boolean isSame(T obj1, T obj2) { + this.hashmap.clear(); + INEXACT_COMPARED = false; + return isSamePrivate(obj1, obj2); + } + + /** + * Set up the parameters for inexact test: report failure if tolerance is + * set below 0 + * + * @param tolerance + * the desired tolerance for the inexact tests + * @return false if tolerance is negative + */ + protected boolean inexactTest(double tolerance) { + INEXACT_COMPARED = false; + INEXACT_ALLOWED = true; + // check if the provided tolerance is > 0 - fail if not. + TOLERANCE = tolerance; + return (tolerance < 0); + } + + /** + * Set up the parameters for exact test: report failure if inexact + * comparisons are made + * + * @return true + */ + protected boolean exactTest() { + INEXACT_COMPARED = false; + INEXACT_ALLOWED = false; + return true; + } + + /** + * Compare two objects that implement the Iterable interface + * + * @param obj1 + * an Iterable object + * @param obj2 + * an Iterable object + * @return true if the two given Iterableobject are the same + */ + public boolean isSameIterable(Iterable obj1, Iterable obj2) { + this.hashmap.clear(); + INEXACT_ALLOWED = false; + INEXACT_COMPARED = false; + return isSameIterablePrivate(obj1, obj2); + } + + /** + * Compare two objects that implement the Set interface + * + * @param obj1 + * a Set object + * @param obj2 + * a Set object + * @return true if the two given object represent the same set + */ + public boolean isSameSet(Set obj1, Set obj2) { + this.hashmap.clear(); + INEXACT_ALLOWED = false; + INEXACT_COMPARED = false; + return isSameSetPrivate(obj1, obj2); + } + + /** + * Compare two objects that implement the Traversal interface + * + * @param obj1 + * a Traversal object + * @param obj2 + * a Traversal object + * @return true if the two given object represent the same + * Traversal + */ + public boolean isSameTraversal(Traversal obj1, Traversal obj2) { + this.hashmap.clear(); + INEXACT_ALLOWED = false; + INEXACT_COMPARED = false; + return isSameTraversalPrivate(obj1, obj2); + } + + /* ------------ THE METHODS USED TO COMPARE TWO OBJECTS ------------- */ + /** + *

+ * Invoked by isSame method after the hashmap + * that records seen objects has been cleared. + *

+ * + *

+ * Compare the two given objects for extensional equality. + *

+ *

+ * Consider inexact numbers (types double, float, + * Double, Float) to be the same if the relative + * difference is below TOLERANCE + *

+ *

+ * Use == for all other primitive types and their wrapper + * classes. + *

+ *

+ * Use the String equals method to compare two objects of the + * type String. + *

+ * Compare two objects in the same class that implements the + * Set interface by their size and by matching the elements + * using the equals method. + *

+ *

+ * Traverse over Arrays, datasets that implement + * {@link Traversal Traversal} and datasets that implement + * Iterableinterface. + *

+ *

+ * For datasets that implement the Map interface compare their + * sizes, and entry sets for set equality of key-value pairs. + *

+ *

+ * For the instances of a class that implements the + * {@link ISame ISame} interface, use its same + * method. + *

+ *

+ * Special tests allow the user to provide the method to invoke on the given + * object with the given argument list + *

+ *

+ * Additionally, user may specify that a method invocation must throw a + * given Exception with the given message. + *

+ * + * @param obj1 + * typically the actual value + * @param obj2 + * typically the expected value + * @return true if the two given object are the same + */ + @SuppressWarnings("unchecked") + private boolean isSamePrivate(T obj1, T obj2) { + + /** make sure both objects are not null */ + if (obj1 == null) + return obj2 == null; // Returns true iff obj1 & obj2 are both null, + // otherwise returns false + if (obj2 == null) + return false; + if (obj1 == obj2) + return true; // obj1 and obj2 are the same object + + /** handle the world teachpack colors */ + if (this.checkIColors(obj1, obj2)) + return true; + + Reflector r1 = new Reflector(obj1); + Reflector r2 = new Reflector(obj2); + + boolean sameClass = r1.sampleClass.equals(r2.sampleClass); + + // if (sameClass) { + String r1Name = r1.sampleClass.getName(); + String r2Name = r2.sampleClass.getName(); + + + /** handle String objects separately */ + if (r1Name.equals("java.lang.String")) { + return obj1.equals(obj2); + } + + /** handle the primitive types separately */ + if (r1.sampleClass.isPrimitive()) { + if (! sameClass) return false; + if (isDouble(r1Name)) + return isSameDouble((Double) obj1, (Double) obj2); + else if (isFloat(r1Name)) + return isSameFloat((Float) obj1, (Float) obj2); + else + return (obj1.equals(obj2)); + } + + /** handle the wrapper types separately */ + if (isWrapperClass(r1Name)) { + if (! sameClass) return false; + if (isDouble(r1Name)) + return isSameDouble((Double) obj1, (Double) obj2); + else if (isFloat(r1Name)) + return isSameFloat((Float) obj1, (Float) obj2); + else + return (obj1.equals(obj2)); + } + + /** handle the Canvas class in the draw teachpack + * No, on second thought let's not do that. * / + if (isOurCanvas(r1Name)) + return (obj1.equals(obj2)); + + /** handle the images in the WorldImage hierarchy * / + if (isWorldImage(r1Name)) + return obj1.equals(obj2); - /** handle the images in the tunes package */ - if (isTunesPackage(r1Name)) - return obj1.equals(obj2); - - - /** - * Record the information about the object compared and check - * whether the current pair has already been tested for equality, or - * has been viewed before. - */ - Integer i1 = System.identityHashCode(obj1); - Integer i2 = System.identityHashCode(obj2); - /* - * <> - * [Author: Sachin Venugopalan; Date: 03/03/2012] - * System.identityHashCode(obj) is used instead of obj.hashCode() for 2 - * reasons: - * 1. The default (Java-platform-implemented) hashcode for an - * Object, although not guaranteed to be unique, may for all practical - * purposes be treated as such. However, in the event that hashcode() of - * user-defined objects is overridden such that objects that are not the - * "same" ["same()"-ness in our Tester framework is analogous to Java's - * definition of "equal()"] return the same hashcode [since there exists - * no restriction in language/convention that would disallow this; e.g. - * all objects returning hashcode 43, say, is permitted, albeit regarded - * a bad hashing function], we do not wish to call upon the overridden - * version of hashcode(); instead we'd like to use the value retunred by - * the original implementation. System.identityHashCode(obj) achieves - * this. - * 2. Asking for the hashcode of self-referential definitions - * using Java's in-built container objects like java.unil.Arraylist - * (which extends AbstractList that overrides hashcode()) causes Java to - * spawn a circular call structure that loops infinitely; it then - * becomes the responsibility of the programmer to make sure that such a - * structure is culled!!) Calling System.identityHashCode(obj) instead - * avoids this complexity. - */ + /** handle the images in the tunes package * / + if (isTunesPackage(r1Name)) + return obj1.equals(obj2); + */ + + /** + * Record the information about the object compared and check + * whether the current pair has already been tested for equality, or + * has been viewed before. + */ + Integer i1 = System.identityHashCode(obj1); + Integer i2 = System.identityHashCode(obj2); + /* + * <> + * [Author: Sachin Venugopalan; Date: 03/03/2012] + * System.identityHashCode(obj) is used instead of obj.hashCode() for 2 + * reasons: + * 1. The default (Java-platform-implemented) hashcode for an + * Object, although not guaranteed to be unique, may for all practical + * purposes be treated as such. However, in the event that hashcode() of + * user-defined objects is overridden such that objects that are not the + * "same" ["same()"-ness in our Tester framework is analogous to Java's + * definition of "equal()"] return the same hashcode [since there exists + * no restriction in language/convention that would disallow this; e.g. + * all objects returning hashcode 43, say, is permitted, albeit regarded + * a bad hashing function], we do not wish to call upon the overridden + * version of hashcode(); instead we'd like to use the value retunred by + * the original implementation. System.identityHashCode(obj) achieves + * this. + * 2. Asking for the hashcode of self-referential definitions + * using Java's in-built container objects like java.unil.Arraylist + * (which extends AbstractList that overrides hashcode()) causes Java to + * spawn a circular call structure that loops infinitely; it then + * becomes the responsibility of the programmer to make sure that such a + * structure is culled!!) Calling System.identityHashCode(obj) instead + * avoids this complexity. + */ // this code can be used for debugging: -// if(showComparisonDetail){ -// System.out.println("Comparing:-"); -// System.out.println("\tclass 1:" + r1.sampleClass.getName() -// + "; hashcode:" + i1); -// System.out.println("\tclass 2:" + r2.sampleClass.getName() -// + "; hashcode:" + i2); -// System.out.println("\tpair-hashmap key:" + i1 * i1 + i2 * i2); -// } - - Integer i2match = this.hashmap.get(i1 * i1 + i2 * i2); - /* - * <> - * [Author: Sachin Venugopalan; Date: 03/03/2012] - * In order to handle circularity, we need to keep track of pairs of - * objects that have already been compared for equality (or for - * which an equality check has already been initiated, and is - * pending completion owing to a circular definition). In order to - * look up pairs of already-seen objects efficiently, we need a - * hash-based lookup directory such as a Hashset. The "key" (or - * identifier) that is stored in the lookup directory should - * identify the pair of objects; also it ought to be "symmetrical" - * i.e. the pair of objects is not to be treated as an ordered pair - * -- this stems from the fact that equality (rather, "sameness") - * between two objects itself is a symmetrical relationship. - * In other words, having seen the pair (obj1, obj2) is identical to - * having seen the pair (obj2, obj1) We have chosen i1*i1 + i2*i2 to - * be this symmetrical identifier of the pair of objects, where i1 & - * i2 are their (original) hashcodes. This choice is perhaps better - * than i1*i2, say, because such an alternate choice potentially - * leads to more collisions. However, there is no absolute guarantee - * of uniqueness of i1*i1 + i2*i2 (simply because there is no - * absolute guarantee of uniqueness of i1 and i2; we simply assume - * their uniqueness for all practical purposes, given Java's current - * implementation of hashcodes that relates to the unique memory - * locations at which objects reside) Hence, we perform a second - * "validation" step to confirm the identity of the pair of objects - * by seeing if they map to a chosen value i1+i2 (note that this is - * again symmetric) The second validation step may not be very - * meaningful, and for the time being, it exists more for legacy reasons. - */ - if ((i2match != null) && (i2match.equals(i1 + i2))) { -// if(showComparisonDetail) -// System.out.println("\tcheck done already for this pair:" + i1 -// * i1 + i2 * i2 + " => " + i2match.toString()); - return true; - } - this.hashmap.put(i1 * i1 + i2 * i2, i1 + i2); - - /** handle Array objects */ - if (obj1.getClass().isArray() && obj2.getClass().isArray() - && obj1.getClass() == obj2.getClass()) { - int length = Array.getLength(obj1); - if (Array.getLength(obj2) == length) { - for (int i = 0; i < length; i++) { - if (!isSame(Array.get(obj1, i), Array.get(obj2, i))) - return false; - } - return true; - } else - return false; - } - - /** handle ISame objects by delegating to the user-defined method */ - if ((obj1 instanceof ISame) && (obj2 instanceof ISame)) - return ((ISame) obj1).same((ISame) obj2); - - /** - * handle the Set objects in the Java Collection library by - * comparing their size, and data items -- here we use the equals - * method when comparing the set elements - */ - if ((obj1 instanceof Set) && (obj2 instanceof Set) - && obj1.getClass().getName().startsWith("java.util")) - return isSameSet((Set) obj1, (Set) obj2); - - /** - * handle Iterable objects in the Java Collection library by - * comparing their size, then comparing the elements pairwise - */ - if ((obj1 instanceof Iterable) && (obj2 instanceof Iterable) - && obj1.getClass().getName().startsWith("java.util")) - return isSameIterablePrivate((Iterable) obj1, - (Iterable) obj2); - - /** - * handle the Map objects in the Java Collection library by - * comparing their size, the key-set, and key-value mappings - */ - if ((obj1 instanceof Map) && (obj2 instanceof Map) - && obj1.getClass().getName().startsWith("java.util")) - return isSameMap((Map) obj1, (Map) obj2); - - /** now handle the general case */ - boolean sameValues = true; - int i = 0; - try { - for (; i < Array.getLength(r1.sampleDeclaredFields); i++) { - sameValues = sameValues - && isSamePrivate( - r1.sampleDeclaredFields[i].get(obj1), - r2.sampleDeclaredFields[i].get(obj2)); - } - } catch (IllegalAccessException e) { - System.out.println("same comparing " - + r1.sampleDeclaredFields[i].getType().getName() - + " and " - + r2.sampleDeclaredFields[i].getType().getName() - + "cannot access the field " + i + " message: " - + e.getMessage()); - System.out.println("class 1: " + r1.sampleClass.getName()); - System.out.println("class 2: " + r2.sampleClass.getName()); - } - - return sameValues; - } else - return false; - } - - /** - * Determine whether the relative difference between two double numbers is - * below the expected TOLERANCE Measure absolute tolerance, if - * one of the numbers is exact zero. - * - * @param d1 - * the first inexact number - * @param d2 - * the second inexact number - * @return true is the two numbers are nearly the same - */ - protected boolean isSameDouble(double d1, double d2) { - if (d1 - d2 == 0.0) - return true; - else { - INEXACT_COMPARED = true; - if (d1 == 0.0) - return Math.abs(d2) < TOLERANCE; - if (d2 == 0.0) - return Math.abs(d1) < TOLERANCE; - - else - // d1, d2 are non-zero - // return (Math.abs(d1 - d2) / (Math.max (Math.abs(d1), - // Math.abs(d2)))) - return Math.abs(d1 - d2) / (Math.abs((d1 + d2) / 2)) < TOLERANCE; - } - } - - /** - * Determine whether the relative difference between two float numbers is - * below the expected TOLERANCE Measure absolute tolerance, if - * one of the numbers is exact zero. - * - * @param f1 - * the first inexact number - * @param f2 - * the second inexact number - * @return true is the two numbers are nearly the same - */ - protected boolean isSameFloat(float f1, float f2) { - if (f1 - f2 == 0.0) - return true; - else { - INEXACT_COMPARED = true; - Double d1 = ((Float) f1).doubleValue(); - Double d2 = ((Float) f2).doubleValue(); - - if (f1 == 0.0) - return Math.abs(d2) < TOLERANCE; - if (f2 == 0.0) - return Math.abs(d1) < TOLERANCE; - - // f1, f2 are non-zero - return (Math.abs(d1 - d2) / (Math.max(Math.abs(d1), Math.abs(d2)))) < TOLERANCE; - } - } - - /** - * Determine whether two Iterable objects generate the same - * data elements in the same order. - * - * @param obj1 - * the first Iterable dataset - * @param obj2 - * the second Iterable dataset - * @return true is the two datasets are extensionally equal - */ - private boolean isSameIterablePrivate(Iterable obj1, Iterable obj2) { - Iterator it1 = obj1.iterator(); - Iterator it2 = obj2.iterator(); - - return this.isSameData(it1, it2); - } - - /** - * Determine whether two Traversal objects generate the same - * data elements in the same order. - * - * @param obj1 - * the first Traversal dataset - * @param obj2 - * the second Traversal dataset - * @return true is the two datasets are extensionally equal - */ - private boolean isSameTraversalPrivate(Traversal obj1, - Traversal obj2) { - Traversal it1 = obj1; - Traversal it2 = obj2; - - return this.isSameTraversalData(it1, it2); - } - - /** - * Determine whether two Iterators generate the same data in - * the same order. - * - * @param it1 - * the first Iterator - * @param it2 - * the second Iterator - * @return true is both datasets contained the same data elements (in the - * same order) - */ - protected boolean isSameData(Iterator it1, Iterator it2) { - /** if the first dataset is empty, the second one has to be too */ - if (!it1.hasNext()) { - return !it2.hasNext(); - } - /** the first dataset is nonempty - make sure the second one is too... */ - else if (!it2.hasNext()) - return false; - /** now both have data - compare the next pair of data and recur */ - else { - return this.isSamePrivate(it1.next(), it2.next()) - && this.isSameData(it1, it2); - } - } - - /** - * Determine whether two Traversals generate the same data in - * the same order. - * - * @param tr1 - * the first Traversal - * @param tr2 - * the second Traversal - * @return true is both datasets contained the same data elements - * (in the same order) - */ - protected boolean isSameTraversalData(Traversal tr1, Traversal tr2) { - /** if the first dataset is empty, the second one has to be too */ - if (tr1.isEmpty()) { - return tr2.isEmpty(); - } - /** the first dataset is nonempty - make sure the second one is too... */ - else if (tr2.isEmpty()) - return false; - /** now both have data - compare the next pair of data and recur */ - else { - return this.isSamePrivate(tr1.getFirst(), tr2.getFirst()) - && this.isSameTraversalData(tr1.getRest(), tr2.getRest()); - } - } - - /** - * Determine whether two Map objects have the same key-value - * sets of data - * - * @param obj1 - * the first Map dataset - * @param obj2 - * the second Map dataset - * @return true if the two maps are extensionally equal - */ - protected boolean isSameMap(Map obj1, Map obj2) { - // make sure both maps have the same size keyset - if (obj1.size() != obj2.size()) - return false; - - // the key sets for the two maps have the same size - pick one - Set set1 = obj1.keySet(); - - for (Object key : set1) { - // make sure each key is in both key sets - if (!obj2.containsKey(key)) - return false; - - // now compare the corresponding values - if (!this.isSamePrivate(obj1.get(key), obj2.get(key))) - return false; - } - - // all tests passed - return true; - } - - /** - * Determine whether two Set objects contain the same data - * - * @param obj1 - * the first Set dataset - * @param obj2 - * the second Set dataset - * @return true if the two sets contain the same elements, compared by - * using the Java equals method. - */ - protected boolean isSameSetPrivate(Set obj1, Set obj2) { - // make sure both sets have the same size number of elements - if (obj1.size() != obj2.size()) - return false; - - // return obj1.containsAll(obj2); - - // the sets have the same size - pick one - for (T item1 : obj1) { - boolean match = false; // did we find a match for this item? - - // make sure each item of the first set matches an item in the - // second set - for (T item2 : obj2) { - // now compare the corresponding values using 'equals' as - // required - // for the Set interface - if (item1.equals(item2)) - match = true; // match found - } - - // no match found for this item - if (match == false) { - System.out.println("Mismatch for " + obj1 + " and " + obj2); - return false; - } - } - - // all tests passed - return true; - - } - - /** ------- THE METHODS USED TO DETERMINE THE TYPES OF OBJECTS ---------- */ - /** - * Does the class with the given name represent inexact numbers? - * - * @param name - * the name of the class in question - * @return true if this is double, - * float, or Double or Float - */ - protected boolean isDouble(String name) { - return name.equals("double") || name.equals("java.lang.Double"); - } - - /** - * Does the class with the given name represent inexact numbers? - * - * @param name - * the name of the class in question - * @return true if this is double, float, or Double or Float - */ - protected boolean isFloat(String name) { - return name.equals("float") || name.equals("java.lang.Float"); - } - - /** - * Does the class with the given name represent a wrapper class for a - * primitive class? - * - * @param name - * the name of the class in question - * @return true if this is a class that represents a wrapper class - */ - protected static boolean isWrapperClass(String name) { - return name.equals("java.lang.Integer") - || name.equals("java.lang.Long") - || name.equals("java.lang.Short") - || name.equals("java.math.BigInteger") - || name.equals("java.math.BigDecimal") - || name.equals("java.lang.Float") - || name.equals("java.lang.Double") - || name.equals("java.lang.Byte") - || name.equals("java.lang.Boolean") - || name.equals("java.lang.Character"); - } - - /** - * Is this a name of one of our Canvas classes? - * - * @param name the name of the class to consider - * @return true if the given name is one of our - * Canvas classes - */ - protected static boolean isOurCanvas(String name) { - return name.equals("draw.Canvas") || name.equals("idraw.Canvas") - || name.equals("adraw.Canvas") || name.equals("funworld.Canvas") - || name.equals("impworld.Canvas") || name.equals("appletworld.Canvas") - || name.equals("javalib.worldcanvas.WorldCanvas") +// if(showComparisonDetail){ +// System.out.println("Comparing:-"); +// System.out.println("\tclass 1:" + r1.sampleClass.getName() +// + "; hashcode:" + i1); +// System.out.println("\tclass 2:" + r2.sampleClass.getName() +// + "; hashcode:" + i2); +// System.out.println("\tpair-hashmap key:" + i1 * i1 + i2 * i2); +// } + + Integer i2match = this.hashmap.get(i1 * i1 + i2 * i2); + /* + * <> + * [Author: Sachin Venugopalan; Date: 03/03/2012] + * In order to handle circularity, we need to keep track of pairs of + * objects that have already been compared for equality (or for + * which an equality check has already been initiated, and is + * pending completion owing to a circular definition). In order to + * look up pairs of already-seen objects efficiently, we need a + * hash-based lookup directory such as a Hashset. The "key" (or + * identifier) that is stored in the lookup directory should + * identify the pair of objects; also it ought to be "symmetrical" + * i.e. the pair of objects is not to be treated as an ordered pair + * -- this stems from the fact that equality (rather, "sameness") + * between two objects itself is a symmetrical relationship. + * In other words, having seen the pair (obj1, obj2) is identical to + * having seen the pair (obj2, obj1) We have chosen i1*i1 + i2*i2 to + * be this symmetrical identifier of the pair of objects, where i1 & + * i2 are their (original) hashcodes. This choice is perhaps better + * than i1*i2, say, because such an alternate choice potentially + * leads to more collisions. However, there is no absolute guarantee + * of uniqueness of i1*i1 + i2*i2 (simply because there is no + * absolute guarantee of uniqueness of i1 and i2; we simply assume + * their uniqueness for all practical purposes, given Java's current + * implementation of hashcodes that relates to the unique memory + * locations at which objects reside) Hence, we perform a second + * "validation" step to confirm the identity of the pair of objects + * by seeing if they map to a chosen value i1+i2 (note that this is + * again symmetric) The second validation step may not be very + * meaningful, and for the time being, it exists more for legacy reasons. + */ + if ((i2match != null) && (i2match.equals(i1 + i2))) { +// if(showComparisonDetail) +// System.out.println("\tcheck done already for this pair:" + i1 +// * i1 + i2 * i2 + " => " + i2match.toString()); + return true; + } + this.hashmap.put(i1 * i1 + i2 * i2, i1 + i2); + + /** handle Array objects */ + if (obj1.getClass().isArray() && obj2.getClass().isArray() + && obj1.getClass() == obj2.getClass()) { + int length = Array.getLength(obj1); + if (Array.getLength(obj2) == length) { + for (int i = 0; i < length; i++) { + if (!isSame(Array.get(obj1, i), Array.get(obj2, i))) + return false; + } + return true; + } else // not same length + return false; + } + + /** handle ISame objects by delegating to the user-defined method */ + if ((obj1 instanceof ISame) && (obj2 instanceof ISame)) + return ((ISame) obj1).same((ISame) obj2); + + /** + * handle the Set objects in the Java Collection library by + * comparing their size, and data items -- here we use the equals + * method when comparing the set elements + */ + if ((obj1 instanceof Set) && (obj2 instanceof Set) + && obj1.getClass().getName().startsWith("java.util")) + return isSameSet((Set) obj1, (Set) obj2); + + /** + * handle Iterable objects in the Java Collection library by + * comparing their size, then comparing the elements pairwise + */ + if ((obj1 instanceof Iterable) && (obj2 instanceof Iterable) + && obj1.getClass().getName().startsWith("java.util")) + return isSameIterablePrivate((Iterable) obj1, + (Iterable) obj2); + + /** + * handle the Map objects in the Java Collection library by + * comparing their size, the key-set, and key-value mappings + */ + if ((obj1 instanceof Map) && (obj2 instanceof Map) + && obj1.getClass().getName().startsWith("java.util")) + return isSameMap((Map) obj1, (Map) obj2); + + /** now handle the general case */ + if (sameClass) { + boolean sameValues = true; + int i = 0; + try { + for (; i < Array.getLength(r1.sampleDeclaredFields); i++) { + sameValues = sameValues + && isSamePrivate( + r1.sampleDeclaredFields[i].get(obj1), + r2.sampleDeclaredFields[i].get(obj2)); + } + } catch (IllegalAccessException e) { + System.out.println("same comparing " + + r1.sampleDeclaredFields[i].getType().getName() + + " and " + + r2.sampleDeclaredFields[i].getType().getName() + + "cannot access the field " + i + " message: " + + e.getMessage()); + System.out.println("class 1: " + r1.sampleClass.getName()); + System.out.println("class 2: " + r2.sampleClass.getName()); + } + + return sameValues; + } else // not same class + return false; + } + + /** + * Determine whether the relative difference between two double numbers is + * below the expected TOLERANCE Measure absolute tolerance, if + * one of the numbers is exact zero. + * + * @param d1 + * the first inexact number + * @param d2 + * the second inexact number + * @return true is the two numbers are nearly the same + */ + protected boolean isSameDouble(double d1, double d2) { + if (d1 - d2 == 0.0) + return true; + else { + INEXACT_COMPARED = true; + if (d1 == 0.0) + return Math.abs(d2) < TOLERANCE; + if (d2 == 0.0) + return Math.abs(d1) < TOLERANCE; + + else + // d1, d2 are non-zero + // return (Math.abs(d1 - d2) / (Math.max (Math.abs(d1), + // Math.abs(d2)))) + return Math.abs(d1 - d2) / (Math.abs((d1 + d2) / 2)) < TOLERANCE; + } + } + + /** + * Determine whether the relative difference between two float numbers is + * below the expected TOLERANCE Measure absolute tolerance, if + * one of the numbers is exact zero. + * + * @param f1 + * the first inexact number + * @param f2 + * the second inexact number + * @return true is the two numbers are nearly the same + */ + protected boolean isSameFloat(float f1, float f2) { + if (f1 - f2 == 0.0) + return true; + else { + INEXACT_COMPARED = true; + Double d1 = ((Float) f1).doubleValue(); + Double d2 = ((Float) f2).doubleValue(); + + if (f1 == 0.0) + return Math.abs(d2) < TOLERANCE; + if (f2 == 0.0) + return Math.abs(d1) < TOLERANCE; + + // f1, f2 are non-zero + return (Math.abs(d1 - d2) / (Math.max(Math.abs(d1), Math.abs(d2)))) < TOLERANCE; + } + } + + /** + * Determine whether two Iterable objects generate the same + * data elements in the same order. + * + * @param obj1 + * the first Iterable dataset + * @param obj2 + * the second Iterable dataset + * @return true is the two datasets are extensionally equal + */ + private boolean isSameIterablePrivate(Iterable obj1, Iterable obj2) { + Iterator it1 = obj1.iterator(); + Iterator it2 = obj2.iterator(); + + return this.isSameData(it1, it2); + } + + /** + * Determine whether two Traversal objects generate the same + * data elements in the same order. + * + * @param obj1 + * the first Traversal dataset + * @param obj2 + * the second Traversal dataset + * @return true is the two datasets are extensionally equal + */ + private boolean isSameTraversalPrivate(Traversal obj1, + Traversal obj2) { + Traversal it1 = obj1; + Traversal it2 = obj2; + + return this.isSameTraversalData(it1, it2); + } + + /** + * Determine whether two Iterators generate the same data in + * the same order. + * + * @param it1 + * the first Iterator + * @param it2 + * the second Iterator + * @return true is both datasets contained the same data elements (in the + * same order) + */ + protected boolean isSameData(Iterator it1, Iterator it2) { + /** if the first dataset is empty, the second one has to be too */ + if (!it1.hasNext()) { + return !it2.hasNext(); + } + /** the first dataset is nonempty - make sure the second one is too... */ + else if (!it2.hasNext()) + return false; + /** now both have data - compare the next pair of data and recur */ + else { + return this.isSamePrivate(it1.next(), it2.next()) + && this.isSameData(it1, it2); + } + } + + /** + * Determine whether two Traversals generate the same data in + * the same order. + * + * @param tr1 + * the first Traversal + * @param tr2 + * the second Traversal + * @return true is both datasets contained the same data elements + * (in the same order) + */ + protected boolean isSameTraversalData(Traversal tr1, Traversal tr2) { + /** if the first dataset is empty, the second one has to be too */ + if (tr1.isEmpty()) { + return tr2.isEmpty(); + } + /** the first dataset is nonempty - make sure the second one is too... */ + else if (tr2.isEmpty()) + return false; + /** now both have data - compare the next pair of data and recur */ + else { + return this.isSamePrivate(tr1.getFirst(), tr2.getFirst()) + && this.isSameTraversalData(tr1.getRest(), tr2.getRest()); + } + } + + /** + * Determine whether two Map objects have the same key-value + * sets of data + * + * @param obj1 + * the first Map dataset + * @param obj2 + * the second Map dataset + * @return true if the two maps are extensionally equal + */ + protected boolean isSameMap(Map obj1, Map obj2) { + // make sure both maps have the same size keyset + if (obj1.size() != obj2.size()) + return false; + + // the key sets for the two maps have the same size - pick one + Set set1 = obj1.keySet(); + + for (Object key : set1) { + // make sure each key is in both key sets + if (!obj2.containsKey(key)) + return false; + + // now compare the corresponding values + if (!this.isSamePrivate(obj1.get(key), obj2.get(key))) + return false; + } + + // all tests passed + return true; + } + + /** + * Determine whether two Set objects contain the same data + * + * @param obj1 + * the first Set dataset + * @param obj2 + * the second Set dataset + * @return true if the two sets contain the same elements, compared by + * using the Java equals method. + */ + protected boolean isSameSetPrivate(Set obj1, Set obj2) { + // make sure both sets have the same size number of elements + if (obj1.size() != obj2.size()) + return false; + + // return obj1.containsAll(obj2); + + // the sets have the same size - pick one + for (T item1 : obj1) { + boolean match = false; // did we find a match for this item? + + // make sure each item of the first set matches an item in the + // second set + for (T item2 : obj2) { + // now compare the corresponding values using 'equals' as + // required + // for the Set interface + if (item1.equals(item2)) + match = true; // match found + } + + // no match found for this item + if (match == false) { + System.out.println("Mismatch for " + obj1 + " and " + obj2); + return false; + } + } + + // all tests passed + return true; + + } + + /** ------- THE METHODS USED TO DETERMINE THE TYPES OF OBJECTS ---------- */ + /** + * Does the class with the given name represent inexact numbers? + * + * @param name + * the name of the class in question + * @return true if this is double, + * float, or Double or Float + */ + protected boolean isDouble(String name) { + return name.equals("double") || name.equals("java.lang.Double"); + } + + /** + * Does the class with the given name represent inexact numbers? + * + * @param name + * the name of the class in question + * @return true if this is double, float, or Double or Float + */ + protected boolean isFloat(String name) { + return name.equals("float") || name.equals("java.lang.Float"); + } + + /** + * Does the class with the given name represent a wrapper class for a + * primitive class? + * + * @param name + * the name of the class in question + * @return true if this is a class that represents a wrapper class + */ + protected static boolean isWrapperClass(String name) { + return name.equals("java.lang.Integer") + || name.equals("java.lang.Long") + || name.equals("java.lang.Short") + || name.equals("java.math.BigInteger") + || name.equals("java.math.BigDecimal") + || name.equals("java.lang.Float") + || name.equals("java.lang.Double") + || name.equals("java.lang.Byte") + || name.equals("java.lang.Boolean") + || name.equals("java.lang.Character"); + } + + /** + * Is this a name of one of our Canvas classes? + * + * @param name the name of the class to consider + * @return true if the given name is one of our + * Canvas classes + */ + protected static boolean isOurCanvas(String name) { + return name.equals("draw.Canvas") || name.equals("idraw.Canvas") + || name.equals("adraw.Canvas") || name.equals("funworld.Canvas") + || name.equals("impworld.Canvas") || name.equals("appletworld.Canvas") + || name.equals("javalib.worldcanvas.WorldCanvas") || name.equals("javalib.worldcanvas.AppletCanvas") - || name.equals("impsoundworld.Canvas") + || name.equals("impsoundworld.Canvas") || name.equals("appletsoundworld.Canvas"); - } + } /** * Is this a name of one of classes in the javalib.tunes @@ -750,66 +754,67 @@ protected static boolean isTunesPackage(String name) { return name.startsWith("javalib.tunes."); } - /** - * If obj1 is an instance of one of the IColors - * compare the objects by just checking that the class names are the same - * - * @param obj1 - * the object that could be an IColor - * @param obj2 - * the object we are comparing with - * @return true if the test succeeds - */ - protected boolean checkIColors(T obj1, T obj2) { - /** Convert IColor-s from the colors teachpack to java.awt.Color */ - if (obj1.getClass().getName().equals("colors.Red")) - return obj2.getClass().getName().equals("colors.Red"); - if (obj1.getClass().getName().equals("colors.White")) - return obj2.getClass().getName().equals("colors.White"); - if (obj1.getClass().getName().equals("colors.Blue")) - return obj2.getClass().getName().equals("colors.Blue"); - if (obj1.getClass().getName().equals("colors.Black")) - return obj2.getClass().getName().equals("colors.Black"); - if (obj1.getClass().getName().equals("colors.Green")) - return obj2.getClass().getName().equals("colors.Green"); - if (obj1.getClass().getName().equals("colors.Yellow")) - return obj2.getClass().getName().equals("colors.Yellow"); - return false; - } - - /** - * If name is the name of one of the classes that - * extend the WorldImage class within the interim - * implementation of the World library, - * compare the objects by just checking that the class names are the same - * - * Note: The new (javalib package) implementation supports - * the comparison of images that checks the relevant image parameters - * (size, color, pinhole location, etc.) It also moved the definition of the - * WorldImage class hierarchy into its own package, eliminating the - * code duplication. - * - * @param name - * the class name that could be the name of the old WorldImage class - * @return true if the the given name matches one of the class names in the old - * WorldImage hierarchy - */ - protected static boolean isWorldImage(String name){ - ArrayList worldPackageNames = new ArrayList(Arrays.asList( - "funworld", "impworld", "appletworld", - "impsoundworld", "appletsoundworld")); - ArrayList worldImageClassNames = new ArrayList(Arrays.asList( - "CircleImage", "DiskImage", "EllipseImage", "FrameImage", - "FromFileImage", "LineImage", "OvalImage", "OverlayImages", - "OverlayImagesXY", "RectangleImage", "TextImage")); - for(String packagename: worldPackageNames){ - if (name.startsWith(packagename)) - - for (String classname : worldImageClassNames){ - if (name.endsWith(classname)) - return true; - } - } - return false; - } -} \ No newline at end of file + /** + * If obj1 is an instance of one of the IColors + * compare the objects by just checking that the class names are the same + * + * @param obj1 + * the object that could be an IColor + * @param obj2 + * the object we are comparing with + * @return true if the test succeeds + */ + protected boolean checkIColors(T obj1, T obj2) { + /** Convert IColor-s from the colors teachpack to java.awt.Color */ + if (obj1.getClass().getName().equals("colors.Red")) + return obj2.getClass().getName().equals("colors.Red"); + if (obj1.getClass().getName().equals("colors.White")) + return obj2.getClass().getName().equals("colors.White"); + if (obj1.getClass().getName().equals("colors.Blue")) + return obj2.getClass().getName().equals("colors.Blue"); + if (obj1.getClass().getName().equals("colors.Black")) + return obj2.getClass().getName().equals("colors.Black"); + if (obj1.getClass().getName().equals("colors.Green")) + return obj2.getClass().getName().equals("colors.Green"); + if (obj1.getClass().getName().equals("colors.Yellow")) + return obj2.getClass().getName().equals("colors.Yellow"); + return false; + } + + /** + * If name is the name of one of the classes that + * extend the WorldImage class within the interim + * implementation of the World library, + * compare the objects by just checking that the class names are the same + * + * Note: The new (javalib package) implementation supports + * the comparison of images that checks the relevant image parameters + * (size, color, pinhole location, etc.) It also moved the definition of the + * WorldImage class hierarchy into its own package, eliminating the + * code duplication. + * + * @param name + * the class name that could be the name of the old WorldImage class + * @return true if the the given name matches one of the class names in the old + * WorldImage hierarchy + */ + protected static boolean isWorldImage(String name){ + ArrayList worldPackageNames = new ArrayList(Arrays.asList( + "funworld", "impworld", "appletworld", + "impsoundworld", "appletsoundworld")); + ArrayList worldImageClassNames = new ArrayList(Arrays.asList( + "CircleImage", "Crop", "EllipseImage", + "FreezeImage", "FromFileImage", "FromURLImage", "LinearImage", + "OverlayImage", "PolygonImage", "RasterImage", + "RectangleImage", "TextImage")); + for(String packagename: worldPackageNames){ + if (name.startsWith(packagename)) + + for (String classname : worldImageClassNames){ + if (name.endsWith(classname)) + return true; + } + } + return false; + } +} diff --git a/src/tester/Tester.java b/src/tester/Tester.java index 694105a..be76a0e 100644 --- a/src/tester/Tester.java +++ b/src/tester/Tester.java @@ -1850,6 +1850,23 @@ public boolean checkEquivalent(T obj1, T obj2, return this.checkEquivalent(obj1, obj2, equiv, ""); } + /** + * Use the given {@link Equivalence Equivalence} + * function object to determine the NON-equivalence + * of the two given objects. + * + * @param obj1 the first object to compare + * @param obj2 the second object to compare + * @param equiv the function object that implements the + * {@link Equivalence Equivalence} comparison + * + * @return true if the two objects are equivalent + */ + public boolean checkInequivalent(T obj1, T obj2, + Equivalence equiv) { + return this.checkInequivalent(obj1, obj2, equiv, ""); + } + /** * Use the given {@link Equivalence Equivalence} * function object to determine the equivalence @@ -1870,6 +1887,25 @@ public boolean checkEquivalent(T obj1, T obj2, this.combine(obj1, obj2)); } + /** + * Use the given {@link Equivalence Equivalence} + * function object to determine the equivalence + * of the two given objects. + * + * @param obj1 the first object to compare + * @param obj2 the second object to compare + * @param equiv the function object that implements the + * {@link Equivalence Equivalence} comparison + * @param testname the String that describes this test + * + * @return true if the two objects are equivalent + */ + public boolean checkInequivalent(T obj1, T obj2, + Equivalence equiv, String testname) { + this.testname = "Equivalence test: \n" + testname; + return this.checkExpect(! equiv.equivalent(obj1, obj2), "Should be inequivalent: \n" + testname); + } + /*--------------------------------------------------------------------*/ /*---------- HELPERS FOR THE TEST EVALUATION SECTION -----------------*/ /*--------------------------------------------------------------------*/ @@ -2518,9 +2554,10 @@ protected void fullTestReport() { * @param obj * The 'Examples' class instance where the tests are defined */ - public static void run(Object obj) { + public static boolean run(Object obj) { Tester t = new Tester(); t.runAnyTests(obj, false, true); + return t.errors==0; } /** @@ -2529,13 +2566,14 @@ public static void run(Object obj) { * * @param objs An array of 'Examples' class instances where tests are defined */ - public static void runFullReport(Object... objs) { + public static boolean runFullReport(Object... objs) { Tester t = new Tester(); if(objs != null){ for(Object obj : objs){ t.runAnyTests(obj, true, true); } } + return t.errors==0; } /** @@ -2544,9 +2582,10 @@ public static void runFullReport(Object... objs) { * * @param obj The 'Examples' class instance where the tests are defined */ - public static void runReport(Object obj, boolean full, boolean printall) { + public static boolean runReport(Object obj, boolean full, boolean printall) { Tester t = new Tester(); t.runAnyTests(obj, full, printall); + return t.errors==0; } /** @@ -2557,7 +2596,7 @@ public static void runReport(Object obj, boolean full, boolean printall) { * @param printall true if all data should be displayed * @param objs An array of 'Examples' class instances where tests are defined */ - public static void runReports(boolean full, boolean printall, + public static boolean runReports(boolean full, boolean printall, Object... objs) { Tester t = new Tester(); if(objs != null){ @@ -2565,5 +2604,6 @@ public static void runReports(boolean full, boolean printall, t.runAnyTests(obj, full, printall); } } + return t.errors==0; } -} \ No newline at end of file +}