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); }