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}