package common;
import java.util.Enumeration;
import java.util.NoSuchElementException;
import java.util.Vector;
import javax.swing.tree.TreeNode;
import expressions.Expression;
/**
* Abstract base class for proof nodes that present the fundamental
* parts of the {@link common.AbstractProofModel}s. The user interface
* and other modules should never access methods of this class directly,
* as they are subject to change, instead the upper layer should restrict
* usage to the {@link common.ProofNode} interface.
*
* @author Benedikt Meurer
* @version $Id$
*
* @see common.ProofNode
* @see javax.swing.tree.TreeNode
*/
public abstract class AbstractProofNode implements ProofNode {
//
// Constants
//
/**
* Empty {@link ProofStep} array which is returned from {@link #getSteps()}
* when no steps have been added to a proof node.
*/
protected static final ProofStep[] EMPTY_ARRAY = new ProofStep[0];
/**
* An enumeration that is always empty. This is used when an enumeration of a
* leaf node's children is requested.
*/
protected static final Enumeration EMPTY_ENUMERATION = new Enumeration() {
public boolean hasMoreElements() { return false; }
public Object nextElement() { throw new NoSuchElementException("No more elements"); }
};
//
// Attributes
//
/**
* The child nodes of this node, or <code>null</code> if this node has no
* children.
*
* @see #children()
* @see #getChildAt(int)
* @see #getChildCount()
*/
protected Vector<AbstractProofNode> children;
/**
* The {@link Expression} associated with this proof node.
*
* @see #getExpression()
*/
protected Expression expression;
/**
* The parent node of this node, or <code>null</code> if this is the root
* node of a proof tree.
*
* @see #getParent()
* @see #setParent(ProofNode)
*/
protected AbstractProofNode parent;
/**
* The proof steps that were already performed on this {@link ProofNode},
* which consist of both the {@link ProofRule} and the {@link Expression}.
*
* @see #getSteps()
* @see #setSteps(ProofStep[])
*/
protected ProofStep[] steps;
/**
* The user object associated with this {@link ProofNode}.
*
* @see #getUserObject()
* @see #setUserObject(Object)
*/
private transient Object userObject;
//
// Constructor
//
/**
* Allocates a new {@link AbstractProofNode} for the given
* <code>expression</code>. The list of {@link ProofStep}s
* starts as an empty list.
*
* @param expression the {@link Expression} for this proof node.
*
* @throws NullPointerException if <code>expression</code>
* is <code>null</code>.
*/
protected AbstractProofNode(Expression expression) {
if (expression == null) {
throw new NullPointerException("expression is null");
}
this.expression = expression;
}
//
// Primitives
//
/**
* {@inheritDoc}
*
* @see ProofNode#getExpression()
*/
public Expression getExpression() {
return this.expression;
}
/**
* Sets the {@link Expression} which is associated
* with this proof node. This method is used by proof
* model implementations, for example to implement the
* {@link ProofModel#translateToCoreSyntax(ProofNode)}
* method.
*
* @param expression the new {@link Expression}.
*/
public void setExpression(Expression expression) {
this.expression = expression;
}
/**
* {@inheritDoc}
*
* @see ProofNode#getSteps()
*/
public ProofStep[] getSteps() {
if (this.steps == null) {
return EMPTY_ARRAY;
}
else {
return this.steps;
}
}
/**
* Sets the {@link ProofStep}s which should be marked as
* completed for this proof node. This method should only
* be used by proof node and model implementations. Other
* modules, like the user interface, should never try to
* explicitly set the steps for a node.
*
* @param steps list of {@link ProofStep}s that should
* be marked as completed for this node.
*
* @see #getSteps()
*/
public void setSteps(ProofStep[] steps) {
this.steps = steps;
}
/**
* {@inheritDoc}
*
* @see ProofNode#isProven()
*/
public boolean isProven() {
// check if any axiom was applied
for (ProofStep step : getSteps()) {
if (step.getRule().isAxiom())
return true;
}
return false;
}
/**
* {@inheritDoc}
*
* @see ProofNode#containsSyntacticSugar()
*/
public boolean containsSyntacticSugar() {
return getExpression().containsSyntacticSugar();
}
/**
* Creates and returns a forward-order enumeration of this node's children.
* Modifying this node's child array invalidates any child enumerations
* created before the modification.
*
* @return an {@link Enumeration} of this node's children
*
* @see javax.swing.tree.TreeNode#children()
*/
public Enumeration children() {
if (this.children == null) {
return EMPTY_ENUMERATION;
}
else {
return this.children.elements();
}
}
/**
* {@inheritDoc}
*
* @see ProofNode#getChildAt(int)
*/
public AbstractProofNode getChildAt(int childIndex) {
if (this.children == null) {
throw new ArrayIndexOutOfBoundsException("node has no children");
}
return this.children.elementAt(childIndex);
}
/**
* Returns the number of children of this node.
*
* @return an integer giving the number of children of this node.
*
* @see javax.swing.tree.TreeNode#getChildCount()
*/
public int getChildCount() {
if (this.children == null) {
return 0;
}
else {
return this.children.size();
}
}
/**
* {@inheritDoc}
*
* @see ProofNode#getParent()
*/
public AbstractProofNode getParent() {
return this.parent;
}
/**
* Sets this node's parent to <code>parent</code> but does not
* change the <code>parent</code>'s child array. This method is
* called from the {@link #insert(AbstractProofNode, int)} and
* {@link #remove(AbstractProofNode)} methods toreassign a child's
* parent, it should not be messaged from anywhere else.
*
* @param parent this node's new parent.
*/
public void setParent(AbstractProofNode parent) {
this.parent = parent;
}
/**
* Returns the index of the specified child in this node's child array. If the
* specified node is not a child of this node, returns <code>-1</code>.
* This method performs a linear search and is O(n) where n is the number of
* children.
*
* @param aChild the {@link TreeNode} to search for among this node's
* children.
*
* @return an integer giving the index of the node in this node's child array,
* or <code>-1</code> if the specified node is a not a child of this
* node.
*
* @throws IllegalArgumentException if <code>aChild</code> is null.
*
* @see javax.swing.tree.TreeNode#getIndex(javax.swing.tree.TreeNode)
*/
public int getIndex(TreeNode aChild) {
if (aChild == null) {
throw new IllegalArgumentException("argument is null");
}
if (!isNodeChild(aChild)) {
return -1;
}
return this.children.indexOf(aChild);
}
/**
* Returns <code>true</code> if this node is allowed to have children.
*
* @return <code>true</code> if this node allows children, else
* <code>false</code>.
*
* @see javax.swing.tree.TreeNode#getAllowsChildren()
*/
public boolean getAllowsChildren() {
return true;
}
//
// User Objects
//
/**
* {@inheritDoc}
*
* @see common.ProofNode#getUserObject()
*/
public Object getUserObject() {
return this.userObject;
}
/**
* {@inheritDoc}
*
* @see common.ProofNode#setUserObject(java.lang.Object)
*/
public void setUserObject(Object userObject) {
this.userObject = userObject;
}
//
// Tree Queries
//
/**
* {@inheritDoc}
*
* @see ProofNode#isNodeAncestor(TreeNode)
*/
public boolean isNodeAncestor(TreeNode anotherNode) {
if (anotherNode == null) {
return false;
}
TreeNode ancestor = this;
do {
if (ancestor == anotherNode) {
return true;
}
} while ((ancestor = ancestor.getParent()) != null);
return false;
}
/**
* {@inheritDoc}
*
* @see ProofNode#isNodeDescendant(ProofNode)
*/
public boolean isNodeDescendant(ProofNode anotherNode) {
if (anotherNode == null)
return false;
return anotherNode.isNodeAncestor(this);
}
/**
* {@inheritDoc}
*
* @see ProofNode#isNodeRelated(ProofNode)
*/
public boolean isNodeRelated(ProofNode aNode) {
return (aNode != null) && (getRoot() == aNode.getRoot());
}
/**
* {@inheritDoc}
*
* @see ProofNode#getRoot()
*/
public AbstractProofNode getRoot() {
AbstractProofNode ancestor = this;
AbstractProofNode previous;
do {
previous = ancestor;
ancestor = ancestor.getParent();
} while (ancestor != null);
return previous;
}
/**
* {@inheritDoc}
*
* @see ProofNode#isRoot()
*/
public boolean isRoot() {
return (getParent() == null);
}
//
// Child Queries
//
/**
* {@inheritDoc}
*
* @see ProofNode#isNodeChild(TreeNode)
*/
public boolean isNodeChild(TreeNode aNode) {
boolean retval;
if (aNode == null) {
retval = false;
}
else {
if (getChildCount() == 0) {
retval = false;
}
else {
retval = (aNode.getParent() == this);
}
}
return retval;
}
/**
* {@inheritDoc}
*
* @see ProofNode#getFirstChild()
*/
public AbstractProofNode getFirstChild() {
if (getChildCount() == 0) {
throw new NoSuchElementException("node has no children");
}
return getChildAt(0);
}
/**
* {@inheritDoc}
*
* @see ProofNode#getLastChild()
*/
public AbstractProofNode getLastChild() {
if (getChildCount() == 0) {
throw new NoSuchElementException("node has no children");
}
return getChildAt(getChildCount() - 1);
}
/**
* {@inheritDoc}
*
* @see ProofNode#getChildAfter(TreeNode)
*/
public AbstractProofNode getChildAfter(TreeNode aChild) {
if (aChild == null) {
throw new IllegalArgumentException("argument is null");
}
int index = getIndex(aChild);
if (index == -1) {
throw new IllegalArgumentException("node is not a child");
}
if (index < getChildCount() - 1) {
return getChildAt(index + 1);
}
else {
return null;
}
}
/**
* {@inheritDoc}
*
* @see ProofNode#getChildBefore(TreeNode)
*/
public AbstractProofNode getChildBefore(TreeNode aChild) {
if (aChild == null) {
throw new IllegalArgumentException("argument is null");
}
int index = getIndex(aChild);
if (index == -1) {
throw new IllegalArgumentException("argument is not a child");
}
if (index > 0) {
return getChildAt(index - 1);
}
else {
return null;
}
}
//
// Leaf Queries
//
/**
* Returns <code>true</code> if this node has no children. To distinguish
* between nodes that have no children and nodes that <i>cannot</i> have
* children (e.g. to distinguish files from empty directories), use this
* method in conjunction with {@link #getAllowsChildren()}.
*
* @return <code>true</code> if this node has no children.
*
* @see #getAllowsChildren()
* @see javax.swing.tree.TreeNode#isLeaf()
*/
public boolean isLeaf() {
return (getChildCount() == 0);
}
/**
* {@inheritDoc}
*
* @see ProofNode#getFirstLeaf()
*/
public AbstractProofNode getFirstLeaf() {
AbstractProofNode node = this;
while (!node.isLeaf()) {
node = node.getFirstChild();
}
return node;
}
/**
* {@inheritDoc}
*
* @see ProofNode#getLastLeaf()
*/
public AbstractProofNode getLastLeaf() {
AbstractProofNode node = this;
while (!node.isLeaf()) {
node = node.getLastChild();
}
return node;
}
//
// Insertion / Removal
//
/**
* Removes <code>newChild</code> from its parent and makes it a child of
* this node by adding it to the end of this node's child array.
*
* @param newChild node to add as a child of this node.
*
* @throws IllegalArgumentException if <code>newChild</code> is <code>null</code>.
*
* @see #insert(ProofNode, int)
*/
public void add(AbstractProofNode newChild) {
if (newChild != null && newChild.getParent() == this) {
insert(newChild, getChildCount() - 1);
}
else {
insert(newChild, getChildCount());
}
}
/**
* Removes <code>newChild</code> from its present parent (if it has a
* parent), sets the child's parent to this node, and then adds the child
* to this node's child array at index <code>childIndex</code>.
* <code>newChild</code> must not be <code>null</code> and must not be an
* ancestor of this node.
*
* @param newChild the {@link ProofNode} to insert under this node.
* @param childIndex the index in this node's child array
* where this node is to be inserted.
*
* @throw ArrayIndexOutOfBoundsException if <code>childIndex</code> is out of bounds.
* @throws IllegalArgumentException if <code>newChild</code> is null or is an ancestor of this node.
*
* @see #isNodeDescendant(ProofNode)
*/
public void insert(AbstractProofNode newChild, int childIndex) {
if (newChild == null) {
throw new IllegalArgumentException("new child is null");
}
else if (isNodeAncestor(newChild)) {
throw new IllegalArgumentException("new child is an ancestor");
}
// unlink from the old parent
AbstractProofNode oldParent = (AbstractProofNode)newChild.getParent();
if (oldParent != null) {
oldParent.remove(newChild);
}
// link to the new parent
newChild.setParent(this);
// allocate the list of children on demand
if (this.children == null) {
this.children = new Vector<AbstractProofNode>();
}
// add to the list of children
this.children.insertElementAt(newChild, childIndex);
}
/**
* Removes the child at the specified index from this node's children
* and sets that node's parent to <code>null</code>. The child node to
* remove must be a <code>ProofNode</code>.
*
* @param childIndex the index in this node's child array of the child to remove.
*
* @throws ArrayIndexOutOfBoundsException if <code>childIndex</code> is out of bounds.
*/
public void remove(int childIndex) {
AbstractProofNode child = (AbstractProofNode)getChildAt(childIndex);
this.children.removeElementAt(childIndex);
child.setParent(null);
}
/**
* Removes <code>aChild</code> from this node's child array, giving it a
* <code>null</code> parent.
*
* @param aChild a child of this node to remove.
*
* @throws IllegalArgumentException if <code>aChild</code> is <code>null</code>
* or is not a child of this node.
*/
public void remove(AbstractProofNode aChild) {
if (aChild == null) {
throw new IllegalArgumentException("argument is null");
}
if (!isNodeChild(aChild)) {
throw new IllegalArgumentException("argument is not a child");
}
remove(getIndex(aChild));
}
/**
* Removes all of this node's children, setting their parents to
* <code>null</code>. If this node has no children, this method
* does nothing.
*/
public void removeAllChildren() {
for (int i = getChildCount() - 1; i >= 0; --i) {
remove(i);
}
}
/**
* Removes the subtree rooted at this node from the tree, giving this
* node a <code>null</code> parent. Does nothing if this node is the
* root of its tree.
*/
public void removeFromParent() {
AbstractProofNode parent = getParent();
if (parent != null) {
parent.remove(this);
}
}
}