package animation; import javax.swing.JTree; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.MutableTreeNode; import javax.swing.tree.TreePath; /** * represents a line of pseudocode * @author kolja * */ public class PseudoCodeNode extends DefaultMutableTreeNode { public static enum CodeAction { SKIP, STOP, CONTINUE } public static enum CodeStatus { UNFINISHED, BREAKPOINT, FINISHED } private static final long serialVersionUID = 1L; private static int nextNodeId = 0; private final int nodeId; private AnimationController controller; private AnimatedAlgorithm alg; private JTree tree; private CodeLine code; private boolean selected; private boolean breakPoint; private boolean function; private int currentCodeLine; // next forward code line public PseudoCodeNode( String description, JTree tree, CodeLine line, AnimatedAlgorithm alg ) { super( description ); this.alg = alg; synchronized( PseudoCodeNode.class ) { nodeId = nextNodeId++; } selected = false; this.tree = tree; breakPoint = false; currentCodeLine = -1; code = line; function = false; } public void setController( AnimationController c ) { if( children != null ) { for( Object ch : children ) { ((PseudoCodeNode)ch).setController( c ); } } controller = c; } public void writeToStack( Memory m ) { m.declare( "_pos" + nodeId, currentCodeLine, false ); m.declare( "_func" + nodeId, function, false ); currentCodeLine = -1; function = false; setSelected( false ); if( children == null ) return; for( Object c : children ) ((PseudoCodeNode)c).writeToStack( m ); } public void loadFromStack( Memory m ) { currentCodeLine = m.read( "_pos" + nodeId, false ); function = m.read( "_func" + nodeId, false ); if( children == null ) return; for( Object c : children ) ((PseudoCodeNode)c).loadFromStack( m ); } @Override public void add( MutableTreeNode node ) { ((PseudoCodeNode)node).setController( controller ); super.add( node ); } /** * * @return the tree that this node belongs to */ public JTree getTree() { return tree; } /** * checks if this node should be highlighted * @return true if it should, false otherwise */ public boolean isSelected() { return selected; } /** * checks if one of the subnodes of this node is selected. * @return true if one is, false otherwise */ public boolean hasSelectedSubnode() { if( children != null ) { for( Object ch : children ) { if( ((PseudoCodeNode)ch).isSelected() || ((PseudoCodeNode)ch).hasSelectedSubnode() ) return true; } } return false; } private void expandToRoot() { if( parent != null ) ((PseudoCodeNode)parent).expandToRoot(); tree.expandPath( new TreePath( this.getPath() ) ); } /** * highlight this line of pseudocode. * should be called when the line is entered, as it triggers breakpoints * @param selected whether to select or deselect this line * @return false iff a breakpoint was reached and the node was set to be selected. */ public CodeAction setSelected( boolean selected ) { if( selected && breakPoint ) controller.setContinuous( false ); this.selected = selected; if( selected ) { if( controller == null || controller.getStepOption() != 1 || breakPoint ) expandToRoot(); } else { if( controller == null || controller.getStepOption() != 1 ) tree.collapsePath( new TreePath( this.getPath() ) ); } if( breakPoint && selected ) return CodeAction.STOP; // Breakpoint if( controller != null && controller.getStepOption() == 1 && !tree.isVisible( new TreePath( this.getPath() ) ) ) return CodeAction.SKIP; // Step would be to detailed return CodeAction.CONTINUE; // Normal } /** * set a breakpoint at this line of code * @param breakPoint whether there should be a breakpoint or node */ public void setBreakPoint( boolean breakPoint ) { this.breakPoint = breakPoint; } /** * check if there is a breakpoint set at this line of code * @return true, iff there is a breakpoint */ public boolean hasBreakPoint() { return breakPoint; } private PseudoCodeNode getForwardNode() { if( currentCodeLine == -1 ) return this; if( children != null && children.size() > currentCodeLine ) return ((PseudoCodeNode)children.get( currentCodeLine )).getForwardNode(); return this; } private PseudoCodeNode getBackwardNode() { if( currentCodeLine - 1 <= -1 ) return this; if( children != null && children.size() >= currentCodeLine - 1 ) return ((PseudoCodeNode)children.get( currentCodeLine - 1 )).getBackwardNode(); return this; } private CodeStatus stepInto( Memory m ) { currentCodeLine = 0; if( children == null || children.size() == 0 ) { setSelected( false ); return CodeStatus.FINISHED; } else { setSelected( false ); return selectChild( 0, m ); } } /** * Perform one atomic step of the algorithm. Stops at the end of the program. * @return whether the whole stage is finished (afterwards). * For example if all steps are finished, then {@code FINISHED} is returned. */ public CodeStatus forwardStep( Memory m ) { if( currentCodeLine == -1 ) { if( !m.isDefined( "node_" + nodeId + "_call", false ) ) { StackFrame tmp = null; if( function ) { tmp = m.removeFrame(); m.addFrame( tmp ); // a little abuse of the stack to get direct access of the current frame before it could be destroyed by the user defined function } ControlFlow cf = code.runForward( m ); switch( cf.getStatus() ) { case ControlFlow.STEP_INTO: writeToStack( m ); function = true; switch( stepInto( m ) ) { case BREAKPOINT: hiddenActionWithoutSideEffects( m ); return CodeStatus.BREAKPOINT; case FINISHED: currentCodeLine = -1; switch( setSelected( true ) ) { case SKIP: hiddenActionWithoutSideEffects( m ); return forwardStepOverIntern( m ); case STOP: hiddenActionWithoutSideEffects( m ); return CodeStatus.BREAKPOINT; default: break; } case UNFINISHED: hiddenActionWithoutSideEffects( m ); return CodeStatus.UNFINISHED; } case ControlFlow.STEP_OVER: if( function ) { m.addFrame( tmp ); // add old stack frame loadFromStack( m ); // load stored variables m.removeFrame(); // remove the stack frame setSelected( false ); } hiddenActionWithoutSideEffects( m ); return CodeStatus.FINISHED; case ControlFlow.CALL: alg.addActiveFunction( cf.getFunction() ); m.declare( "node_" + nodeId + "_call", cf.getFunction(), false ); setSelected( false ); m.declare( "callback", this, true ); switch( cf.getFunction().setSelected( true ) ) { case CONTINUE: break; case SKIP: switch( cf.getFunction().forwardStepOverIntern( m ) ) { case BREAKPOINT: hiddenActionWithoutSideEffects( m ); return CodeStatus.BREAKPOINT; case FINISHED: setSelected( true ); case UNFINISHED: hiddenActionWithoutSideEffects( m ); return CodeStatus.UNFINISHED; } break; case STOP: hiddenActionWithoutSideEffects( m ); return CodeStatus.BREAKPOINT; } hiddenActionWithoutSideEffects( m ); return CodeStatus.UNFINISHED; } } else { m.undeclare( "node_" + nodeId + "_call", false ); hiddenActionWithoutSideEffects( m ); return CodeStatus.FINISHED; } } else { if( children == null || children.size() <= currentCodeLine ) { throw new IllegalStateException( "Some wired stuff is going on" ); } switch( ( (PseudoCodeNode)children.get( currentCodeLine ) ).forwardStep( m ) ) { case BREAKPOINT: hiddenActionWithoutSideEffects( m ); return CodeStatus.BREAKPOINT; case FINISHED: ( (PseudoCodeNode)children.get( currentCodeLine ) ).setSelected( false ); currentCodeLine++; if( children.size() <= currentCodeLine ) { currentCodeLine = -1; switch( setSelected( true ) ) { case SKIP: hiddenActionWithoutSideEffects( m ); return forwardStepOverIntern( m ); case STOP: hiddenActionWithoutSideEffects( m ); return CodeStatus.BREAKPOINT; default: break; } } else { CodeStatus status = selectChild( currentCodeLine, m ); if( status == CodeStatus.FINISHED ) { currentCodeLine = -1; status = CodeStatus.UNFINISHED; switch( setSelected( true ) ) { case SKIP: hiddenActionWithoutSideEffects( m ); return forwardStepOverIntern( m ); case STOP: hiddenActionWithoutSideEffects( m ); return CodeStatus.BREAKPOINT; default: break; } } hiddenActionWithoutSideEffects( m ); return status; } case UNFINISHED: hiddenActionWithoutSideEffects( m ); return CodeStatus.UNFINISHED; } } hiddenActionWithoutSideEffects( m ); return CodeStatus.UNFINISHED; } private CodeStatus selectChild( int index, Memory m ) { switch( ( (PseudoCodeNode)children.get( index ) ).setSelected( true ) ) { case CONTINUE: return CodeStatus.UNFINISHED; case SKIP: switch( ( (PseudoCodeNode)children.get( index ) ).forwardStepOverIntern( m ) ) { case BREAKPOINT: return CodeStatus.BREAKPOINT; case FINISHED: ( (PseudoCodeNode)children.get( index ) ).setSelected( false ); currentCodeLine++; if( children == null || currentCodeLine >= children.size() ) { return CodeStatus.FINISHED; } else { return selectChild( currentCodeLine, m ); } case UNFINISHED: throw new IllegalStateException( "Skipping a node returned UNFINISHED" ); } case STOP: return CodeStatus.BREAKPOINT; } return CodeStatus.UNFINISHED; } /** * Perform steps until the next line of code on the same level of indentation as this line * is reached. Stops at the end of the program. * @return whether the whole stage is finished (afterwards). * For example if all steps are finished, then {@code FINISHED} is returned. */ public CodeStatus forwardStepOver( Memory m ) { return getForwardNode().forwardStepOverIntern( m ); } private CodeStatus forwardStepOverIntern( Memory m ) { CodeStatus status = null; do { status = forwardStep( m ); } while( status == CodeStatus.UNFINISHED ); return status; } /** * Perform steps until the next line of code on the level of indentation above this lines * level is reached. Stops at the end of the program. * @return whether the whole stage is finished (afterwards). * For example if all steps are finished, then {@code FINISHED} is returned. */ public CodeStatus forwardStepOut( Memory m ) { return getForwardNode().forwardStepOutIntern( m ); } private CodeStatus forwardStepOutIntern( Memory m ) { if( parent != null ) return ((PseudoCodeNode)parent).forwardStepOverIntern( m ); return forwardStepOverIntern( m ); } /** * Undo one atomic step of the algorithm. Stops at the beginning of the program. * @return whether the whole stage is finished in backwards direction (afterwards). * For example if all steps have been reverted, then {@code FINISHED} is returned. */ public CodeStatus backwardStep( Memory m ) { // TODO return null; } /** * Perform backward steps until the previous line of code on the same level of indentation * as this line is reached. Stops at the end of the program. * @return whether the whole stage is finished in backwards direction (afterwards). * For example if all steps have been reverted, then {@code FINISHED} is returned. */ public CodeStatus backwardStepOver( Memory m ) { return getBackwardNode().backwardStepOverIntern( m ); } private CodeStatus backwardStepOverIntern( Memory m ) { CodeStatus status = null; do { status = backwardStep( m ); } while( status == CodeStatus.UNFINISHED ); return status; } /** * Perform backward steps until the previous line of code on the level of indentation above * this lines level is reached. Stops at the end of the program. * @return whether the whole stage is finished in backwards direction (afterwards). * For example if all steps have been reverted, then {@code FINISHED} is returned. */ public CodeStatus backwardStepOut( Memory m ) { return getBackwardNode().backwardStepOutIntern( m ); } private CodeStatus backwardStepOutIntern( Memory m ) { if( parent != null ) return ((PseudoCodeNode)parent).backwardStepOver( m ); return backwardStepOutIntern( m ); } protected void hiddenActionWithoutSideEffects( Memory m ) {} }