View Javadoc

1   /*
2    *  jDTAUS Core RI Task Monitor
3    *  Copyright (C) 2005 Christian Schulte
4    *  <cs@schulte.it>
5    *
6    *  This library is free software; you can redistribute it and/or
7    *  modify it under the terms of the GNU Lesser General Public
8    *  License as published by the Free Software Foundation; either
9    *  version 2.1 of the License, or any later version.
10   *
11   *  This library is distributed in the hope that it will be useful,
12   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   *  Lesser General Public License for more details.
15   *
16   *  You should have received a copy of the GNU Lesser General Public
17   *  License along with this library; if not, write to the Free Software
18   *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19   *
20   */
21  package org.jdtaus.core.monitor.ri;
22  
23  import java.util.Date;
24  import java.util.HashMap;
25  import java.util.Iterator;
26  import java.util.Locale;
27  import java.util.Map;
28  import javax.swing.event.EventListenerList;
29  import org.jdtaus.core.container.ContainerFactory;
30  import org.jdtaus.core.logging.spi.Logger;
31  import org.jdtaus.core.monitor.Task;
32  import org.jdtaus.core.monitor.TaskEvent;
33  import org.jdtaus.core.monitor.TaskListener;
34  import org.jdtaus.core.monitor.spi.TaskMonitor;
35  import org.jdtaus.core.text.Message;
36  
37  /**
38   * jDTAUS Core SPI {@code TaskMonitor} reference implementation.
39   * <p>The reference implementation uses a thread checking the state of all tasks
40   * in the system periodically which is started upon initialization and runs
41   * endlessly. Monitoring is controlled by property {@code pollIntervalMillis}
42   * specifying the milliseconds of one period. Each time a period ends, tasks
43   * are checked for state changes and corresponding events are fired. Property
44   * {@code pollIntervalMillis} defaults to {@code 250ms}.</p>
45   *
46   * <p><b>Note:</b><br/>
47   * {@code TaskEvent}s of type {@code STARTED} and {@code ENDED} are fired by the
48   * thread executing the task's operation. Since tasks are monitored
49   * asynchronously, {@code TaskEvent}s of type {@code CHANGED_STATE} are fired by
50   * the monitor thread, not by the thread executing the task's operation. Make
51   * sure {@code TaskListener} implementations are prepared for being notified
52   * by a different thread than the one executing a task's operation.</p>
53   *
54   * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
55   * @version $JDTAUS: DefaultTaskMonitor.java 8787 2012-12-03 02:13:32Z schulte $
56   *
57   * @see org.jdtaus.core.container.Container
58   */
59  public class DefaultTaskMonitor implements TaskMonitor
60  {
61      //--Constructors------------------------------------------------------------
62  
63  // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausConstructors
64      // This section is managed by jdtaus-container-mojo.
65  
66      /** Standard implementation constructor <code>org.jdtaus.core.monitor.ri.DefaultTaskMonitor</code>. */
67      public DefaultTaskMonitor()
68      {
69          super();
70      }
71  
72  // </editor-fold>//GEN-END:jdtausConstructors
73  
74      //------------------------------------------------------------Constructors--
75      //--Dependencies------------------------------------------------------------
76  
77  // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausDependencies
78      // This section is managed by jdtaus-container-mojo.
79  
80      /**
81       * Gets the configured <code>Logger</code> implementation.
82       *
83       * @return The configured <code>Logger</code> implementation.
84       */
85      private Logger getLogger()
86      {
87          return (Logger) ContainerFactory.getContainer().
88              getDependency( this, "Logger" );
89  
90      }
91  
92      /**
93       * Gets the configured <code>TaskListener</code> implementation.
94       *
95       * @return The configured <code>TaskListener</code> implementation.
96       */
97      private TaskListener[] getTaskListener()
98      {
99          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 }