/* ******************************************************************* * Copyright (c) 1999-2001 Xerox Corporation, * 2002 Palo Alto Research Center, Incorporated (PARC). * All rights reserved. * This program and the accompanying materials are made available * under the terms of the Common Public License v1.0 * which accompanies this distribution and is available at * http://www.eclipse.org/legal/cpl-v10.html * * Contributors: * Xerox/PARC initial implementation * ******************************************************************/ package org.aspectj.testing.compare; import java.util.*; /** * Generic tree and pairwise traversal over it * for comparison purposes. * Implement a generic tree by delegation and * a traversal method for comparing two trees. * Guarantees after initialization: *
It will throw Error if used before initialization completes. */ public class GenericTreeNode implements Comparable { /** underlying node - never null */ Object node; /** node children - type-safe if present */ List children; /** node parent - may be null if this is tree root */ GenericTreeNode parent; /** used to compare underlying node */ Comparator nodeComparator; /** Class used to verify children - may be null */ Class childClass; /** Track whether we are initialized */ boolean ready; /** cache for heavy toString() */ String toStringCache; // --------------------------------------- static /** Singleton visitor to match trees exactly, stopping at first failure */ public static final GenericTreeNodesVisitorI EXACT = new MatchExact(); /** Singleton visitor to print non-matching nodes to System.err */ public static final GenericTreeNodesVisitorI PRINTERR = new PrintNonMatches(); /** Singleton visitor to print all nodes to System.err */ public static final GenericTreeNodesVisitorI PRINTALL = new PrintAllPairs(); /** Singleton comparator for all of us */ public static final Comparator COMPARATOR = new gtnComparator(); /** * Visit two generic trees in order. * Visit the parents, and recursively visit the children. * The children are visited in an order determined by * (any) ordering factory invoked to produce an ordered pair * of children lists for visiting. * All children are visited, even if not paired. * When one list of children is smaller, the * remainder are visited with null complements. * This is true for parents as well; this will visit * all the children of a parent even when the * other parent is null. * This will return false without further visits * as soon as the visitor returns false. That means * the visitor can decide to stop visiting when null * nodes (parent or children) are encountered). * This will return true only after the tree was * visited without objection from the visitor. *
A simple example of using this to determine if * two trees are strictly equal is this: * @return false if the visitor returned false for any visit; * true otherwise. * @throws Error if any children are not GenericTreeNode * @throws IllegalArgumentException if the visitor or both parents are null */ public static boolean traverse(GenericTreeNode lhsParent , GenericTreeNode rhsParent , GenericTreeNodeListOrdererFactoryI childrenPrepFactory , GenericTreeNodesVisitorI visitor) { if (null == visitor) { throw new IllegalArgumentException("null visitor"); } if ((null == lhsParent) && (null == rhsParent)) { throw new IllegalArgumentException("null parents"); } if (visitor.visit(lhsParent, rhsParent)) { List lhsList = (null == lhsParent ? null : lhsParent.getChildren()); List rhsList = (null == rhsParent ? null : rhsParent.getChildren()); if (null != childrenPrepFactory) { GenericTreeNodeListOrdererI factory = childrenPrepFactory.produce(lhsParent, rhsParent, visitor); if (null != factory) { List[] prepKids = factory.produceLists(lhsList, rhsList); if (null != prepKids) { lhsList = prepKids[0]; rhsList = prepKids[1]; } } } ListIterator lhsIterator = (null == lhsList ? null : lhsList.listIterator()); ListIterator rhsIterator = (null == rhsList ? null : rhsList.listIterator()); Object lhs = null; Object rhs = null; while (true) { lhs = null; rhs = null; // get the child pair if ((null != lhsIterator) && (lhsIterator.hasNext())) { lhs = lhsIterator.next(); } if ((null != rhsIterator) && (rhsIterator.hasNext())) { rhs = rhsIterator.next(); } if ((null == rhs) && (null == lhs)) { break; } if ((null != lhs) && (!(lhs instanceof GenericTreeNode))) { throw new Error("GenericTreeNode expected, got lhs " + lhs); } if ((null != rhs) && (!(rhs instanceof GenericTreeNode))) { throw new Error("GenericTreeNode expected, got rhs " + rhs); } // traverse the child pair if (!traverse((GenericTreeNode) lhs, (GenericTreeNode) rhs, childrenPrepFactory, visitor)) { return false; } } } return true; // see also other return statements above } // traverse // --------------------------------------- constructors public GenericTreeNode() { //ready = false; } public void init(GenericTreeNode parent, Comparator nodeComparator, Object node, List children, Class childClass) { if (ready) ready = false; // weak sync if (null == node) { throw new IllegalArgumentException("null node"); } if (null == nodeComparator) { throw new IllegalArgumentException("null nodeComparator"); } this.nodeComparator = nodeComparator; this.node = node; this.children = children; this.parent = parent; if (null != childClass) { ListIterator iter = children.listIterator(); while (iter.hasNext()) { Object kid = iter.next(); if (!(childClass.isAssignableFrom(kid.getClass()))) { String err = "child " + kid + " is not " + childClass; throw new IllegalArgumentException(err); } } this.childClass = childClass; } ready = true; } //-------------------- Object interface /** * ambiguous: equal if *
GenericTreeNode [parent={parentString()] [children={size}] [Node={thisString()]*/ public String longString() { StringBuffer sb = new StringBuffer(); sb.append("GenericTreeNode "); sb.append("[parent="); sb.append(parentString()); sb.append("] [children="); if (null == children) { sb.append("0"); } else { sb.append("" + children.size()); } sb.append("] [Node="); sb.append(thisString()); sb.append("]"); return sb.toString(); } /** render this as "[root] [next] ... [parent]" */ protected String thisString() { return node.toString(); } /** render parent hierarchy as "[root] [next] ... [parent]" */ protected String parentString() { if (null == parent) { return "[null]"; } else { Vector parents = new Vector(); GenericTreeNode par = parent; do { parents.addElement(par); par = par.getParent(); } while (null != par); int size = parents.size(); StringBuffer sb = new StringBuffer(32*size); par = (GenericTreeNode) parents.remove(--size); sb.append("["); do { sb.append(par.toString()); par = (size<1?null:(GenericTreeNode) parents.remove(--size)); if (null != par) { sb.append("] ["); } } while (null != par); sb.append("]"); return sb.toString(); } } // parentString() // -------------------------- Comparable interface /** * Comparable just delegates to Comparator. * i.e.,
return compare(this, rhs)
* @param rhs the Object to compare from the right
* @throws ClassCastException if either is not instanceof GenericTreeNode
* @return 0 if equal, >0 if this is greater than rhs, <0 otherwise.
*/
public int compareTo(Object rhs) {
return COMPARATOR.compare(this, rhs);
}
//-------------------- GenericTreeNode interface
/** @return the actual underlying node */
public Object getNode() { assertReady(); return node ; }
/** @return the actual underlying node */
public GenericTreeNode getParent() { assertReady(); return parent ; }
/** @return List of children - null if none */
public List getChildren() { assertReady(); return children ; }
/** @return Comparator used for comparing node (not children) */
public Comparator getNodeComparator() { assertReady(); return nodeComparator; }
/** @return Class which any children can be assigned to */
public Class getChildClass() { assertReady(); return childClass; }
//-------------------- misc
protected void assertReady() {
if (!ready) throw new Error("not ready");
}
//-------------------- inner classes
/**
* This relies entirely on the GenericTreeNode implementation of equals()
* which delegates to the underlying node equals() or the comparator
*/
static class MatchExact implements GenericTreeNodesVisitorI {
public boolean visit(GenericTreeNode lhs,GenericTreeNode rhs) {
return (null == lhs ? (null == rhs) : lhs.equals(rhs));
}
}
/**
* This prints non-matching pairs to System.err,
* returning true always.
*/
static class PrintNonMatches implements GenericTreeNodesVisitorI {
public boolean visit(GenericTreeNode lhs,GenericTreeNode rhs) {
if (! (null == lhs ? (null == rhs) : lhs.equals(rhs))) {
System.err.println("[lhs=" + lhs + "] [rhs=" + rhs + "]");
}
return true;
}
}
/**
* This prints all pairs to System.err, returning true always.
*/
static class PrintAllPairs implements GenericTreeNodesVisitorI {
public boolean visit(GenericTreeNode lhs,GenericTreeNode rhs) {
System.err.println("[lhs=" + lhs + "] [rhs=" + rhs + "]");
return true;
}
}
/**
* have to separate to distinguish
* gtn.equals() from gtnComparator.equals
*/
static class gtnComparator implements Comparator {
public boolean equals(Object o) {
if (null == o) return false;
return ( o instanceof gtnComparator );
}
public int hashCode() {
return gtnComparator.class.hashCode();
}
// -------------------------- Comparator interface
/**
* Shallow compare of two GenericTreeNodes.
* Comparator.equals(Comparator)
Comparator.compare(Object lhs, Object rhs)