001package jmri.util;
002
003import java.util.Date;
004import java.util.Timer;
005import java.util.TimerTask;
006import javax.annotation.Nonnull;
007
008
009/**
010 * Common utility methods for working with (@link java.util.Timer)
011 * <p>
012 * Each {@link java.util.Timer} uses a thread, which means that they're
013 * not throw-away timers:  You either track when you can destroy them
014 * (and that destruction is not obvious), or they stick around consuming
015 * resources.
016 * <p>
017 * This class provides most of the functionality of a Timer.
018 * Some differences:
019 * <ul>
020 * <li>When migrating code that uses Timer.cancel() to end operation, you have to
021 *     retain references to the individual TimerTask objects and cancel them instead.
022 * </ul>
023 * <p>
024 * For convenience, this also provides methods to ensure that the task is invoked
025 * on a specific JMRI thread.
026 * <p>
027 * Please note the comment in the {@link Timer} Javadoc about how
028 * {@link java.util.concurrent.ScheduledThreadPoolExecutor} might provide a better
029 * underlying implementation.
030 * Method JavaDoc tweaked from java.util.Timer.
031 * @author Bob Jacobsen Copyright 2018
032 */
033@javax.annotation.concurrent.Immutable
034public final class TimerUtil {
035
036    // class only supplies static methods
037    private TimerUtil() {}
038
039    // Timer implementation methods
040
041    /**
042     * Schedule a TimerTask for execution at the specified time.
043     * If time is in the past, the task is scheduled for immediate execution.
044     * @param task task to be scheduled.
045     * @param time time at which task is to be executed.
046     */
047    public static void schedule(@Nonnull TimerTask task, @Nonnull Date time) {
048        synchronized (commonTimer) {
049            try {
050                commonTimer.schedule(task, time);
051            } catch (IllegalStateException e) {
052                log.warn("During schedule()", e);
053            }
054        }
055    }
056
057    /**
058     * Schedules the specified task for repeated <i>fixed-delay execution</i>,
059     * beginning at the specified time.
060     * Subsequent executions take place at approximately regular intervals,
061     * separated by the specified period.
062     * @param task   task to be scheduled.
063     * @param firstTime First time at which task is to be executed.
064     * @param period time in milliseconds between successive task executions.
065     */
066    public static void schedule(@Nonnull TimerTask task, @Nonnull Date firstTime, long period) {
067        synchronized (commonTimer) {
068            try {
069                commonTimer.schedule(task, firstTime, period);
070            } catch (IllegalStateException e) {
071                log.warn("During schedule()", e);
072            }
073        }
074    }
075
076    /**
077     * Schedules the specified task for execution after the specified delay.
078     * @param task  task to be scheduled.
079     * @param delay delay in milliseconds before task is to be executed.
080     */
081    public static void schedule(@Nonnull TimerTask task, long delay) {
082        synchronized (commonTimer) {
083            try {
084                commonTimer.schedule(task, delay);
085            } catch (IllegalStateException e) {
086                log.warn("During schedule()", e);
087            }
088        }
089    }
090
091    /**
092     * Schedules the specified task for repeated <i>fixed-delay execution</i>,
093     * beginning after the specified delay.
094     * Subsequent executions take place at approximately regular intervals
095     * separated by the specified period.
096     * @param task   task to be scheduled.
097     * @param delay  delay in milliseconds before task is to be executed.
098     * @param period time in milliseconds between successive task executions.
099     */
100    public static void schedule(@Nonnull TimerTask task, long delay, long period) {
101        synchronized (commonTimer) {
102            try {
103                commonTimer.schedule(task, delay, period);
104            } catch (IllegalStateException e) {
105                log.warn("During schedule()", e);
106            }
107        }
108    }
109
110    /**
111     * Schedules the specified task for repeated <i>fixed-delay execution</i>,
112     * beginning at the specified time.
113     * Subsequent executions take place at approximately regular intervals,
114     * separated by the specified period.
115     * @param task   task to be scheduled.
116     * @param firstTime First time at which task is to be executed.
117     * @param period time in milliseconds between successive task executions.
118     */
119    public static void scheduleAtFixedRate(@Nonnull TimerTask task, @Nonnull Date firstTime, long period) {
120        synchronized (commonTimer) {
121            try {
122                commonTimer.schedule(task, firstTime, period);
123            } catch (IllegalStateException e) {
124                log.warn("During schedule()", e);
125            }
126        }
127    }
128
129    /**
130     * Schedules the specified task for repeated <i>fixed-delay execution</i>,
131     * beginning after the specified delay.
132     * Subsequent executions take place at approximately regular intervals
133     * separated by the specified period.
134     * @param task   task to be scheduled.
135     * @param delay  delay in milliseconds before task is to be executed.
136     * @param period time in milliseconds between successive task executions.
137     */
138    public static void scheduleAtFixedRate(@Nonnull TimerTask task, long delay, long period) {
139        synchronized (commonTimer) {
140            try {
141                commonTimer.schedule(task, delay, period);
142            } catch (IllegalStateException e) {
143                log.warn("During schedule()", e);
144            }
145        }
146    }
147
148
149    // GUI-thread implementation methods
150
151    // arrange to run on GUI thread
152    private static TimerTask gtask(TimerTask task) {
153        return new TimerTask(){
154                @Override
155                public void run() {
156                    ThreadingUtil.runOnGUIEventually(() -> {task.run();});
157                }
158        };
159    }
160
161    /**
162     * Schedule a TimerTask on GUI Thread for execution at the specified time.
163     * If time is in the past, the task is scheduled for immediate execution.
164     * @param task task to be scheduled.
165     * @param time time at which task is to be executed.
166     * @return Actual scheduled task; use this if you need to cancel
167     */
168    public static @Nonnull TimerTask scheduleOnGUIThread(@Nonnull TimerTask task, @Nonnull Date time) {
169        synchronized (commonTimer) {
170            var gtask = gtask(task);
171            try {
172                commonTimer.schedule(gtask, time);
173            } catch (IllegalStateException e) {
174                log.warn("During schedule()", e);
175            }
176            return gtask;
177        }
178    }
179
180    /**
181     * Schedules the specified task for repeated <i>fixed-delay execution</i>
182     * on the GUI Thread, beginning at the specified time.
183     * Subsequent executions take place at approximately regular intervals,
184     * separated by the specified period.
185     * @param task   task to be scheduled.
186     * @param firstTime First time at which task is to be executed.
187     * @param period time in milliseconds between successive task executions.
188     * @return Actual scheduled task; use this if you need to cancel
189     */
190    public static @Nonnull TimerTask scheduleOnGUIThread(@Nonnull TimerTask task, @Nonnull Date firstTime, long period) {
191        synchronized (commonTimer) {
192            var gtask = gtask(task);
193            try {
194                commonTimer.schedule(gtask, firstTime, period);
195            } catch (IllegalStateException e) {
196                log.warn("During schedule()", e);
197            }
198            return gtask;
199        }
200    }
201
202    /**
203     * Schedules the specified task for execution on the GUI Thread
204     * after the specified delay.
205     * @param task  task to be scheduled.
206     * @param delay delay in milliseconds before task is to be executed.
207     * @return Actual scheduled task; use this if you need to cancel
208     */
209    public static @Nonnull TimerTask scheduleOnGUIThread(@Nonnull TimerTask task, long delay) {
210        synchronized (commonTimer) {
211            var gtask = gtask(task);
212            try {
213                commonTimer.schedule(gtask, delay);
214            } catch (IllegalStateException e) {
215                log.warn("During schedule()", e);
216            }
217            return gtask;
218        }
219    }
220
221    /**
222     * Schedules the specified task for repeated <i>fixed-delay execution</i>
223     * on the GUI Thread, beginning after the specified delay.
224     * Subsequent executions take place at approximately regular intervals
225     * separated by the specified period.
226     * @param task   task to be scheduled.
227     * @param delay  delay in milliseconds before task is to be executed.
228     * @param period time in milliseconds between successive task executions.
229     * @return Actual scheduled task; use this if you need to cancel
230     */
231    public static @Nonnull TimerTask scheduleOnGUIThread(@Nonnull TimerTask task, long delay, long period) {
232        synchronized (commonTimer) {
233            var gtask = gtask(task);
234            try {
235                commonTimer.schedule(gtask, delay, period);
236            } catch (IllegalStateException e) {
237                log.warn("During schedule()", e);
238            }
239            return gtask;
240        }
241    }
242
243    /**
244     * Schedules the specified task for repeated <i>fixed-delay execution</i>,
245     * on the GUI Thread, beginning at the specified time.
246     * Subsequent executions take place at approximately regular intervals,
247     * separated by the specified period.
248     * @param task   task to be scheduled.
249     * @param firstTime First time at which task is to be executed.
250     * @param period time in milliseconds between successive task executions.
251     * @return Actual scheduled task; use this if you need to cancel
252     */
253    public static @Nonnull TimerTask scheduleAtFixedRateOnGUIThread(@Nonnull TimerTask task, @Nonnull Date firstTime, long period) {
254        synchronized (commonTimer) {
255            var gtask = gtask(task);
256            try {
257                commonTimer.schedule(gtask, firstTime, period);
258            } catch (IllegalStateException e) {
259                log.warn("During schedule()", e);
260            }
261            return gtask;
262        }
263    }
264
265    /**
266     * Schedules the specified task for repeated <i>fixed-delay execution</i>
267     * on the GUI Thread beginning after the specified delay.
268     * Subsequent executions take place at approximately regular intervals
269     * separated by the specified period.
270     * @param task   task to be scheduled.
271     * @param delay  delay in milliseconds before task is to be executed.
272     * @param period time in milliseconds between successive task executions.
273     * @return Actual scheduled task; use this if you need to cancel
274     */
275    public static @Nonnull TimerTask scheduleAtFixedRateOnGUIThread(@Nonnull TimerTask task, long delay, long period) {
276        synchronized (commonTimer) {
277            var gtask = gtask(task);
278            try {
279                commonTimer.schedule(gtask(task), delay, period);
280            } catch (IllegalStateException e) {
281                log.warn("During schedule()", e);
282            }
283            return gtask;
284        }
285    }
286
287
288    // arrange to run on layout thread
289    private static TimerTask ltask(TimerTask task) {
290        return new TimerTask(){
291                @Override
292                public void run() {
293                    ThreadingUtil.runOnLayoutEventually(() -> {task.run();});
294                }
295        };
296    }
297
298    /**
299     * Schedule a TimerTask on Layout Thread for execution at the specified time.
300     * If time is in the past, the task is scheduled for immediate execution.
301     * @param task task to be scheduled.
302     * @param time time at which task is to be executed.
303     */
304    public static void scheduleOnLayoutThread(@Nonnull TimerTask task, @Nonnull Date time) {
305        synchronized (commonTimer) {
306            try {
307                commonTimer.schedule(ltask(task), time);
308            } catch (IllegalStateException e) {
309                log.warn("During schedule()", e);
310            }
311        }
312    }
313
314    /**
315     * Schedules the specified task for repeated <i>fixed-delay execution</i>
316     * on the Layout Thread, beginning at the specified time.
317     * Subsequent executions take place at approximately regular intervals,
318     * separated by the specified period.
319     * @param task   task to be scheduled.
320     * @param firstTime First time at which task is to be executed.
321     * @param period time in milliseconds between successive task executions.
322     */
323    public static void scheduleOnLayoutThread(@Nonnull TimerTask task, @Nonnull Date firstTime, long period) {
324        synchronized (commonTimer) {
325            try {
326                commonTimer.schedule(ltask(task), firstTime, period);
327            } catch (IllegalStateException e) {
328                log.warn("During schedule()", e);
329            }
330        }
331    }
332
333    /**
334     * Schedules the specified task for execution on the Layout Thread
335     * after the specified delay.
336     * @param task  task to be scheduled.
337     * @param delay delay in milliseconds before task is to be executed.
338     */
339    public static void scheduleOnLayoutThread(@Nonnull TimerTask task, long delay) {
340        synchronized (commonTimer) {
341            try {
342                commonTimer.schedule(ltask(task), delay);
343            } catch (IllegalStateException e) {
344                log.warn("During schedule()", e);
345            }
346        }
347    }
348
349    /**
350     * Schedules the specified task for repeated <i>fixed-delay execution</i>
351     * on the Layout Thread beginning after the specified delay.
352     * Subsequent executions take place at approximately regular intervals
353     * separated by the specified period.
354     * @param task   task to be scheduled.
355     * @param delay  delay in milliseconds before task is to be executed.
356     * @param period time in milliseconds between successive task executions.
357     */
358    public static void scheduleOnLayoutThread(@Nonnull TimerTask task, long delay, long period) {
359        synchronized (commonTimer) {
360            try {
361                commonTimer.schedule(ltask(task), delay, period);
362            } catch (IllegalStateException e) {
363                log.warn("During schedule()", e);
364            }
365        }
366    }
367
368    /**
369     * Schedules the specified task for repeated <i>fixed-delay execution</i>,
370     * on the Layout Thread, beginning at the specified time.
371     * Subsequent executions take place at approximately regular intervals,
372     * separated by the specified period.
373     * @param task   task to be scheduled.
374     * @param firstTime First time at which task is to be executed.
375     * @param period time in milliseconds between successive task executions.
376     */
377    public static void scheduleAtFixedRateOnLayoutThread(
378        @Nonnull TimerTask task, @Nonnull Date firstTime, long period) {
379        synchronized (commonTimer) {
380            try {
381                commonTimer.schedule(ltask(task), firstTime, period);
382            } catch (IllegalStateException e) {
383                log.warn("During schedule()", e);
384            }
385        }
386    }
387
388    /**
389     * Schedules the specified task for repeated <i>fixed-delay execution</i>
390     * on the Layout Thread beginning after the specified delay.
391     * Subsequent executions take place at approximately regular intervals
392     * separated by the specified period.
393     * @param task   task to be scheduled.
394     * @param delay  delay in milliseconds before task is to be executed.
395     * @param period time in milliseconds between successive task executions.
396     */
397    public static void scheduleAtFixedRateOnLayoutThread(@Nonnull TimerTask task, long delay, long period) {
398        synchronized (commonTimer) {
399            try {
400                commonTimer.schedule(ltask(task), delay, period);
401            } catch (IllegalStateException e) {
402                log.warn("During schedule()", e);
403            }
404        }
405    }
406
407    static final Timer commonTimer = new Timer("JMRI Common Timer", true);
408
409    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TimerUtil.class);
410}