/* * @(#)DeleteRedBlackAnimation.java * * Last Modified: 9/15/01 */ import java.util.*; import java.awt.*; import java.awt.geom.*; /** * * The Animation object that defines the Deletion of a node in a DeleteRedBlackAnimation. Two constructors exist, * one setting the animator and line paints, the other using defaults. The animation builds RotationBSTAnimations * as it goes, keeping only one currently animating and allowing rewinding only to the previous * rotation.
*
* @author Corey Sanders
* @version 1.3 9/15/01
*/
public class DeleteRedBlackAnimation extends DeleteBSTAnimation {
/**
* The list of links that need to be changed to red at the next possible location.
*/
LinkedList redChangeLinks = new LinkedList();
/**
* The list of links that need to be changed to black at the next possible location.
*/
LinkedList blackChangeLinks = new LinkedList();
/**
* Refers to the current Rotation being drawn.
*/
RotationBSTAnimation currentRotation = null;
/**
* Holds the node that is replacing.
*/
private RedBlackTree childNode;
/**
* Refers to the current SelectionBSTAnimation
being drawn.
*/
protected SelectionBSTAnimation currentSelection;
/**
* Current RedBlackTree node. This is the current node being checked for cases.
*/
private RedBlackTree currentNode;
/**
* Current RedBlackTree sibling. This is the child of the parent of the current node being checked.
*/
private RedBlackTree currentNodeSibling;
/**
* Constant that defines the starting location.
*/
protected final int START = 0;
/**
* Constant that defines the fading of the erasing node.
*/
protected final int FADE_NODE = 1;
/**
* Defines the partitioning through the tree.
*/
protected int SELECTION_TREE = 2;
/**
* Defines the partitioning through the tree.
*/
protected int FADE_NODE_REPLACE = 3;
/**
* Defines the partitioning through the tree.
*/
protected int REPLACE_NODE = 4;
/**
* The location for the rotations to begin.
*/
private double WAITING_FIX_UP = 4.1;
/**
* Defines the final move through the tree.
*/
protected int FIX_UP = 5;
/**
* Defines the final move through the tree.
*/
protected int FINAL_MOVE = 6;
/**
* Defines the final move through the tree.
*/
protected int END = 7;
/**
* The location for the rotations to check.
*/
private int rotationCheck;
/**
* The location for beginning of red-black deletion.
*/
protected static final int BEGIN_FIXUP = 0;
/**
* The location for case one in red-black deletion.
*/
protected static final int CASE_ONE = 1;
/**
* The location for case two in red-black deletion.
*/
protected static final int CASE_TWO = 2;
/**
* The location for case three in red-black deletion.
*/
protected static final int CASE_THREE = 3;
/**
* The location for case four in red-black deletion.
*/
protected static final int CASE_FOUR = 4;
/**
* The location for the end of red-black deletion.
*/
protected static final int END_FIXUP = 5;
/************************/
/* Accessor methods */
/************************/
/**
* Gets the node being deleted during the replacing.
*
* @return RedBlackTree of the child node.
*/
public RedBlackTree getChildNode() {
return childNode;
}
/************************/
/* Mutator methods */
/************************/
/**
* Sets the node being deleted during the Deletion.
*
* @param BSTTree of the node being deleted.
*/
public void setChildNode(RedBlackTree node) {
childNode = node;
}
/**
* The constructor which initiates the status and prepares the line paints. The node
* which is being deleted must be passed.
*
* @param erasingNode the BSTTree which is being deleted.
* @param RightLinePaint the paint for the right line when drawing the deletion.
* @param LeftLinePaint the paint for the left line when drawing the deletion
* @param startingCmd the Animation command that this should start.
* @param stepTime the time for each step of the Animation. Sets the initial value.
*/
public DeleteRedBlackAnimation(BSTTree erasingNode, PaintSettings RightLinePaint, PaintSettings LeftLinePaint, String startingCmd, int stepTime) {
// Call to AbstractAnimation
super(erasingNode, RightLinePaint, LeftLinePaint, startingCmd,stepTime );
}
/**
* The constructor which initiates the status and sets the line paints to null.
*
* @param erasingNode the BSTTree which is deleted during the deletion.
*/
public DeleteRedBlackAnimation(BSTTree erasingNode) {
this(erasingNode, null, null, Animation.PLAY, DEFAULT_STEP);
}
/***********************/
/* Creation Methods */
/***********************/
/**
* Creates the moving nodes corresponding to the final moving of the nodes.
*/
protected void createFinalMovingNodes() {
// Intialize
finalMovingNodes = new MovingBSTTreeAnimation();
// Head node
RedBlackTree headNode = (RedBlackTree)getErasingNode().getHead().getChild();
// If there is a grandchild.
MovingRedBlackTree headMovingNode = new MovingRedBlackTree(headNode);
if (headNode != getErasingNode() && headNode.isAnimateDrawing() ) {
// If there is a grandchild.
headMovingNode = new MovingRedBlackTree(headNode);
// Add grandchild to descendant animation
finalMovingNodes.add(headMovingNode, headNode);
headMovingNode.setMovePosition(MovingBSTTree.FOLLOW_NODE);
// Set listeners
finalMovingNodes.addAnimationListener(headNode);
(headNode).addAnimator(finalMovingNodes);
}
// Add all children
addNode((RedBlackTree)headNode.getLeftTree(), finalMovingNodes, MovingBSTTree.FOLLOW_NODE, headMovingNode);
addNode((RedBlackTree)headNode.getRightTree(), finalMovingNodes, MovingBSTTree.FOLLOW_NODE, headMovingNode);
}
/**
* Adds all children nodes to the animator list, setting the Moving node as its parent. The move position
* defines the moving of the new node.
*
* @param node the node which the MovingBSTTree made imitates.
* @param animator the MovingBSTTreeAnimation to which the new MovingBSTTree node is added.
* @param movePostion the moving position of the new MovingBSTTree.
* @param parent MovingBSTTree
parent for the node, specifically for following the node.
*/
protected void addNode(RedBlackTree node, MovingBSTTreeAnimation animator, int movePosition, MovingRedBlackTree parent) {
if (node.isEmpty())
return;
// Create new MovingBSTTree
MovingRedBlackTree movingNode = new MovingRedBlackTree(node, parent);
if (node != getErasingNode() && node.isAnimateDrawing()) {
// Sets the move position
movingNode.setMovePosition(movePosition);
// Adds the animator to the MovingBSTTreeAnimation
animator.add(movingNode, node);
// Adds the listener to the animation and the animation to the node.
animator.addAnimationListener(node);
node.addAnimator(animator);
}
// Recursively goes through children
addNode((RedBlackTree)node.getLeftTree(), animator, MovingBSTTree.FOLLOW_NODE, movingNode);
addNode((RedBlackTree)node.getRightTree(), animator, MovingBSTTree.FOLLOW_NODE, movingNode);
}
/*********************/
/* 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.
* BSTTreeHead calls:
*
removeTreeType
- to do the final deletion in the actual code (after partitioning has occured)DeleteBSTAnimation
.
*
*/
protected void makeCurrentRotation() {
if (rotationCheck != END_FIXUP) {
while ((currentNode.getParentTree() != currentNode.getHead()) && (!currentNode.isRedLink())) {
if (rotationCheck == BEGIN_FIXUP) {
rotationCheck = CASE_ONE;
}
if (currentNode == ((RedBlackTree)currentNode.getParentTree()).getLeftTree()) {
currentNodeSibling = (RedBlackTree)((RedBlackTree)currentNode.getParentTree()).getRightTree();
if (rotationCheck == CASE_ONE) {
rotationCheck = CASE_TWO;
// CASE ONE
if (currentNodeSibling.isRedLink()) {
messageAction("Case one on left:");
if (currentNodeSibling.getKey() == null || currentNodeSibling.getKey() == null ) {
messageAction("Sibling ("+currentNodeSibling.getKey().toString()+") of "+currentNode.getKey().toString()+" is red,");
}
blackChangeLinks.add(currentNodeSibling);
redChangeLinks.add(((RedBlackTree)currentNode.getParentTree()));
currentRotation = (RotationBSTAnimation)((RedBlackTreeHead)currentNode.getHead()).makeRotationAnimation(currentNodeSibling);
if (currentRotation == null) {
((RedBlackTreeHead)currentNode.getHead()).rotateUpTreeType(currentNodeSibling);
setBlackChangeLinks();
setRedChangeLinks();
messageAction(Animation.REDRAW);
currentNodeSibling = (RedBlackTree)((RedBlackTree)currentNode.getParentTree()).getRightTree();
}
else {
break;
}
}
}
if (rotationCheck == CASE_TWO) {
// CASE TWO
if (currentNodeSibling.isEmpty()) {
rotationCheck = END_FIXUP;
break;
}
if ( (!((RedBlackTree)currentNodeSibling.getLeftTree()).isRedLink()) && (!((RedBlackTree)currentNodeSibling.getRightTree()).isRedLink()) ){
messageAction("Case two on left:");
if (!currentNodeSibling.isEmpty()) {
messageAction("Sibling ("+currentNodeSibling.getKey().toString()+"). Both of its children are black.");
}
rotationCheck = BEGIN_FIXUP;
currentNodeSibling.setRedLink(true);
messageAction(Animation.REDRAW);
currentNode = (RedBlackTree)currentNode.getParentTree();
currentLocation = WAITING_FIX_UP;
}
else {
rotationCheck = CASE_THREE;
}
}
if (rotationCheck == CASE_THREE) {
rotationCheck = CASE_FOUR;
if (!((RedBlackTree)currentNodeSibling.getRightTree()).isRedLink()) {
messageAction("Case three on left:");
if (!currentNodeSibling.isEmpty()) {
messageAction("Sibling ("+currentNodeSibling.getKey().toString()+"). Left Child is red and right child is black.");
}
blackChangeLinks.add(((RedBlackTree)currentNodeSibling.getLeftTree()));
redChangeLinks.add(currentNodeSibling);
currentRotation = (RotationBSTAnimation)((RedBlackTreeHead)currentNode.getHead()).makeRotationAnimation((RedBlackTree)currentNodeSibling.getLeftTree());
if (currentRotation == null) {
((RedBlackTreeHead)currentNode.getHead()).rotateUpTreeType((RedBlackTree)currentNodeSibling.getLeftTree());
setBlackChangeLinks();
setRedChangeLinks();
messageAction(Animation.REDRAW);
currentNodeSibling = (RedBlackTree)((RedBlackTree)currentNode.getParentTree()).getRightTree();
}
else {
break;
}
}
}
if (rotationCheck == CASE_FOUR) {
messageAction("Case four on left:");
if (!currentNodeSibling.isEmpty()) {
messageAction("Sibling ("+currentNodeSibling.getKey().toString()+"). Right Child is red and left child is black.");
}
blackChangeLinks.add((RedBlackTree)currentNode.getParentTree());
blackChangeLinks.add((RedBlackTree)currentNodeSibling.getRightTree());
if (((RedBlackTree)currentNode.getParentTree()).isRedLink()) {
redChangeLinks.add(currentNodeSibling);
}
else {
blackChangeLinks.add(currentNodeSibling);
}
currentRotation = (RotationBSTAnimation)((RedBlackTreeHead)currentNode.getHead()).makeRotationAnimation(currentNodeSibling);
if (currentRotation == null) {
((RedBlackTreeHead)currentNode.getHead()).rotateUpTreeType(currentNodeSibling);
setBlackChangeLinks();
setRedChangeLinks();
messageAction(Animation.REDRAW);
}
rotationCheck = END_FIXUP;
break;
}
}
// Right Tree
else {
currentNodeSibling = (RedBlackTree)((RedBlackTree)currentNode.getParentTree()).getLeftTree();
if (rotationCheck == CASE_ONE) {
rotationCheck = CASE_TWO;
// CASE ONE
if (currentNodeSibling.isRedLink()) {
messageAction("Case one on right:");
if (currentNodeSibling.getKey() == null || currentNodeSibling.getKey() == null ) {
messageAction("Sibling ("+currentNodeSibling.getKey().toString()+") of "+currentNode.getKey().toString()+" is red,");
}
blackChangeLinks.add(currentNodeSibling);
redChangeLinks.add(((RedBlackTree)currentNode.getParentTree()));
currentRotation = (RotationBSTAnimation)(RotationBSTAnimation)((RedBlackTreeHead)currentNode.getHead()).makeRotationAnimation(currentNodeSibling);
if (currentRotation == null) {
((RedBlackTreeHead)currentNode.getHead()).rotateUpTreeType(currentNodeSibling);
setBlackChangeLinks();
setRedChangeLinks();
messageAction(Animation.REDRAW);
currentNodeSibling = (RedBlackTree)((RedBlackTree)currentNode.getParentTree()).getLeftTree();
}
else {
break;
}
}
}
if (rotationCheck == CASE_TWO) {
if (currentNodeSibling.isEmpty()) {
rotationCheck = END_FIXUP;
break;
}
if ( !((RedBlackTree)currentNodeSibling.getRightTree()).isRedLink() && !((RedBlackTree)currentNodeSibling.getLeftTree()).isRedLink() ) {
messageAction("Case two on right:");
if (!currentNodeSibling.isEmpty()) {
messageAction("Sibling ("+currentNodeSibling.getKey().toString()+"). Both of its children are black.");
}
rotationCheck = BEGIN_FIXUP;
currentNodeSibling.setRedLink(true);
messageAction(Animation.REDRAW);
currentNode = (RedBlackTree)currentNode.getParentTree();
currentLocation = WAITING_FIX_UP;
}
else {
rotationCheck = CASE_THREE;
}
}
if (rotationCheck == CASE_THREE) {
rotationCheck = CASE_FOUR;
if (!((RedBlackTree)currentNodeSibling.getLeftTree()).isRedLink()) {
messageAction("Case three on right:");
if (!currentNodeSibling.isEmpty()) {
messageAction("Sibling ("+currentNodeSibling.getKey().toString()+"). Right Child is red and left child is black.");
}
blackChangeLinks.add(((RedBlackTree)currentNodeSibling.getRightTree()));
redChangeLinks.add(currentNodeSibling);
currentRotation = (RotationBSTAnimation)(RotationBSTAnimation)((RedBlackTreeHead)currentNode.getHead()).makeRotationAnimation((RedBlackTree)currentNodeSibling.getRightTree());
if (currentRotation == null) {
setBlackChangeLinks();
setRedChangeLinks();
((RedBlackTreeHead)currentNode.getHead()).rotateUpTreeType((RedBlackTree)currentNodeSibling.getLeftTree());
messageAction(Animation.REDRAW);
currentNodeSibling = (RedBlackTree)((RedBlackTree)currentNode.getParentTree()).getLeftTree();
}
else {
break;
}
}
}
if (rotationCheck == CASE_FOUR) {
messageAction("Case four on right:");
if (!currentNodeSibling.isEmpty()) {
messageAction("Sibling ("+currentNodeSibling.getKey().toString()+"). Left Child is red and right child is black.");
}
blackChangeLinks.add((RedBlackTree)currentNode.getParentTree());
blackChangeLinks.add((RedBlackTree)currentNodeSibling.getLeftTree());
if (((RedBlackTree)currentNode.getParentTree()).isRedLink()) {
redChangeLinks.add(currentNodeSibling);
}
else {
blackChangeLinks.add(currentNodeSibling);
}
currentRotation = (RotationBSTAnimation)(RotationBSTAnimation)((RedBlackTreeHead)currentNode.getHead()).makeRotationAnimation(currentNodeSibling);
if (currentRotation == null) {
setBlackChangeLinks();
setRedChangeLinks();
((RedBlackTreeHead)currentNode.getHead()).rotateUpTreeType(currentNodeSibling);
messageAction(Animation.REDRAW);
}
rotationCheck = END_FIXUP;
break;
}
}// End of Right Side
}// End of WHILE loop
if (currentRotation == null) {
setBlackChangeLinks();
setRedChangeLinks();
if (currentNode.getKey() != null) {
messageAction("Set Current Node ("+currentNode.getKey().toString()+") as black");
}
currentNode.setRedLink(false);
messageAction(Animation.REDRAW);
currentLocation = END;
}
}// End of FIXUP
else if (rotationCheck == END_FIXUP) {
setBlackChangeLinks();
setRedChangeLinks();
if (currentNode.getKey() != null) {
messageAction("Set Current Node ("+currentNode.getKey().toString()+") as black");
}
currentNode.setRedLink(false);
messageAction(Animation.REDRAW);
currentLocation = END;
}
}
/**
* Draws a dotted line according to the current location within the graphics.
*
* @param g2 Graphics2D to which the line is drawn.
* @param location the double location of progress in the animation.
*/
protected void drawDottedLine( Graphics2D g2, double location) {
if (location >= WAITING_FIX_UP) {
return;
}
// Makes the left dotted line
Line2D.Double dottedLeft = new Line2D.Double( (getErasingNode().getCurrentTransform().getTranslateX() + getErasingNode().getCurrentTransform().getScaleX()/2.0 - 1) , (getErasingNode().getCurrentTransform().getTranslateY() + getErasingNode().getCurrentTransform().getScaleY()/2.0) , (getErasingNode().getCurrentTransform().getTranslateX() + getErasingNode().getCurrentTransform().getScaleX()/2.0 - 1) , (g2.getClipBounds().getY() + g2.getClipBounds().getHeight()));
// Makes the right dotted line
Line2D.Double dottedRight = new Line2D.Double( (getErasingNode().getCurrentTransform().getTranslateX() + getErasingNode().getCurrentTransform().getScaleX()/2.0+ 1) , (getErasingNode().getCurrentTransform().getTranslateY() + getErasingNode().getCurrentTransform().getScaleY()/2.0) , (getErasingNode().getCurrentTransform().getTranslateX() + getErasingNode().getCurrentTransform().getScaleX()/2.0 + 1) , (g2.getClipBounds().getY() + g2.getClipBounds().getHeight()));
// Sets break distance
float dash[] = {10.0f};
BasicStroke dashed = new BasicStroke(2.0f,
BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER,
10.0f, dash, 0.0f);
// Draw dotted line.
if (location <= FADE_NODE) { // Fading in
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float)location));
}
else if (location >= FADE_NODE_REPLACE) {
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float)(1.0 - (location - (Math.floor(location)))) ));
}
else {// Full Composite
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0F));
}
// Set graphics information
g2.setStroke(dashed);
g2.setPaint(getLeftLinePaintSettings().getPaint());
g2.draw(dottedLeft);
g2.setPaint(getRightLinePaintSettings().getPaint());
g2.draw(dottedRight);
}
/**
* 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.DELETE_RED_BLACK_ANIMATION, cmd, description, currentLocation / END);
}
}