/* * @(#)InsertRedBlackAnimation.java * * Last Modified: 9/15/01 */ import java.util.*; import java.awt.*; import java.awt.geom.*; import java.text.*; /** * * The Animation object that defines the Insertion into a Red-Black Tree. Two constructors exist, * one setting the animator and animation color Schemes.

* * @author Corey Sanders * @version 1.2 9/15/01 */ public class InsertRedBlackAnimation extends InsertBSTAnimation implements AnimationListener { /** * The location for the rotations to begin. */ private double WAITING_ROTATIONS; /** * The location for beginning of checking rotations. */ protected static final int BEGIN_CHECK = 0; /** * The location for the left checking of rotations. */ protected static final int LEFT_CHECK = 1; /** * The location for the right checking of rotations. */ protected static final int RIGHT_CHECK = 2; /** * The location for the rotations to check. */ private int rotationCheck; /** * Holds the links that must be changed to red. */ LinkedList redChangeLinks = new LinkedList(); /** * Holds the links that must be changed to black. */ LinkedList blackChangeLinks = new LinkedList(); /** * The location for the rotations to begin. */ private int ROTATIONS; /** * Refers to the current Rotation being drawn. */ RotationBSTAnimation currentRotation = null; /** * The current index of the search through the tree. */ private int currentIndexSearch; /** * The constructor which initiates the status and prepares the colorSchemes. The node * which is animating must be passed. The first step added is the identity step. * * @param node the BSTTree which is animated during the animation. * @param AnimationSchemeLeft the NodeSettings associated with a color scheme according to NodeSettings for the left Animation. * @param AnimationSchemeRight the NodeSettings associated with a color scheme according to NodeSettings for the right Animation. * @param AnimatorScheme the NodeSettings associated with a color scheme according to NodeSettings. * @param KeyAnimatorScheme the KeySettings associated with a color scheme according to KeySettings. * @param startingCmd the Animation command that this should start. * @param stepTime the time for each step of the Animation. Sets the initial value. */ public InsertRedBlackAnimation(BSTTree node, NodeSettings AnimationSchemeLeft, NodeSettings AnimationSchemeRight, NodeSettings AnimatorScheme, KeySettings KeyAnimatorScheme, String startingCmd, int stepTime) { super(node, AnimationSchemeLeft, AnimationSchemeRight, AnimatorScheme, KeyAnimatorScheme, startingCmd, stepTime); } /** * The constructor which initiates the status and sets the colorSchemes to null. No colors * will be change using this animation. The node * which is animating must be passed. The first step added is the identity step. * * @param node the BSTTree which is animated during the animation. */ public InsertRedBlackAnimation(BSTTree node) { this(node, null, null, null , null , Animation.PLAY, DEFAULT_STEP); } /*********************/ /* Animation methods */ /*********************/ /** * Draws the animation of the next step, using the status of the animation (Animation.PLAY, Animation.PAUSE and so forth). * After completing the drawing, the Animation sends an AnimationEvent to all its listeners, indicating * any information that the listerners may wish to use. * * @param g2 the graphics to which the animation step should be drawn. * @param startingStatus the status used as the starting command of animation, if needed. */ public void drawAnimation(Graphics2D g2, String startingStatus) { setStartingCommand(startingStatus); // FINISH status if (getStatus().equals(Animation.FINISH)) { if (getCurrentLocation() < WAITING_ROTATIONS) { int size = getNodeMovements().size(); for(int i = (int)Math.floor(getCurrentLocation()); i < size; i++) { RedBlackTree currentNode = ((RedBlackTree)getNodeMovements().get(i)); if (currentNode != null) { // Fixes 4-nodes if (((RedBlackTree)currentNode.getLeftTree()).isRedLink() && ((RedBlackTree)currentNode.getRightTree()).isRedLink()) { currentNode.setRedLink(true); ((RedBlackTree)currentNode.getLeftTree()).setRedLink(false); ((RedBlackTree)currentNode.getRightTree()).setRedLink(false); } } } restore(); setCurrentLocation(ROTATIONS); currentRotation = null; currentIndexSearch = (getNodeMovements().size()-1); rotationCheck = BEGIN_CHECK; messageAction(Animation.REDRAW); setBlackChangeLinks(); setRedChangeLinks(); } if (currentRotation != null) { currentRotation.setStatus(Animation.FINISH); currentRotation.drawAnimation(g2, Animation.FINISH); setBlackChangeLinks(); setRedChangeLinks(); } while(true) { for( ; currentIndexSearch>=0; currentIndexSearch--) { if (getNodeMovements().get(currentIndexSearch) != null) { makeCurrentRotation((RedBlackTree)getInsertNode(), (RedBlackTree)getNodeMovements().get(currentIndexSearch)); } } animationAction(); return; } } // BEGIN status if (getStatus().equals(Animation.BEGIN)) { setCurrentLocation(0); setPreviousLocation(0); //animator scheme exists getInsertNode().saveSettings(); getInsertNode().setNodeSettings(getAnimatorScheme()); ((DrawingKey)getInsertNode().getValue()).saveSettings(); ((DrawingKey)getInsertNode().getValue()).setKeySettings(getKeyAnimatorScheme()); animationAction(); messageAction(Animation.BEGIN + " Insertion of "+getInsertNode().getKey().toString()); // set starting status setStatus(getStartingCommand()); WAITING_ROTATIONS = getMovements().size(); ROTATIONS = getMovements().size() + 1; return; } // Currently on a step and no changes have occured. Return to starting command if (getStatus().equals(Animation.STEP)) { setStatus(getStartingCommand()); } // PLAY status if (getStatus().equals(Animation.PLAY)) { messageAction(Animation.PLAY); setPreviousLocation(getCurrentLocation()); if (getCurrentLocation() < WAITING_ROTATIONS) { if(getStep()) { // Skip middle animation steps. setCurrentLocation(Math.ceil(getCurrentLocation()) + getStepSize()); } else { // Normal step setCurrentLocation(getCurrentLocation() + getStepSize()); } // Set node positions. nextNodeIndex = (int)Math.ceil(getCurrentLocation()); previousNodeIndex = (int)Math.floor(getCurrentLocation()); // Completed Animation if (getCurrentLocation() >= (getMovements().size()-1)) { setStatus(Animation.STEP); restore(); messageAction(Animation.REDRAW); currentIndexSearch = (getNodeMovements().size()-1); setCurrentLocation(WAITING_ROTATIONS); rotationCheck = BEGIN_CHECK; } else { // Finished a step in the Animation. if (Math.ceil(getPreviousLocation()) == Math.floor(getCurrentLocation()) && getPreviousLocation() != 0) { setStatus(Animation.STEP); comparisonCount++; RedBlackTree previousNode = ((RedBlackTree)getNodeMovements().get(previousNodeIndex)); if (previousNode != null) { // Fixes 4-nodes if (((RedBlackTree)previousNode.getLeftTree()).isRedLink() && ((RedBlackTree)previousNode.getRightTree()).isRedLink()) { previousNode.setRedLink(true); ((RedBlackTree)previousNode.getLeftTree()).setRedLink(false); ((RedBlackTree)previousNode.getRightTree()).setRedLink(false); messageAction(Animation.REDRAW); messageAction("Four-node split, by setting the parent red bit."); } } } // Set movements getMovements().set(previousNodeIndex, resetPosition(previousNodeIndex)); getMovements().set(nextNodeIndex, resetPosition(nextNodeIndex)); // Draw just the node without links. getInsertNode().drawNode(g2, getMovements().getTransformStep(getCurrentLocation())); } } if (getCurrentLocation() < ROTATIONS && getCurrentLocation() >= WAITING_ROTATIONS) { if(getStep()) { // Skip middle animation steps. setCurrentLocation(ROTATIONS); } else { // Normal step setCurrentLocation(getCurrentLocation() + getStepSize()); } } if (getCurrentLocation() >= ROTATIONS) { if (getPreviousLocation() < ROTATIONS) { for( ; currentIndexSearch>=0; currentIndexSearch--) { if (getNodeMovements().get(currentIndexSearch) != null) { makeCurrentRotation((RedBlackTree)getInsertNode(), (RedBlackTree)getNodeMovements().get(currentIndexSearch)); if (currentRotation != null) break; } } if (currentIndexSearch < 0) { setStatus(Animation.FINISH); } else { // Draw rotation animation currentRotation.drawAnimation(g2, Animation.PLAY); messageAction(Animation.REDRAW); currentRotation.addAnimationListener(this); if (rotationCheck == BEGIN_CHECK) { currentIndexSearch--; } } } else { // Set values for animation currentRotation.setStepTime(getStepTime()); currentRotation.setStep(getStep()); // Set play status currentRotation.setStatus(Animation.PLAY); // Draw rotation animation currentRotation.drawAnimation(g2, Animation.PLAY); if (currentRotation.getStatus().equals(Animation.FINISH)) { setBlackChangeLinks(); setRedChangeLinks(); messageAction(Animation.REDRAW); setCurrentLocation(WAITING_ROTATIONS); currentRotation.removeAnimationListener(this); currentRotation = null; } } } } // REWIND status if (getStatus().equals(Animation.REWIND)) { messageAction(Animation.REWIND); setPreviousLocation(getCurrentLocation()); if (getCurrentLocation() < WAITING_ROTATIONS) { if(getStep()) { // Skip middle animation steps. setCurrentLocation(Math.floor(getCurrentLocation()) - getStepSize()); } else { // Normal step setCurrentLocation(getCurrentLocation() - getStepSize()); } // Set node positions. nextNodeIndex = (int)Math.ceil(getCurrentLocation()); previousNodeIndex = (int)Math.floor(getCurrentLocation()); // Beginning of Animation if (getCurrentLocation() <= 0) { setStatus(Animation.PAUSE); setCurrentLocation(0); animationAction(); return; } // Finished a step in the Animation. if (Math.ceil(getPreviousLocation()) == Math.floor(getCurrentLocation()) && getPreviousLocation() != 0) { setStatus(Animation.STEP); comparisonCount--; } // Set movements getMovements().set(previousNodeIndex, resetPosition(previousNodeIndex)); getMovements().set(nextNodeIndex, resetPosition(nextNodeIndex)); // Draw just the node without links. getInsertNode().drawNode(g2, getMovements().getTransformStep(getCurrentLocation())); } if (getCurrentLocation() < ROTATIONS && getCurrentLocation() >= WAITING_ROTATIONS) { messageAction("Cannot Rewind : Beginning of Rotation"); setCurrentLocation(getPreviousLocation()); // Pause status setStatus(Animation.PAUSE); } if (getCurrentLocation() >= ROTATIONS) { // Set values for animation currentRotation.setStepTime(getStepTime()); currentRotation.setStep(getStep()); // Set play status currentRotation.setStatus(Animation.REWIND); // Draw rotation animation currentRotation.drawAnimation(g2, Animation.REWIND); if (currentRotation.getStatus().equals(Animation.PAUSE)) { messageAction("Cannot Rewind : Beginning of Rotation"); // Pause status setStatus(Animation.PAUSE); } } } // PAUSE status if (getStatus().equals(Animation.PAUSE)) { if (getCurrentLocation() < WAITING_ROTATIONS) { nextNodeIndex = (int)Math.ceil(getCurrentLocation()); previousNodeIndex = (int)Math.floor(getCurrentLocation()); // Set movements getMovements().set(previousNodeIndex, resetPosition(previousNodeIndex)); getMovements().set(nextNodeIndex, resetPosition(nextNodeIndex)); // Draw just the node without links. getInsertNode().drawNode(g2, getMovements().getTransformStep(getCurrentLocation())); } if (getCurrentLocation() < ROTATIONS && getCurrentLocation() >= WAITING_ROTATIONS) { } if (getCurrentLocation() >= ROTATIONS) { // Set play status currentRotation.setStatus(Animation.PAUSE); // Draw rotation animation currentRotation.drawAnimation(g2, Animation.PAUSE); } messageAction(Animation.PAUSE); } // STOP status if (getStatus().equals(Animation.STOP)) { if (getCurrentLocation() < WAITING_ROTATIONS) { } if (getCurrentLocation() < ROTATIONS && getCurrentLocation() >= WAITING_ROTATIONS) { } if (getCurrentLocation() >= ROTATIONS) { // Set play status currentRotation.setStatus(Animation.STOP); // Draw rotation animation currentRotation.drawAnimation(g2, Animation.STOP); } messageAction(Animation.STOP); } // FINISH status if (getStatus().equals(Animation.FINISH)) { NumberFormat nf = NumberFormat.getNumberInstance(); nf.setMaximumFractionDigits(3); messageAction(Animation.FINISH); messageAction("*--------Insertion of "+getInsertNode().getKey().toString()+"--------*\n Comparisons: "+ comparisonCount+"\n Average Insertion: "+nf.format(((BSTTreeHead)getInsertNode().getHead()).averageInsertion(insertionSize))+ "\n Worst Case Insertion: "+nf.format(((BSTTreeHead)getInsertNode().getHead()).worstCaseInsertion(insertionSize) )); animationAction(); return; } // Call listeners animationAction(); } /** * Restores the settings of all nodes encountered during the animation. Usually called at * the end of the animation (Animation.FINISH) to restore all settings changed throughout * the animation. * *

This also restores the animator node. It also passes the animation action * to all nodes of Animation.FINISH. */ protected void restore() { super.restore(); AnimationEvent animationEvent = new AnimationEvent(this, AnimationEvent.INSERT_RED_BLACK_ANIMATION, Animation.FINISH); ((RedBlackTree)getInsertNode()).animationEventPerformed(animationEvent); ((RedBlackTree)getInsertNode()).setRedLink(true); messageAction("New Nodes have red bit set upon insertion."); } /** * Constructs the current Rotation according to the current tree, and the defined rotationCheck. * This method called out of context results in unforeseen errors. * * @param newTree RedBlackTree that represents the newly added node. * @param node RedBlackTree that represents the current checking node. * */ protected void makeCurrentRotation(RedBlackTree newTree, RedBlackTree node) { if (rotationCheck == BEGIN_CHECK) { // Keeps Red-Link integrity if ((newTree.getKey()).compareTo(node.getKey()) < 0) { // Go to the left rotationCheck = LEFT_CHECK; if (node != node.getHead().getChild()) { // Two consequetive red links. if ((node.isRedLink()) && (((RedBlackTree)node.getLeftTree()).isRedLink())) { // Same Direction if (node != ((RedBlackTree)node.getParentTree()).getLeftTree()) { currentRotation = (RotationBSTAnimation)((RedBlackTreeHead)node.getHead()).makeRotationAnimation((RedBlackTree)node.getLeftTree()); if (currentRotation == null) { ((RedBlackTreeHead)node.getHead()).rotateUpTreeType((RedBlackTree)node.getLeftTree()); messageAction(Animation.REDRAW); } else { return; } } } } } else { rotationCheck = RIGHT_CHECK; if (node != node.getHead().getChild()) { // Two consequetive red links. if ((node.isRedLink()) && (((RedBlackTree)node.getRightTree()).isRedLink())) { // Same Direction if (node != ((RedBlackTree)node.getParentTree()).getRightTree()) { currentRotation = (RotationBSTAnimation)((RedBlackTreeHead)node.getHead()).makeRotationAnimation((RedBlackTree)node.getRightTree()); if (currentRotation == null) { ((RedBlackTreeHead)node.getHead()).rotateUpTreeType((RedBlackTree)node.getRightTree()); messageAction(Animation.REDRAW); } else { return; } } } } } } if (rotationCheck == LEFT_CHECK) { rotationCheck = BEGIN_CHECK; if (!node.getLeftTree().isEmpty()) { if ((((RedBlackTree)node.getLeftTree()).isRedLink()) && (((RedBlackTree)node.getLeftTree().getLeftTree()).isRedLink()) ) { // Reference to rotated up parent. RedBlackTree newParent = (RedBlackTree)node.getLeftTree(); currentRotation = (RotationBSTAnimation)((RedBlackTreeHead)node.getHead()).makeRotationAnimation((RedBlackTree)node.getLeftTree()); blackChangeLinks.add(newParent); redChangeLinks.add(node); if (currentRotation == null) { ((RedBlackTreeHead)node.getHead()).rotateUpTreeType((RedBlackTree)node.getLeftTree()); setBlackChangeLinks(); setRedChangeLinks(); messageAction(Animation.REDRAW); } } } } if (rotationCheck == RIGHT_CHECK) { rotationCheck = BEGIN_CHECK; if (!node.getRightTree().isEmpty()) { if ((((RedBlackTree)node.getRightTree()).isRedLink()) && (((RedBlackTree)node.getRightTree().getRightTree()).isRedLink()) ) { // Reference to rotated up parent. RedBlackTree newParent = (RedBlackTree)node.getRightTree(); currentRotation = (RotationBSTAnimation)((RedBlackTreeHead)node.getHead()).makeRotationAnimation((RedBlackTree)node.getRightTree()); blackChangeLinks.add(newParent); redChangeLinks.add(node); if (currentRotation == null) { ((RedBlackTreeHead)node.getHead()).rotateUpTreeType((RedBlackTree)node.getRightTree()); setBlackChangeLinks(); setRedChangeLinks(); messageAction(Animation.REDRAW); } } } } } /** * Fixes the redChangeLinks by settings all the nodes links within the list to red. Then * the nodes are removed from the list, always leaving it clear after this call. */ public void setRedChangeLinks() { ListIterator list = redChangeLinks.listIterator(0); while (list.hasNext()) { RedBlackTree currentNode = (RedBlackTree)list.next(); if (!currentNode.isEmpty()) { // Reverses the link currentNode.setRedLink(true); } } redChangeLinks.clear(); } /** * Fixes the blackChangeLinks by settings all the nodes links within the list to black. Then * the nodes are removed from the list, always leaving it clear after this call. */ public void setBlackChangeLinks() { ListIterator list = blackChangeLinks.listIterator(0); while (list.hasNext()) { RedBlackTree currentNode = (RedBlackTree)list.next(); if (!currentNode.isEmpty()) { // Reverses the link currentNode.setRedLink(false); } } blackChangeLinks.clear(); } /** * Implements AnimationListener which requires the following method. * The only status of animation it listens for is Animation.ANIMATION_MESSAGE, to pass * the message on. * * @param e AnimationEvent that represents the information of the Animation. */ public void animationEventPerformed(AnimationEvent e) { if (e.getStatus().equals(Animation.ANIMATION_MESSAGE)) { messageAction(e.getAnimationDescription()); } if (e.getStatus().equals(Animation.STEP)) { animationAction(Animation.STEP, null); } } /** * Calls all of the listeners of the current Animation and passed information regarding the * progress and status of the current Animation. Additionally, the id of the type of animation is * passed. Within, the animationEventPerformed method is called. * * @param cmd String Animation command passed instead of the current Status. * @param description String description for messages. */ protected void animationAction(String cmd, String description) { super.animationAction(AnimationEvent.INSERT_RED_BLACK_ANIMATION, cmd, description, getCurrentLocation() / (double)getMovements().size()); } }