001/*
002 *  jDTAUS Core RI Task Monitor
003 *  Copyright (C) 2005 Christian Schulte
004 *  <cs@schulte.it>
005 *
006 *  This library is free software; you can redistribute it and/or
007 *  modify it under the terms of the GNU Lesser General Public
008 *  License as published by the Free Software Foundation; either
009 *  version 2.1 of the License, or any later version.
010 *
011 *  This library is distributed in the hope that it will be useful,
012 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
013 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 *  Lesser General Public License for more details.
015 *
016 *  You should have received a copy of the GNU Lesser General Public
017 *  License along with this library; if not, write to the Free Software
018 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
019 *
020 */
021package org.jdtaus.core.monitor.ri;
022
023import java.util.Date;
024import java.util.HashMap;
025import java.util.Iterator;
026import java.util.Locale;
027import java.util.Map;
028import javax.swing.event.EventListenerList;
029import org.jdtaus.core.container.ContainerFactory;
030import org.jdtaus.core.logging.spi.Logger;
031import org.jdtaus.core.monitor.Task;
032import org.jdtaus.core.monitor.TaskEvent;
033import org.jdtaus.core.monitor.TaskListener;
034import org.jdtaus.core.monitor.spi.TaskMonitor;
035import org.jdtaus.core.text.Message;
036
037/**
038 * jDTAUS Core SPI {@code TaskMonitor} reference implementation.
039 * <p>The reference implementation uses a thread checking the state of all tasks
040 * in the system periodically which is started upon initialization and runs
041 * endlessly. Monitoring is controlled by property {@code pollIntervalMillis}
042 * specifying the milliseconds of one period. Each time a period ends, tasks
043 * are checked for state changes and corresponding events are fired. Property
044 * {@code pollIntervalMillis} defaults to {@code 250ms}.</p>
045 *
046 * <p><b>Note:</b><br/>
047 * {@code TaskEvent}s of type {@code STARTED} and {@code ENDED} are fired by the
048 * thread executing the task's operation. Since tasks are monitored
049 * asynchronously, {@code TaskEvent}s of type {@code CHANGED_STATE} are fired by
050 * the monitor thread, not by the thread executing the task's operation. Make
051 * sure {@code TaskListener} implementations are prepared for being notified
052 * by a different thread than the one executing a task's operation.</p>
053 *
054 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
055 * @version $JDTAUS: DefaultTaskMonitor.java 8787 2012-12-03 02:13:32Z schulte $
056 *
057 * @see org.jdtaus.core.container.Container
058 */
059public class DefaultTaskMonitor implements TaskMonitor
060{
061    //--Constructors------------------------------------------------------------
062
063// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausConstructors
064    // This section is managed by jdtaus-container-mojo.
065
066    /** Standard implementation constructor <code>org.jdtaus.core.monitor.ri.DefaultTaskMonitor</code>. */
067    public DefaultTaskMonitor()
068    {
069        super();
070    }
071
072// </editor-fold>//GEN-END:jdtausConstructors
073
074    //------------------------------------------------------------Constructors--
075    //--Dependencies------------------------------------------------------------
076
077// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausDependencies
078    // This section is managed by jdtaus-container-mojo.
079
080    /**
081     * Gets the configured <code>Logger</code> implementation.
082     *
083     * @return The configured <code>Logger</code> implementation.
084     */
085    private Logger getLogger()
086    {
087        return (Logger) ContainerFactory.getContainer().
088            getDependency( this, "Logger" );
089
090    }
091
092    /**
093     * Gets the configured <code>TaskListener</code> implementation.
094     *
095     * @return The configured <code>TaskListener</code> implementation.
096     */
097    private TaskListener[] getTaskListener()
098    {
099        return (TaskListener[]) ContainerFactory.getContainer().
100            getDependency( this, "TaskListener" );
101
102    }
103
104    /**
105     * Gets the configured <code>Locale</code> implementation.
106     *
107     * @return The configured <code>Locale</code> implementation.
108     */
109    private Locale getLocale()
110    {
111        return (Locale) ContainerFactory.getContainer().
112            getDependency( this, "Locale" );
113
114    }
115
116// </editor-fold>//GEN-END:jdtausDependencies
117
118    //------------------------------------------------------------Dependencies--
119    //--Properties--------------------------------------------------------------
120
121// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausProperties
122    // This section is managed by jdtaus-container-mojo.
123
124    /**
125     * Gets the value of property <code>defaultPollIntervalMillis</code>.
126     *
127     * @return Default number of milliseconds per poll interval.
128     */
129    private java.lang.Long getDefaultPollIntervalMillis()
130    {
131        return (java.lang.Long) ContainerFactory.getContainer().
132            getProperty( this, "defaultPollIntervalMillis" );
133
134    }
135
136// </editor-fold>//GEN-END:jdtausProperties
137
138    //--------------------------------------------------------------Properties--
139    //--TaskEventSource---------------------------------------------------------
140
141    public void addTaskListener( final TaskListener listener )
142    {
143        if ( listener == null )
144        {
145            throw new NullPointerException( "listener" );
146        }
147
148        this.taskListeners.add( TaskListener.class, listener );
149    }
150
151    public void removeTaskListener( final TaskListener listener )
152    {
153        if ( listener == null )
154        {
155            throw new NullPointerException( "listener" );
156        }
157
158        this.taskListeners.remove( TaskListener.class, listener );
159    }
160
161    public TaskListener[] getTaskListeners()
162    {
163        return (TaskListener[]) this.taskListeners.getListeners(
164            TaskListener.class );
165
166    }
167
168    //---------------------------------------------------------TaskEventSource--
169    //--TaskMonitor-------------------------------------------------------------
170
171    public void monitor( final Task task )
172    {
173        if ( task == null )
174        {
175            throw new NullPointerException( "task" );
176        }
177
178        synchronized ( this.stateMap )
179        {
180            this.fireTaskEvent( new TaskEvent( task, TaskEvent.STARTED ) );
181            this.createTaskState( task );
182            this.checkMonitorThread();
183        }
184    }
185
186    public void finish( final Task task )
187    {
188        if ( task == null )
189        {
190            throw new NullPointerException( "task" );
191        }
192
193        synchronized ( this.stateMap )
194        {
195            if ( this.changedState( task ) )
196            {
197                this.fireTaskEvent( new TaskEvent( task,
198                                                   TaskEvent.CHANGED_STATE ) );
199
200            }
201
202            this.removeTaskState( task );
203            this.fireTaskEvent( new TaskEvent( task, TaskEvent.ENDED ) );
204        }
205    }
206
207    //-------------------------------------------------------------TaskMonitor--
208    //--DefaultTaskMonitor------------------------------------------------------
209
210    /** List of {@code TaskListener}s. */
211    private final EventListenerList taskListeners = new EventListenerList();
212
213    /** The thread monitoring tasks. */
214    private MonitorThread monitorThread;
215
216    /** Maps {@code Task}s to corresponding {@code TaskState} instances. */
217    private final Map stateMap = new HashMap( 1000 );
218
219    /** Number of milliseconds per poll interval. */
220    private Long pollIntervalMillis;
221
222    /**
223     * Creates a new {@code DefaultTaskMonitor} instance taking the
224     * milliseconds of one period.
225     *
226     * @param pollIntervalMillis the number of milliseconds per poll interval.
227     */
228    public DefaultTaskMonitor( final long pollIntervalMillis )
229    {
230        if ( pollIntervalMillis > 0L )
231        {
232            this.pollIntervalMillis = new Long( pollIntervalMillis );
233        }
234    }
235
236    /**
237     * Gets the value of property {@code pollIntervalMillis}.
238     *
239     * @return the number of milliseconds per poll interval.
240     */
241    private long getPollIntervalMillis()
242    {
243        if ( this.pollIntervalMillis == null )
244        {
245            this.pollIntervalMillis = this.getDefaultPollIntervalMillis();
246        }
247
248        return this.pollIntervalMillis.longValue();
249    }
250
251    /** Caches the state of a task. */
252    private static final class TaskState
253    {
254
255        boolean indeterminate;
256
257        boolean cancelable;
258
259        boolean cancelled;
260
261        int minimum;
262
263        int maximum;
264
265        int progress;
266
267        Message progressDescription;
268
269        private TaskState()
270        {
271            super();
272        }
273
274    }
275
276    /** Thread monitoring all currently running {@code Task}s for changes. */
277    private final class MonitorThread extends Thread
278    {
279
280        /** Milliseconds per poll interval. */
281        private final long pollIntervalMillis;
282
283        /** Creates a new {@code MonitorThread} instance. */
284        private MonitorThread( final long pollIntervalMillis )
285        {
286            super( "DefaultTaskMonitor" );
287            this.pollIntervalMillis = pollIntervalMillis;
288        }
289
290        /** {@inheritDoc} */
291        public void run()
292        {
293            boolean monitoring = true;
294
295            while ( monitoring )
296            {
297                try
298                {
299                    Thread.sleep( this.pollIntervalMillis );
300                    monitoring = this.checkTasks();
301                }
302                catch ( final InterruptedException e )
303                {
304                    monitoring = this.checkTasks();
305                }
306            }
307        }
308
309        public void start()
310        {
311            super.start();
312
313            if ( getLogger().isDebugEnabled() )
314            {
315                getLogger().debug( getThreadStartedMessage(
316                    getLocale(), new Long( this.pollIntervalMillis ) ) );
317
318            }
319        }
320
321        /**
322         * Checks the state of all currently running tasks for changes and
323         * fires corresponding events.
324         */
325        private boolean checkTasks()
326        {
327            synchronized ( DefaultTaskMonitor.this.stateMap )
328            {
329                for ( final Iterator it = DefaultTaskMonitor.this.stateMap.
330                    keySet().iterator(); it.hasNext(); )
331                {
332                    final Task task = (Task) it.next();
333                    if ( changedState( task ) )
334                    {
335                        fireTaskEvent( new TaskEvent(
336                            task, TaskEvent.CHANGED_STATE ) );
337
338                    }
339                }
340
341                return !DefaultTaskMonitor.this.stateMap.isEmpty();
342            }
343        }
344
345    }
346
347    /**
348     * Gets the monitor thread.
349     *
350     * @return the monitor thread.
351     */
352    private synchronized void checkMonitorThread()
353    {
354        if ( this.monitorThread == null
355             || !this.monitorThread.isAlive() )
356        {
357            this.monitorThread =
358                new MonitorThread( this.getPollIntervalMillis() );
359
360            this.monitorThread.start();
361        }
362    }
363
364    /**
365     * Notifies all registered {@code TaskListener}s about {@code TaskEvent}s.
366     *
367     * @param e The event to be provided to the listeners.
368     */
369    private void fireTaskEvent( final TaskEvent e )
370    {
371        if ( e == null )
372        {
373            throw new NullPointerException( "e" );
374        }
375
376
377        final Object[] listeners = this.taskListeners.getListenerList();
378        for ( int i = listeners.length - 2; i >= 0; i -= 2 )
379        {
380            if ( listeners[i] == TaskListener.class )
381            {
382                ( (TaskListener) listeners[i + 1] ).onTaskEvent( e );
383            }
384        }
385
386        final TaskListener[] taskListener = this.getTaskListener();
387        for ( int i = taskListener.length - 1; i >= 0; i-- )
388        {
389            taskListener[i].onTaskEvent( e );
390        }
391    }
392
393    /**
394     * Caches the state of a {@code Task}.
395     *
396     * @param task the task to cache state for.
397     *
398     * @throws NullPointerException if {@code task} is {@code null}.
399     * @throws IllegalStateException if the cache already holds state for
400     * {@code task}.
401     */
402    private void createTaskState( final Task task )
403    {
404        final TaskState state = new TaskState();
405        state.cancelable = task.isCancelable();
406        state.indeterminate = task.isIndeterminate();
407        state.cancelled = state.cancelable
408                          ? task.isCancelled()
409                          : false;
410
411        state.progressDescription = task.getProgressDescription();
412
413        if ( state.indeterminate )
414        {
415            state.maximum = Integer.MIN_VALUE;
416            state.minimum = Integer.MIN_VALUE;
417            state.progress = Integer.MIN_VALUE;
418        }
419        else
420        {
421            state.maximum = task.getMaximum();
422            state.minimum = task.getMinimum();
423            state.progress = task.getProgress();
424        }
425
426        if ( this.stateMap.put( task, state ) != null )
427        {
428            throw new IllegalStateException( this.getTaskAlreadyStartedMessage(
429                this.getLocale(),
430                task.getDescription().getText( this.getLocale() ),
431                new Date( task.getTimestamp() ) ) );
432
433        }
434    }
435
436    /**
437     * Removes the cached state of a {@code Task}.
438     *
439     * @param task the task to remove the cached state of.
440     *
441     * @throws NullPointerException if {@code task} is {@code null}.
442     */
443    private void removeTaskState( final Task task )
444    {
445        if ( task == null )
446        {
447            throw new NullPointerException( "task" );
448        }
449
450        this.stateMap.remove( task );
451    }
452
453    /**
454     * Checks the state of a given task for changes.
455     *
456     * @param task the task to check for state changes.
457     *
458     * @return {@code true} if the state of {@code task} changed since the last
459     * time this method got called; {@code false} if the state did not change.
460     *
461     * @throws NullPointerException if {@code task} is {@code null}.
462     * @throws IllegalStateException if no cached state exists for {@code task}.
463     */
464    private boolean changedState( final Task task )
465    {
466        if ( task == null )
467        {
468            throw new NullPointerException( "task" );
469        }
470
471
472        boolean changedState = false;
473        final TaskState state = (TaskState) this.stateMap.get( task );
474
475        if ( state == null )
476        {
477            throw new IllegalStateException();
478        }
479
480        if ( state.indeterminate )
481        {
482            state.indeterminate = task.isIndeterminate();
483            if ( !state.indeterminate )
484            {
485                state.minimum = task.getMinimum();
486                state.maximum = task.getMaximum();
487                state.progress = task.getProgress();
488                changedState = true;
489            }
490        }
491        else
492        {
493            state.indeterminate = task.isIndeterminate();
494            if ( state.indeterminate )
495            {
496                changedState = true;
497            }
498            else
499            {
500                if ( state.minimum != task.getMinimum() )
501                {
502                    state.minimum = task.getMinimum();
503                    changedState = true;
504                }
505                if ( state.maximum != task.getMaximum() )
506                {
507                    state.maximum = task.getMaximum();
508                    changedState = true;
509                }
510                if ( state.progress != task.getProgress() )
511                {
512                    state.progress = task.getProgress();
513                    changedState = true;
514                }
515            }
516        }
517
518        if ( state.cancelable )
519        {
520            state.cancelable = task.isCancelable();
521            if ( !state.cancelable )
522            {
523                changedState = true;
524            }
525            else
526            {
527                if ( state.cancelled != task.isCancelled() )
528                {
529                    state.cancelled = task.isCancelled();
530                    changedState = true;
531                }
532            }
533        }
534        else
535        {
536            state.cancelable = task.isCancelable();
537            if ( !state.cancelable )
538            {
539                state.cancelled = false;
540                changedState = true;
541            }
542        }
543
544        if ( state.progressDescription != task.getProgressDescription() )
545        {
546            state.progressDescription = task.getProgressDescription();
547            changedState = true;
548        }
549        else if ( state.progressDescription != null
550                  && !state.progressDescription.getText( this.getLocale() ).
551            equals( task.getProgressDescription().getText(
552            this.getLocale() ) ) )
553        {
554            changedState = true;
555        }
556
557        return changedState;
558    }
559
560    //------------------------------------------------------DefaultTaskMonitor--
561    //--Messages----------------------------------------------------------------
562
563// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausMessages
564    // This section is managed by jdtaus-container-mojo.
565
566    /**
567     * Gets the text of message <code>threadStarted</code>.
568     * <blockquote><pre>Neuen Thread gestartet. Abtastperiode {0,number}ms.</pre></blockquote>
569     * <blockquote><pre>New thread started. Period {0,number}ms.</pre></blockquote>
570     *
571     * @param locale The locale of the message instance to return.
572     * @param periodMillis Period of the started thread.
573     *
574     * @return Information about a started thread.
575     */
576    private String getThreadStartedMessage( final Locale locale,
577            final java.lang.Number periodMillis )
578    {
579        return ContainerFactory.getContainer().
580            getMessage( this, "threadStarted", locale,
581                new Object[]
582                {
583                    periodMillis
584                });
585
586    }
587
588    /**
589     * Gets the text of message <code>taskAlreadyStarted</code>.
590     * <blockquote><pre>Ein Vorgang mit Beschreibung {0} wurde bereits um {1,time,long} gestartet.</pre></blockquote>
591     * <blockquote><pre>A task with description {0} already has been started at {1,time,long}.</pre></blockquote>
592     *
593     * @param locale The locale of the message instance to return.
594     * @param taskDescription Description of the already running task.
595     * @param startTime Time the already running task got started.
596     *
597     * @return Information about an already running task.
598     */
599    private String getTaskAlreadyStartedMessage( final Locale locale,
600            final java.lang.String taskDescription,
601            final java.util.Date startTime )
602    {
603        return ContainerFactory.getContainer().
604            getMessage( this, "taskAlreadyStarted", locale,
605                new Object[]
606                {
607                    taskDescription,
608                    startTime
609                });
610
611    }
612
613// </editor-fold>//GEN-END:jdtausMessages
614
615    //----------------------------------------------------------------Messages--
616}