Index: javax/swing/Timer.java =================================================================== RCS file: /cvsroot/classpath/classpath/javax/swing/Timer.java,v retrieving revision 1.10 diff -u -r1.10 Timer.java --- javax/swing/Timer.java 31 Jul 2004 15:24:02 -0000 1.10 +++ javax/swing/Timer.java 22 Aug 2004 22:57:24 -0000 @@ -1,4 +1,4 @@ -/* Timer.java -- +/* Timer.java -- Copyright (C) 2002, 2004 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -35,186 +35,45 @@ obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ + package javax.swing; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.Serializable; +import java.util.Comparator; import java.util.EventListener; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.TreeSet; +import javax.swing.SwingUtilities; import javax.swing.event.EventListenerList; - -/** - * DOCUMENT ME! - */ public class Timer implements Serializable { - /** DOCUMENT ME! */ - private static final long serialVersionUID = -1116180831621385484L; - - /** DOCUMENT ME! */ protected EventListenerList listenerList = new EventListenerList(); - // This object manages a "queue" of virtual actionEvents, maintained as a - // simple long counter. When the timer expires, a new event is queued, - // and a dispatcher object is pushed into the system event queue. When - // the system thread runs the dispatcher, it will fire as many - // ActionEvents as have been queued, unless the timer is set to - // coalescing mode, in which case it will fire only one ActionEvent. - - /** DOCUMENT ME! */ - private long queue; - - /** DOCUMENT ME! */ - private Object queueLock = new Object(); - - /** DOCUMENT ME! */ - private Waker waker; - - /** - * DOCUMENT ME! - */ - private void queueEvent() - { - synchronized (queueLock) - { - queue++; - if (queue == 1) - SwingUtilities.invokeLater(new Runnable() - { - public void run() - { - drainEvents(); - } - }); - - } - } - - /** - * DOCUMENT ME! - */ - private void drainEvents() - { - synchronized (queueLock) - { - if (isCoalesce()) - { - if (queue > 0) - fireActionPerformed(); - } - else - { - while (queue > 0) - { - fireActionPerformed(); - queue--; - } - } - queue = 0; - } - } - - static boolean logTimers; - - /** DOCUMENT ME! */ - boolean coalesce = true; - - /** DOCUMENT ME! */ - boolean repeats = true; - - /** DOCUMENT ME! */ - boolean running; - - /** DOCUMENT ME! */ - int ticks; - - /** DOCUMENT ME! */ - int delay; - - /** DOCUMENT ME! */ - int initialDelay; - - /** - * DOCUMENT ME! - */ - private class Waker extends Thread - { - /** - * DOCUMENT ME! - */ - public void run() - { - running = true; - try - { - sleep(initialDelay); - - while (running) - { - try - { - sleep(delay); - } - catch (InterruptedException e) - { - return; - } - queueEvent(); - - if (logTimers) - System.out.println("javax.swing.Timer -> clocktick"); - - if (! repeats) - break; - } - running = false; - } - catch (Exception e) - { - System.out.println("swing.Timer::" + e); - } - } - } - /** - * Creates a new Timer object. + * Creates a Timer which invokes the listener at specific intervals * - * @param d DOCUMENT ME! - * @param listener DOCUMENT ME! + * @param delay interval to fire events to listeners + * @param listener default listener + * @see #setInitialDelay */ - public Timer(int d, ActionListener listener) + public Timer(int delay, ActionListener listener) { - delay = d; - - if (listener != null) + setDelay(delay); + if(listener != null) addActionListener(listener); } /** - * DOCUMENT ME! - * - * @param c DOCUMENT ME! - */ - public void setCoalesce(boolean c) - { - coalesce = c; - } - - /** - * DOCUMENT ME! - * - * @return DOCUMENT ME! - */ - public boolean isCoalesce() - { - return coalesce; - } - - /** - * DOCUMENT ME! + * Adds an ActionListener to the list of listeners to invoke * - * @param listener DOCUMENT ME! + * @param listener the ActionListener to add + * @see java.awt.event.ActionListener */ public void addActionListener(ActionListener listener) { @@ -222,9 +81,10 @@ } /** - * DOCUMENT ME! + * Removes an ActionListener * - * @param listener DOCUMENT ME! + * @param listener the listener to remove + * @see java.awt.event.ActionListener */ public void removeActionListener(ActionListener listener) { @@ -232,136 +92,193 @@ } /** - * DOCUMENT ME! - * - * @param listenerType DOCUMENT ME! - * - * @return DOCUMENT ME! + * Gets the listeners that are of a specific type * + * @param listenerType the type + * @return the array of listeners + * @see #getActionListeners * @since 1.3 */ public EventListener[] getListeners(Class listenerType) { return listenerList.getListeners(listenerType); } - + /** - * DOCUMENT ME! - * - * @return DOCUMENT ME! - * + * Get all the listeners that are ActionListener + * @return the listeners + * @see #getListeners * @since 1.4 */ public ActionListener[] getActionListeners() { - return (ActionListener[]) listenerList.getListeners(ActionListener.class); + return (ActionListener[]) listenerList.getListeners (ActionListener.class); } /** - * DOCUMENT ME! + * fires the specific event to the listeners * - * @param event DOCUMENT ME! + * @param event the event to fire + * @see #isCoalesce + * @see #addActionListener */ protected void fireActionPerformed(ActionEvent event) { - ActionListener[] listeners = getActionListeners(); + final ActionEvent evt = event; + final ActionListener[] listeners = getActionListeners(); - for (int i = 0; i < listeners.length; i++) - listeners[i].actionPerformed(event); + synchronized(this) + { + for (int i = 0; i < listeners.length; i++) + { + final int which = i; + if(!(eventQueued && isCoalesce())) + { + eventQueued = true; + SwingUtilities.invokeLater(new Runnable() + { + public void run() + { + listeners[which].actionPerformed(evt); + eventQueued = false; + } + }); + } + } + } } /** - * DOCUMENT ME! + * Restarts the timer + * + * @see #start + * @see #stop */ - void fireActionPerformed() + public void restart() { - fireActionPerformed(new ActionEvent(this, ticks++, "Timer")); + synchronized(Timer.class) + { + if(!isRunning()) + { + start(); + } + else + { + stop(); + start(); + } + } } /** - * DOCUMENT ME! + * Check if this timer repeatly fires. * - * @param lt DOCUMENT ME! + * @return true if this timer repeats + * @see #setRepeats */ - public static void setLogTimers(boolean lt) + public boolean isRepeats() { - logTimers = lt; + return repeat; } /** - * DOCUMENT ME! + * Check if this timer coalesce events * - * @return DOCUMENT ME! + * @return true if this timer coalesce events + * @see #setCoalesce */ - public static boolean getLogTimers() + public boolean isCoalesce() { - return logTimers; + return coalesce; } /** - * DOCUMENT ME! + * If true, then this timer coalesce events. This may happen if 1) there are + * too many timers 2) there are too many listeners. In that case an + * ActionListener may receive multiple + * actionPerformed with no delays between them. However, if + * coalesce is true, this will not happen. + * By default, this is true. * - * @param d DOCUMENT ME! + * @param flag true if this timer should coalesce events + * @see #fireActionPerformed */ - public void setDelay(int d) + public void setCoalesce(boolean flag) { - delay = d; + coalesce = flag; } /** - * DOCUMENT ME! + * If enabled, log some output to the stdout whenever this timer ticks. + * By default, false. * - * @return DOCUMENT ME! + * @param flag true if this timer should log whenever it ticks */ - public int getDelay() + public static void setLogTimers(boolean flag) { - return delay; + verbose = flag; } /** - * DOCUMENT ME! + * Checks if this timer should log whenever it ticks * - * @param i DOCUMENT ME! + * @return true if this timer logs when it ticks + * @see #setLogTimers */ - public void setInitialDelay(int i) + public static boolean getLogTimers() + { + return verbose; + } + + /** + * Sets the time that this timer should wait before firing + * + * @param delay the delay time in millisecond + * @see #setInitialDelay + */ + public void setDelay(int delay) { - initialDelay = i; + interval = delay; } /** - * DOCUMENT ME! + * Gets the delay time * - * @return DOCUMENT ME! + * @return the delay time + * @see #setDelay */ - public int getInitialDelay() + public int getDelay() { - return initialDelay; + return interval; } /** - * DOCUMENT ME! + * Sets the time that this timer should wait before firing the first time. + * By default this is the same as the regular delay. * - * @param r DOCUMENT ME! + * @param initialDelay the initial delay + * @see #setDelay */ - public void setRepeats(boolean r) + public void setInitialDelay(int initialDelay) { - repeats = r; + init_delay = initialDelay; } /** - * DOCUMENT ME! + * Sets if this timer should repeat. By default, true. * - * @return DOCUMENT ME! + * @param flag true if this timer should repeatly fires */ - public boolean isRepeats() + public void setRepeats(boolean flag) { - return repeats; + repeat = flag; } /** - * DOCUMENT ME! + * Checks if this timer is running * - * @return DOCUMENT ME! + * @return true if this timer is running + * @see #start */ public boolean isRunning() { @@ -369,35 +286,162 @@ } /** - * DOCUMENT ME! + * Starts this timer if it's not started already. + * + * @see #isRunning + * @see #stop */ public void start() { - if (isRunning()) - return; - waker = new Waker(); - waker.start(); + synchronized(Timer.class) + { + if (!isRunning()) + { + if(init_delay == -1) + init_delay = interval; + + ticks = 0; + running = true; + + putPendingTimer(this); + } + } } /** - * DOCUMENT ME! + * Gets the initial delay + * + * @return the initial delay + * @see #setInitialDelay */ - public void restart() + public int getInitialDelay() { - stop(); - start(); + return init_delay == -1 ? interval : init_delay; } /** - * DOCUMENT ME! + * Stops the timer if it's running. + * + * @see #isRunning + * @see #start */ public void stop() { - running = false; - waker.interrupt(); - synchronized (queueLock) + synchronized(Timer.class) + { + if(isRunning()) + { + running = false; + allTimers.remove(this); + if(allTimers.isEmpty()) + waker = null; + } + } + } + + private int ticks = 0; + private static boolean verbose = false; + private boolean running = false; + private boolean repeat = true; + private int interval, init_delay = -1; + private boolean coalesce = true; + + private boolean eventQueued = false; + private long nextExecTime; + + private static synchronized void putPendingTimer(Timer t) + { + if(t.ticks == 0) + t.nextExecTime = t.getInitialDelay() + System.currentTimeMillis(); + else + t.nextExecTime = t.getDelay() + System.currentTimeMillis(); + + allTimers.add(t); + + if(waker == null) + { + waker = new Waker(); + waker.start(); + } + else { - queue = 0; + if(allTimers.size() == 1) + Timer.class.notify(); } } + + private static class Waker extends Thread + { + public void run() + { + while(true) + { + List removedTimers = new LinkedList(); + + synchronized(Timer.class) + { + if(allTimers.isEmpty()) + break; + + Iterator iter = allTimers.iterator(); + while(iter.hasNext()) + { + Timer t = (Timer)iter.next(); + + if(t.nextExecTime < System.currentTimeMillis()) + { + if(verbose) + System.out.println("Timer ticked " + t.ticks + + " times."); + + t.fireActionPerformed(new ActionEvent(t, + t.ticks, + "Timer")); + t.ticks++; + if(t.repeat) + { + removedTimers.add(t); + } + else + { + t.ticks = 0; + t.running = false; + } + iter.remove(); + } + else + { + /* since the TreeSet is sorted, if it's too early to + execute one of the timers, then it's too early to + execute all the subsequent timers + */ + break; + } + } + + iter = removedTimers.iterator(); + while(iter.hasNext()) + { + putPendingTimer((Timer)iter.next()); + } + } + + try + { + sleep(50); + } + catch (InterruptedException _) {} + } + } + } + + private static TreeSet allTimers = new TreeSet(new Comparator() + { + public int compare(Object a, Object b) + { + return (int)(((Timer)a).nextExecTime - ((Timer)b).nextExecTime); + } + }); + + private static Waker waker; }