package de.unisiegen.tpml.core ;
import java.util.Arrays ;
import java.util.EventListener ;
import java.util.LinkedList ;
import javax.swing.event.EventListenerList ;
import javax.swing.event.TreeModelEvent ;
import javax.swing.event.TreeModelListener ;
import javax.swing.tree.TreeNode ;
import javax.swing.tree.TreePath ;
import de.unisiegen.tpml.core.languages.Language ;
import de.unisiegen.tpml.core.languages.LanguageTranslator ;
import de.unisiegen.tpml.core.util.beans.AbstractBean ;
/**
* Abstract implementation of the {@link de.unisiegen.tpml.core.ProofModel}
* interface, which is used by prover implementations. The user interface and
* other modules should never use this class (or a derived class) directly, as
* methods are subject to change. Instead the upper layer should restrict usage
* to the {@link de.unisiegen.tpml.core.ProofModel} interface.
*
* @author Benedikt Meurer
* @version $Rev$
* @see de.unisiegen.tpml.core.AbstractProofNode
* @see de.unisiegen.tpml.core.ProofModel
* @see de.unisiegen.tpml.core.ProofNode
*/
public abstract class AbstractProofModel extends AbstractBean implements
ProofModel
{
//
// Attributes
//
/**
* Whether the user cheated during the proof.
*
* @see #isCheating()
* @see #setCheating(boolean)
*/
private boolean cheating ;
/**
* Whether the proof is finished.
*
* @see #isFinished()
* @see #setFinished(boolean)
*/
private boolean finished ;
/**
* Event listeners.
*
* @see #addTreeModelListener(TreeModelListener)
* @see #removeTreeModelListener(TreeModelListener)
*/
protected EventListenerList listenerList = new EventListenerList ( ) ;
/**
* The root node of the proof tree.
*
* @see #getRoot()
*/
protected AbstractProofNode root ;
/**
* The set of proof rules that can be applied to the nodes within this proof
* model.
*
* @see #getRules()
*/
protected ProofRuleSet ruleSet ;
/**
* The language translator for this model, which is allocated on demand, and
* used to determine whether a given node contains syntactic sugar and
* probably translate the expression for the node to core syntax.
*/
protected LanguageTranslator translator = null ;
//
// Constructor (protected)
//
/**
* Allocates a new <code>AbstractProofModel</code> using the given
* <code>root</code> item.
*
* @param pRoot the new root item.
* @param pRuleSet the set of proof rules.
* @throws NullPointerException if <code>root</code> or <code>ruleSet</code>
* is <code>null</code>.
*/
protected AbstractProofModel ( AbstractProofNode pRoot ,
AbstractProofRuleSet pRuleSet )
{
if ( pRuleSet == null )
{
throw new NullPointerException ( "ruleSet is null" ) ; //$NON-NLS-1$
}
if ( pRoot == null )
{
throw new NullPointerException ( "root is null" ) ; //$NON-NLS-1$
}
this.ruleSet = pRuleSet ;
this.root = pRoot ;
}
//
// Accessors
//
/**
* {@inheritDoc}
*
* @see de.unisiegen.tpml.core.ProofModel#isCheating()
*/
public boolean isCheating ( )
{
return this.cheating ;
}
/**
* Changes the value of the <code>cheating</code> property to the value of
* <code>cheating</code>, and emits a
* {@link java.beans.PropertyChangeEvent} if the values differ.
*
* @param pCheating the new value for the <code>cheating</code> setting.
* @see #isCheating()
*/
protected void setCheating ( boolean pCheating )
{
if ( this.cheating != pCheating )
{
boolean oldCheating = this.cheating ;
this.cheating = pCheating ;
firePropertyChange ( "cheating" , oldCheating , pCheating ) ; //$NON-NLS-1$
}
}
/**
* {@inheritDoc}
*
* @see de.unisiegen.tpml.core.ProofModel#isFinished()
*/
public boolean isFinished ( )
{
return this.finished ;
}
/**
* Changes the value of the <code>finished</code> property to the value of
* <code>finished</code>, and emits a
* {@link java.beans.PropertyChangeEvent} if the values differ.
*
* @param pFinished the new value for the <code>finished</code> setting.
* @see #isFinished()
*/
protected void setFinished ( boolean pFinished )
{
if ( this.finished != pFinished )
{
boolean oldFinished = this.finished ;
this.finished = pFinished ;
firePropertyChange ( "finished" , oldFinished , pFinished ) ; //$NON-NLS-1$
}
}
/**
* {@inheritDoc}
*
* @see de.unisiegen.tpml.core.ProofModel#getLanguage()
*/
public Language getLanguage ( )
{
return this.ruleSet.getLanguage ( ) ;
}
/**
* {@inheritDoc}
*
* @see ProofModel#getRules()
*/
public ProofRule [ ] getRules ( )
{
ProofRule [ ] rules = this.ruleSet.getRules ( ) ;
Arrays.sort ( rules ) ;
return rules ;
}
//
// Actions
//
/**
* {@inheritDoc}
*
* @see de.unisiegen.tpml.core.ProofModel#complete(de.unisiegen.tpml.core.ProofNode)
*/
public void complete ( ProofNode node ) throws ProofGuessException
{
if ( node == null )
{
throw new NullPointerException ( "node is null" ) ; //$NON-NLS-1$
}
// check if we need to guess here
if ( ! node.isProven ( ) )
{
guess ( node ) ;
}
// check if we're done with the proof
if ( ! isFinished ( ) )
{
// complete the child nodes
for ( int n = 0 ; n < node.getChildCount ( ) ; ++ n )
{
complete ( node.getChildAt ( n ) ) ;
}
}
}
/**
* {@inheritDoc}
*
* @see ProofModel#guess(ProofNode)
*/
public abstract void guess ( ProofNode node ) throws ProofGuessException ;
/**
* {@inheritDoc}
*
* @see ProofModel#prove(ProofRule, ProofNode)
*/
public abstract void prove ( ProofRule rule , ProofNode node )
throws ProofRuleException ;
//
// Undo/Redo
//
/**
* The base interface for all undoable edit actions on the tree model. Derived
* classes will need to implement this interface whenever any action on the
* tree is to be performed.
*/
public static interface UndoableTreeEdit
{
/**
* Performs the action of this tree edit. This method is invoked initially,
* when the edit is added via
* {@link AbstractProofModel#addUndoableTreeEdit(UndoableTreeEdit)}, and
* when a previously undone change is redone.
*/
public void redo ( ) ;
/**
* Undoes the effect of a previous call to {@link #redo()}.
*/
public void undo ( ) ;
}
/**
* The list of redoable edits.
*
* @see #isRedoable()
* @see #redo()
*/
private LinkedList < UndoableTreeEdit > redoEdits = new LinkedList < UndoableTreeEdit > ( ) ;
/**
* The list of undoable edits.
*
* @see #isUndoable()
* @see #undo()
*/
private LinkedList < UndoableTreeEdit > undoEdits = new LinkedList < UndoableTreeEdit > ( ) ;
/**
* Adds the specified <code>edit</code> to the undo history. The current
* redo history is cleared.
*
* @param edit the {@link UndoableTreeEdit} to add.
* @throws NullPointerException if <code>edit</code> is <code>null</code>.
*/
protected void addUndoableTreeEdit ( UndoableTreeEdit edit )
{
// remember the previous redoable/undoable properties
boolean oldRedoable = isRedoable ( ) ;
boolean oldUndoable = isUndoable ( ) ;
// clear the redo history
this.redoEdits.clear ( ) ;
// add to the undo history
this.undoEdits.add ( 0 , edit ) ;
// notify the redoable/undoable properties
if ( oldRedoable != isRedoable ( ) )
{
firePropertyChange ( "redoable" , oldRedoable , isRedoable ( ) ) ; //$NON-NLS-1$
}
if ( oldUndoable != isUndoable ( ) )
{
firePropertyChange ( "undoable" , oldUndoable , isUndoable ( ) ) ; //$NON-NLS-1$
}
}
/**
* {@inheritDoc}
*
* @see ProofModel#isRedoable()
*/
public boolean isRedoable ( )
{
return ( ! this.redoEdits.isEmpty ( ) ) ;
}
/**
* {@inheritDoc}
*
* @see ProofModel#isUndoable()
*/
public boolean isUndoable ( )
{
return ( ! this.undoEdits.isEmpty ( ) ) ;
}
/**
* {@inheritDoc}
*
* @see ProofModel#redo()
*/
public void redo ( ) throws CannotRedoException
{
if ( this.redoEdits.isEmpty ( ) )
{
throw new CannotRedoException ( "nothing to redo" ) ; //$NON-NLS-1$
}
// remember the previous redoable/undoable properties
boolean oldRedoable = isRedoable ( ) ;
boolean oldUndoable = isUndoable ( ) ;
// redo the most recent tree edit
UndoableTreeEdit edit = this.redoEdits.poll ( ) ;
this.undoEdits.add ( 0 , edit ) ;
edit.redo ( ) ;
// notify the redoable/undoable properties
if ( oldRedoable != isRedoable ( ) )
{
firePropertyChange ( "redoable" , oldRedoable , isRedoable ( ) ) ; //$NON-NLS-1$
}
if ( oldUndoable != isUndoable ( ) )
{
firePropertyChange ( "undoable" , oldUndoable , isUndoable ( ) ) ; //$NON-NLS-1$
}
}
/**
* {@inheritDoc}
*
* @see ProofModel#undo()
*/
public void undo ( ) throws CannotUndoException
{
if ( this.undoEdits.isEmpty ( ) )
{
throw new CannotUndoException ( "nothing to undo" ) ; //$NON-NLS-1$
}
// remember the previous redoable/undoable properties
boolean oldRedoable = isRedoable ( ) ;
boolean oldUndoable = isUndoable ( ) ;
// undo the most recent tree edit
UndoableTreeEdit edit = this.undoEdits.poll ( ) ;
this.redoEdits.add ( 0 , edit ) ;
edit.undo ( ) ;
// notify the redoable/undoable properties
if ( oldRedoable != isRedoable ( ) )
{
firePropertyChange ( "redoable" , oldRedoable , isRedoable ( ) ) ; //$NON-NLS-1$
}
if ( oldUndoable != isUndoable ( ) )
{
firePropertyChange ( "undoable" , oldUndoable , isUndoable ( ) ) ; //$NON-NLS-1$
}
}
//
// Tree queries
//
/**
* Returns the root of the tree. Returns <code>null</code> only if the tree
* has no nodes.
*
* @return the root of the proof tree.
* @see javax.swing.tree.TreeModel#getRoot()
*/
public AbstractProofNode getRoot ( )
{
return this.root ;
}
/**
* Returns the child of <code>parent</code> at index <code>index</code> in
* the <code>parent</code>'s child array. <code>parent</code> must be a
* node previously obtained from this data source. This should not return
* <code>null</code> if <code>index</code> is a valid index for
* <code>parent</code> (that is
* <code>index >= 0 && index < getChildCount(parent)</code>).
*
* @param parent a node in the tree, obtained from this data source.
* @param index the child index of a child node of <code>parent</code>.
* @return the child of <code>parent</code> at index <code>index</code>.
* @see javax.swing.tree.TreeModel#getChild(java.lang.Object, int)
*/
public ProofNode getChild ( Object parent , int index )
{
return ( ( ProofNode ) parent ).getChildAt ( index ) ;
}
/**
* Returns the number of children of <code>parent</code>. Returns
* <code>0</code> if the node is a leaf or if it has no children.
* <code>parent</code> must be a node previously obtained from this data
* source.
*
* @param parent a node in the tree, obtained from this data source
* @return the number of children of the node <code>parent</code>.
* @see javax.swing.tree.TreeModel#getChildCount(java.lang.Object)
*/
public int getChildCount ( Object parent )
{
return ( ( ProofNode ) parent ).getChildCount ( ) ;
}
/**
* Returns the index of child in parent. If either the parent or child is
* <code>null</code>, returns -1.
*
* @param parent a note in the tree, obtained from this data source.
* @param child the node we are interested in.
* @return the index of the <code>child</code> in the <code>parent</code>,
* or -1 if either the <code>parent</code> or the <code>child</code>
* is <code>null</code>.
* @see TreeNode#getIndex(javax.swing.tree.TreeNode)
* @see javax.swing.tree.TreeModel#getIndexOfChild(java.lang.Object,
* java.lang.Object)
*/
public int getIndexOfChild ( Object parent , Object child )
{
if ( parent == null || child == null ) return - 1 ;
return ( ( ProofNode ) parent ).getIndex ( ( ProofNode ) child ) ;
}
/**
* {@inheritDoc}
*
* @see ProofModel#getPathToRoot(TreeNode)
*/
public TreeNode [ ] getPathToRoot ( TreeNode aNode )
{
return getPathToRoot ( aNode , 0 ) ;
}
/**
* Builds the parents of node up to and including the root node, where the
* original node is the last element in the returned array. The length of the
* returned array gives the node's depth in the tree.
*
* @param aNode the {@link TreeNode} to get the path for
* @param depth an integer giving the number of steps already taken towards
* the root (on recursive calls), used to size the returned array.
* @return an array of {@link TreeNode}s giving the path from the root to the
* specified node.
*/
protected TreeNode [ ] getPathToRoot ( TreeNode aNode , int depth )
{
TreeNode [ ] nodes ;
// This method recurses, traversing towards the root in order
// size the array. On the way back, it fills in the nodes,
// starting from the root and working back to the original node.
/*
* Check for null, in case someone passed in a null node, or they passed in
* an element that isn't rooted at root.
*/
int newDepth = depth ;
if ( aNode == null )
{
if ( newDepth == 0 )
{
return null ;
}
nodes = new TreeNode [ newDepth ] ;
}
else
{
newDepth += 1 ;
if ( aNode == this.root )
nodes = new TreeNode [ newDepth ] ;
else nodes = getPathToRoot ( aNode.getParent ( ) , newDepth ) ;
nodes [ nodes.length - newDepth ] = aNode ;
}
return nodes ;
}
/**
* Returns <code>true</code> if <code>node</code> is a leaf. It is
* possible for this method to return <code>false</code> even if node has no
* children. A directory in a filesystem, for example, may contain no files;
* the node representing the directory is not a leaf, but it also has no
* children.
*
* @param node a node in the tree, obtained from this data source.
* @return <code>true</code> if the <code>node</code> is a leaf.
* @see ProofNode#isLeaf()
* @see javax.swing.tree.TreeModel#isLeaf(java.lang.Object)
*/
public boolean isLeaf ( Object node )
{
return ( ( ProofNode ) node ).isLeaf ( ) ;
}
/**
* This method is not implemented by the {@link ProofModel} class and will an
* {@link UnsupportedOperationException} on every invocation.
*
* @param path path to the node that the user has altered.
* @param newValue the new value from the
* {@link javax.swing.tree.TreeCellEditor}.
* @throws UnsupportedOperationException on every invocation.
* @see javax.swing.tree.TreeModel#valueForPathChanged(javax.swing.tree.TreePath,
* java.lang.Object)
*/
public void valueForPathChanged ( @ SuppressWarnings ( "unused" )
TreePath path , @ SuppressWarnings ( "unused" )
Object newValue )
{
throw new UnsupportedOperationException ( "method not implemented" ) ; //$NON-NLS-1$
}
//
// Events
//
/**
* Adds a listener for the {@link TreeModelEvent} posted after the tree
* changes.
*
* @param l the listener to be added.
* @see #getTreeModelListeners()
* @see #removeTreeModelListener(TreeModelListener)
* @see javax.swing.tree.TreeModel#addTreeModelListener(javax.swing.event.TreeModelListener)
*/
public void addTreeModelListener ( TreeModelListener l )
{
this.listenerList.add ( TreeModelListener.class , l ) ;
}
/**
* Removes a listener previously added with
* {@link #addTreeModelListener(TreeModelListener)}.
*
* @param l the listener to be removed.
* @see #addTreeModelListener(TreeModelListener)
* @see #getTreeModelListeners()
* @see javax.swing.tree.TreeModel#removeTreeModelListener(javax.swing.event.TreeModelListener)
*/
public void removeTreeModelListener ( TreeModelListener l )
{
this.listenerList.remove ( TreeModelListener.class , l ) ;
}
/**
* {@inheritDoc}
*
* @see ProofModel#getTreeModelListeners()
*/
public TreeModelListener [ ] getTreeModelListeners ( )
{
return this.listenerList.getListeners ( TreeModelListener.class ) ;
}
/**
* {@inheritDoc}
*
* @see ProofModel#getListeners(Class)
*/
public < T extends EventListener > T [ ] getListeners (
Class < T > listenerType )
{
return this.listenerList.getListeners ( listenerType ) ;
}
/**
* Notifies all listeners that have registered interest for notification on
* this event type. The event instance is lazily created using the parameters
* passed into the fire method.
*
* @param source the node being changed
* @param path the path to the root node
* @param childIndices the indices of the changed elements
* @param children the changed elements
* @see EventListenerList
*/
protected void fireTreeNodesChanged ( Object source , Object [ ] path ,
int [ ] childIndices , Object [ ] children )
{
// Guaranteed to return a non-null array
Object [ ] listeners = this.listenerList.getListenerList ( ) ;
TreeModelEvent e = null ;
// Process the listeners last to first, notifying
// those that are interested in this event
for ( int i = listeners.length - 2 ; i >= 0 ; i -= 2 )
{
if ( listeners [ i ] == TreeModelListener.class )
{
// Lazily create the event:
if ( e == null )
e = new TreeModelEvent ( source , path , childIndices , children ) ;
( ( TreeModelListener ) listeners [ i + 1 ] ).treeNodesChanged ( e ) ;
}
}
}
/**
* Notifies all listeners that have registered interest for notification on
* this event type. The event instance is lazily created using the parameters
* passed into the fire method.
*
* @param source the node where new elements are being inserted
* @param path the path to the root node
* @param childIndices the indices of the new elements
* @param children the new elements
* @see EventListenerList
*/
protected void fireTreeNodesInserted ( Object source , Object [ ] path ,
int [ ] childIndices , Object [ ] children )
{
// Guaranteed to return a non-null array
Object [ ] listeners = this.listenerList.getListenerList ( ) ;
TreeModelEvent e = null ;
// Process the listeners last to first, notifying
// those that are interested in this event
for ( int i = listeners.length - 2 ; i >= 0 ; i -= 2 )
{
if ( listeners [ i ] == TreeModelListener.class )
{
// Lazily create the event:
if ( e == null )
e = new TreeModelEvent ( source , path , childIndices , children ) ;
( ( TreeModelListener ) listeners [ i + 1 ] ).treeNodesInserted ( e ) ;
}
}
}
/**
* Notifies all listeners that have registered interest for notification on
* this event type. The event instance is lazily created using the parameters
* passed into the fire method.
*
* @param source the node where elements are being removed
* @param path the path to the root node
* @param childIndices the indices of the removed elements
* @param children the removed elements
* @see EventListenerList
*/
protected void fireTreeNodesRemoved ( Object source , Object [ ] path ,
int [ ] childIndices , Object [ ] children )
{
// Guaranteed to return a non-null array
Object [ ] listeners = this.listenerList.getListenerList ( ) ;
TreeModelEvent e = null ;
// Process the listeners last to first, notifying
// those that are interested in this event
for ( int i = listeners.length - 2 ; i >= 0 ; i -= 2 )
{
if ( listeners [ i ] == TreeModelListener.class )
{
// Lazily create the event:
if ( e == null )
e = new TreeModelEvent ( source , path , childIndices , children ) ;
( ( TreeModelListener ) listeners [ i + 1 ] ).treeNodesRemoved ( e ) ;
}
}
}
/**
* Notifies all listeners that have registered interest for notification on
* this event type. The event instance is lazily created using the parameters
* passed into the fire method.
*
* @param source the node where the tree model has changed
* @param path the path to the root node
* @param childIndices the indices of the affected elements
* @param children the affected elements
* @see EventListenerList
*/
protected void fireTreeStructureChanged ( Object source , Object [ ] path ,
int [ ] childIndices , Object [ ] children )
{
// Guaranteed to return a non-null array
Object [ ] listeners = this.listenerList.getListenerList ( ) ;
TreeModelEvent e = null ;
// Process the listeners last to first, notifying
// those that are interested in this event
for ( int i = listeners.length - 2 ; i >= 0 ; i -= 2 )
{
if ( listeners [ i ] == TreeModelListener.class )
{
// Lazily create the event:
if ( e == null )
e = new TreeModelEvent ( source , path , childIndices , children ) ;
( ( TreeModelListener ) listeners [ i + 1 ] ).treeStructureChanged ( e ) ;
}
}
}
//
// Convenience methods for event handling
//
/**
* Invoke this method after you've changed how node is to be represented in
* the tree.
*
* @param node the {@link TreeNode} that was altered.
*/
protected void nodeChanged ( TreeNode node )
{
if ( this.listenerList != null && node != null )
{
// determine the parent node
TreeNode parent = node.getParent ( ) ;
if ( parent != null )
{
// determine the index of the node
int anIndex = parent.getIndex ( node ) ;
if ( anIndex != - 1 )
{
int [ ] cIndexs = new int [ 1 ] ;
cIndexs [ 0 ] = anIndex ;
nodesChanged ( parent , cIndexs ) ;
}
}
else if ( node == getRoot ( ) )
{
nodesChanged ( node , null ) ;
}
}
}
/**
* Invoke this method after you've changed how the children identified by
* <code>childIndicies</code> are to be represented in the tree.
*
* @param node a {@link TreeNode} within this proof model.
* @param childIndices the indices of the children that changed.
*/
protected void nodesChanged ( TreeNode node , int [ ] childIndices )
{
if ( node != null )
{
if ( childIndices != null )
{
// check if any child indices were supplied
int cCount = childIndices.length ;
if ( cCount > 0 )
{
// collect the child nodes
Object [ ] cChildren = new Object [ cCount ] ;
for ( int counter = 0 ; counter < cCount ; ++ counter )
cChildren [ counter ] = node.getChildAt ( childIndices [ counter ] ) ;
// notify the view
fireTreeNodesChanged ( this , getPathToRoot ( node ) , childIndices ,
cChildren ) ;
}
}
else if ( node == getRoot ( ) )
{
fireTreeNodesChanged ( this , getPathToRoot ( node ) , null , null ) ;
}
}
}
/**
* Invoke this method after you've inserted some TreeNodes into node.
* <code>childIndices</code> should be the index of the new elements and
* must be sorted in ascending order.
*
* @param node a node in the proof model.
* @param childIndices the indices of the children that were inserted.
*/
protected void nodesWereInserted ( TreeNode node , int [ ] childIndices )
{
if ( this.listenerList != null && node != null && childIndices != null
&& childIndices.length > 0 )
{
int cCount = childIndices.length ;
Object [ ] newChildren = new Object [ cCount ] ;
for ( int counter = 0 ; counter < cCount ; ++ counter )
newChildren [ counter ] = node.getChildAt ( childIndices [ counter ] ) ;
fireTreeNodesInserted ( this , getPathToRoot ( node ) , childIndices ,
newChildren ) ;
}
}
/**
* Invoke this method after you've removed some TreeNodes from node.
* <code>childIndices</code> should be the index of the removed elements and
* must be sorted in ascending order. And <code>removedChildren</code>
* should be the array of the children objects that were removed.
*
* @param node a node in the proof model.
* @param childIndices the indices of the children that were removed.
* @param removedChildren the removed children.
*/
protected void nodesWereRemoved ( TreeNode node , int [ ] childIndices ,
Object [ ] removedChildren )
{
if ( node != null && childIndices != null )
{
fireTreeNodesRemoved ( this , getPathToRoot ( node ) , childIndices ,
removedChildren ) ;
}
}
}