Newer
Older
MagnetRON / src / org / ntlab / animations / MagnetRONAnimation.java
package org.ntlab.animations;

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Dimension2D;
import java.awt.geom.Point2D;
import java.util.TimerTask;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.ntlab.deltaViewer.DeltaGraphAdapter;
import org.ntlab.deltaViewer.MagnetRONScheduledThreadPoolExecutor;

import com.mxgraph.model.mxICell;
import com.mxgraph.swing.mxGraphComponent;
/**
 * 
 * 
 * @author Nitta Lab.
 */
public abstract class MagnetRONAnimation {

	// Test code (will be deleted)
	private static final String TAG = MagnetRONAnimation.class.getSimpleName();
	
	protected DeltaGraphAdapter mxgraph;
	protected mxGraphComponent mxgraphComponent;

	protected ThreadPoolExecutor threadPoolExecutor;
	protected ScheduledFuture<?> scheduledFuture;
	/**
	 * Initial delays the start of an animation.
	 *
	 * Cannot be negative. Setting to a negative number will result in {@link IllegalArgumentException}.
	 *
	 * @defaultValue 0ms
	 */
	private long initialDelay;
	private static final long DEFAULT_INITIAL_DELAY = 0L;
	/**
	 * Delays the interval between repeating an animation.
	 *
	 * Cannot be negative. Setting to a negative number will result in {@link IllegalArgumentException}.
	 *
	 * @defaultValue 0ms
	 */
	private long delay;
	private static final long DEFAULT_DELAY = 0L;

	/**
	 * Defines the direction/speed at which the {@code MagnetRONAnimation} is expected to
	 * be played.
	 * <p>
	 * The absolute value of {@code rate} indicates the speed which the
	 * {@code Animation} is to be played, while the sign of {@code rate}
	 * indicates the direction. A positive value of {@code rate} indicates
	 * forward play, a negative value indicates backward play and {@code 0.0} to
	 * stop a running {@code MagnetRONAnimation}.
	 * <p>
	 * Rate {@code 1.0} is normal play, {@code 2.0} is 2 time normal,
	 * {@code -1.0} is backwards, etc...
	 *
	 * <p>
	 * Inverting the rate of a running {@code MagnetRONAnimation} will cause the
	 * {@code MagnetRONAnimation} to reverse direction in place and play back over the
	 * portion of the {@code MagnetRONAnimation} that has already elapsed.
	 *
	 * @defaultValue 1.0
	 */
	private double rate;
	private static final double DEFAULT_RATE = 1.0;

	/**
	 * Read-only variable to indicate current direction/speed at which the
	 * {@code MagnetRONAnimation} is being played.
	 * <p>
	 * {@code currentRate} is not necessary equal to {@code rate}.
	 * {@code currentRate} is set to {@code 0.0} when animation is paused or
	 * stopped. {@code currentRate} may also point to different direction during
	 * reverse cycles when {@code reverse} is {@code true}
	 *
	 * @defaultValue 0.0
	 */
	private double currentRate;
	private static final double DEFAULT_CURRENT_RATE = 0.0;

	/**
	 * Defines the number of cycles in this animation. The {@code totalCycleCount}
	 * may be {@code INDEFINITE} for animations that repeat indefinitely, but
	 * must otherwise be > 0.
	 * <p>
	 * It is not possible to change the {@code totalCycleCount} of a running
	 * {@code MagnetRONAnimation}. If the value of {@code totalCycleCount} is changed for a
	 * running {@code MagnetRONAnimation}, the animation has to be stopped and started again to pick
	 * up the new value.
	 *
	 * @defaultValue 1
	 *
	 */
	private int totalCycleCount;
	private static final int DEFAULT_TOTAL_CYCLE_COUNT = 1;
	/**
	 * The current number of cycles in this animation.
	 * 
	 * @defaultValu 0
	 */
	private int currentCycleCount = 0;

	/**
	 * Used to specify an animation that repeats indefinitely, until the
	 * {@code stop()} method is called.
	 */
	private static final int INDEFINITE = -1;

	/**
	 * The status of the {@code MagnetRONAnimation}.
	 *
	 * In {@code MagnetRONAnimation} can be in one of three states:
	 * {@link Status#STOPPED}, {@link Status#PAUSED} or {@link Status#RUNNING}.
	 */
	private Status currentStatus;
	private static final Status DEFAULT_STATUS = Status.STOPPED;

	/**
	 * The action to be executed at the conclusion of this {@code MagnetRONAnimation}.
	 */
	private ActionListener onFinished;

	/**
	 * Defines whether this
	 * {@code MagnetRONAnimation} reverses direction on alternating cycles. If
	 * {@code true}, the
	 * {@code MagnetRONAnimation} will proceed reverses on the cycle. 
	 * Otherwise, animation will loop such that each cycle proceeds forward from the start.
	 *
	 * It is not possible to change the {@code reverse} flag of a running
	 * {@code MagnetRONAnimation}. If the value of {@code reverse} is changed for a
	 * running {@code MagnetRONAnimation}, the animation has to be stopped and started again to pick
	 * up the new value.
	 *
	 * @defaultValue false
	 */
	private boolean reverse;
	private static final boolean DEFAULT_REVERSE = false;

	/**
	 * The object to animate.
	 */
	private mxICell sourceCell;

	/**
	 * The initial point of sourceCell.
	 */
	private Point2D sourceInitPoint;

	/**
	 * The initial size of sourceCell.
	 */
	private Dimension2D sourceInitDimension;

	/**
	 * The possible state for MagnetRONAnimation.
	 */
	protected static enum Status {
		/**
		 * The paused state.
		 */
		PAUSED,
		/**
		 * The running state.
		 */
		RUNNING,
		/**
		 * The stopped state.
		 */
		STOPPED
	}

	private static int animationCount = 0;
	
	/**
	 * The constructor of {@code MagnetRONAnimation}.
	 * 
	 * @param mxgraph: visualization model
	 * @param mxgraphComponent: visualization model group
	 */
	protected MagnetRONAnimation(DeltaGraphAdapter mxgraph, mxGraphComponent mxgraphComponent) {
		this.mxgraph = mxgraph;
		this.mxgraphComponent = mxgraphComponent;
	}

	protected void setThreadPoolExecutor(ThreadPoolExecutor threadPoolExecutor) {
		this.threadPoolExecutor = threadPoolExecutor;
	}

	public void setInitialDelay(long initialDelay) {
		this.initialDelay = initialDelay;
	}

	public void setDelay(long delay) {
		this.delay = delay;
	}

	protected void setRate(double rate) {
		this.rate = rate;
	}

	protected void setCurrentRate(double currentRate) {
		this.currentRate = currentRate;
	}

	public void setTotalCycleCount(int totalCycleCount) {
		this.totalCycleCount = totalCycleCount;
	}

	protected void setCurrentCycleCount(int currentCycleCount) {
		this.currentCycleCount = currentCycleCount;
	}

	protected void setCurrentStatus(Status currentStatus) {
		this.currentStatus = currentStatus;
	}

	public void setOnFinished(ActionListener onFinished) {
		this.onFinished = onFinished;
	}

	public void setReverse(boolean reverse) {
		this.reverse = reverse;
	}

	protected void setSourceCell(mxICell sourceCell) {
		this.sourceCell = sourceCell;
	}

	protected void setSourceInitialPoint(Point2D sourceInitPoint) {
		this.sourceInitPoint = sourceInitPoint;
	}

	protected void setSourceInitialDimension(Dimension2D sourceInitDimension) {
		this.sourceInitDimension = sourceInitDimension;
	}

	protected void setScheduledFuture(ScheduledFuture<?> scheduledFuture) {
		this.scheduledFuture = scheduledFuture;
	}

	protected abstract void setDestination(double x, double y);

	protected abstract void setVelocity(double x, double y);

	protected ThreadPoolExecutor getThreadPoolExecutor() {
		return threadPoolExecutor;
	}

	public long getInitialDelay() {
		if (initialDelay == 0L) return DEFAULT_INITIAL_DELAY;
		return initialDelay;
	}

	public long getDelay() {
		if (delay == 0L) return DEFAULT_DELAY;
		return delay;
	}

	protected double getRate() {
		if (rate == 0.0) return DEFAULT_RATE;
		return rate;
	}

	protected double getCurrentRate() {
		if (currentRate == 0.0) return DEFAULT_CURRENT_RATE;
		return currentRate;
	}

	public int getTotalCycleCount() {
		if (totalCycleCount == 0) return DEFAULT_TOTAL_CYCLE_COUNT;
		return totalCycleCount;
	}

	protected int getCurrentCycleCount() {
		return currentCycleCount;
	}

	protected Status getCurrentStatus() {
		if (currentStatus == null) return DEFAULT_STATUS;
		return currentStatus;
	}

	public ActionListener getOnFinished() {
		return onFinished;
	}

	public boolean getReverse() {
		if (!reverse) return DEFAULT_REVERSE;
		return reverse;
	}

	protected mxICell getSourceCell() {
		return sourceCell;
	}

	protected Point2D getSourceInitialPoint() {
		return sourceInitPoint;
	}

	protected Dimension2D getSourceInitialDimension() {
		return sourceInitDimension;
	}

	protected ScheduledFuture<?> getScheduledFuture() {
		return scheduledFuture;
	}

	/**
	 * Set expand or reduction animation of edge to targetPoint.
	 * Must be call {@code MagnetRONAnimation#init(mxICell, mxPoint, ThreadPoolExecutor)} before calling {@code MagnetRONAnimation#play()}.
	 * 
	 * @param sourceCell: edge object
	 * @param destinationPoint
	 */
	public void init(mxICell sourceCell, double destinationX, double destinationY, ThreadPoolExecutor threadPoolExecutor) {
		setSourceCell(sourceCell);
		setDestination(destinationX, destinationY);
		setThreadPoolExecutor(threadPoolExecutor);
		setSourceInitialPoint(getSourceCell().getGeometry().getPoint());
		setSourceInitialDimension(
				new Dimension((int) getSourceCell().getGeometry().getWidth(), 
						(int) getSourceCell().getGeometry().getHeight()));
		setCurrentCycleCount(0);
	}

	public void updateCurrentCycle() {
		if (!getReverse()) { // Animation direction is forward.
			setCurrentCycleCount((int) (currentCycleCount + Math.signum(getTotalCycleCount())));
		} else {
			setCurrentCycleCount((int) (currentCycleCount - Math.signum(getTotalCycleCount())));
		}
	}

	public void interpolate(double cycleCount) {

	}

	public void playFrom() {

	}

	public void play() {
		switch (getCurrentStatus()) {
			case STOPPED:
				if (getThreadPoolExecutor() != null & getThreadPoolExecutor() instanceof ScheduledThreadPoolExecutor) {
					ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = (ScheduledThreadPoolExecutor) getThreadPoolExecutor();
					setThreadPoolExecutor(scheduledThreadPoolExecutor);
					ScheduledFuture<?> scheduledFuture = scheduledThreadPoolExecutor.scheduleWithFixedDelay(new TimerTask() {
						@Override
						public void run() {
							if(Math.abs(getCurrentCycleCount()) < Math.abs(getTotalCycleCount())) {
								// Test code (will be deleted)
								System.out.println(TAG + ": Run task " + getSourceCell().getId() + " " + MagnetRONAnimation.this.getClass().getSimpleName() + "-" + getCurrentCycleCount() + ". ThreadId=" + Thread.currentThread().getId());
								updateCurrentCycle();
								jumpTo(getCurrentCycleCount());
							} else if(Math.abs(getCurrentCycleCount()) >= Math.abs(getTotalCycleCount())){
								animationCount = 0;
								onFinished();
							}
						}
					}, getInitialDelay(), getDelay(), TimeUnit.MILLISECONDS);
					setScheduledFuture(scheduledFuture);
					setCurrentStatus(Status.RUNNING);
					animationCount = 1;
				};
				break;
			case PAUSED:
				if (getThreadPoolExecutor() != null & getThreadPoolExecutor() instanceof MagnetRONScheduledThreadPoolExecutor) {
					MagnetRONScheduledThreadPoolExecutor scheduledThreadPoolExecutor = (MagnetRONScheduledThreadPoolExecutor) getThreadPoolExecutor();
					scheduledThreadPoolExecutor.resume();
					setCurrentStatus(Status.RUNNING);
				}
				break;
			default:
				break;
		};
	}
	
	/**
	 * Sleep main thread and wait for {@link MagnetRONAnimation#play()} to finish running.
	 */
	public static void waitAnimationEnd() {
		while (animationCount > 0) {
			try {
				Thread.sleep(1L);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		// Buffer for another waiting animation. 
		try {
			Thread.sleep(30L);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Play animation in sync with main thread.
	 */
	public void syncPlay() {
		if (getCurrentStatus() == Status.STOPPED) {
			try {
				Thread.sleep(getInitialDelay());
				while (true) {
					while (getCurrentStatus() == Status.PAUSED) {
						Thread.sleep(1L);
					}
					if(Math.abs(getCurrentCycleCount()) < Math.abs(getTotalCycleCount())) {
						// Test code (will be deleted)
						System.out.println(TAG + ": Run task " + getSourceCell().getId() + " " + MagnetRONAnimation.this.getClass().getSimpleName() + "-" + getCurrentCycleCount() + ". ThreadId=" + Thread.currentThread().getId());
						updateCurrentCycle();
						jumpTo(getCurrentCycleCount());
					} else if(Math.abs(getCurrentCycleCount()) >= Math.abs(getTotalCycleCount())){
						onFinished();
						break;
					}
					Thread.sleep(getDelay());
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	public void playFormStart() {

	}

	public void stop() {
		if (getCurrentStatus() == Status.RUNNING) {
			getScheduledFuture().cancel(true);
		}
		setCurrentStatus(Status.STOPPED);
		setCurrentRate(0.0);
	}

	public void pause() {
		if (getCurrentStatus() == Status.RUNNING) {
			if (getThreadPoolExecutor() != null && getThreadPoolExecutor() instanceof MagnetRONScheduledThreadPoolExecutor) {
				MagnetRONScheduledThreadPoolExecutor scheduledThreadPoolExecutor = (MagnetRONScheduledThreadPoolExecutor) getThreadPoolExecutor();
				scheduledThreadPoolExecutor.pause();
				setCurrentStatus(Status.PAUSED);
			}
		}

	}

	private final void onFinished() {
		stop();
		final ActionListener listener = getOnFinished();
		if (listener != null) {
			try {
				listener.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, null));
			} catch (Exception e) {
				Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e);
			}
		}
	}

	protected abstract void jumpTo(int currentCycleCount);
}