Source code: org/gui4j/core/Gui4jThreadManager.java
1 package org.gui4j.core;
2
3 import java.io.Serializable;
4 import java.lang.reflect.InvocationTargetException;
5 import java.util.LinkedList;
6 import java.util.Map;
7
8 import javax.swing.SwingUtilities;
9
10 import org.apache.commons.logging.Log;
11 import org.apache.commons.logging.LogFactory;
12 import org.gui4j.Gui4jCallBase;
13 import org.gui4j.Gui4jGetValue;
14 import org.gui4j.exception.ErrorTags;
15 import org.gui4j.exception.Gui4jUncheckedException;
16
17
18 /**
19 * The Thread Manager deals with worker threads used to perform GUI actions. The intention is to take a thread
20 * from a pool, use this thread to perform the necessary action and then put the thread back into the pool.
21 */
22 public final class Gui4jThreadManager implements ErrorTags, Serializable
23 {
24 protected static Log mLogger = LogFactory.getLog(Gui4jThreadManager.class);
25
26 private LinkedList mThreadPool = new LinkedList();
27 private LinkedList mWorkPackages = new LinkedList();
28 protected boolean mHighPriorityThreadActive;
29 protected final Gui4jInternal mGui4j;
30 private int mFreeThreadCount;
31 private int mWorkPackageCount;
32 private int mWorkerCount;
33 private int mMaxNumberOfWorkerThreads;
34 private boolean mUseWorkerThreads;
35
36 /**
37 * Constructor for Gui4jThreadManager.
38 * @param gui4j
39 * @param numberOfWorkerThreads
40 */
41 private Gui4jThreadManager(Gui4jInternal gui4j, int numberOfWorkerThreads)
42 {
43 super();
44 mGui4j = gui4j;
45 setNumberOfWorkerThreads(numberOfWorkerThreads);
46 }
47
48 /**
49 * @param gui4j
50 * @param numberOfWorkerThreads
51 * @return a new instance of the Thread Manager. This method is used only by the class <code>Gui4j</code>.
52 */
53 public static Gui4jThreadManager getNewInstance(Gui4jInternal gui4j, int numberOfWorkerThreads)
54 {
55 return new Gui4jThreadManager(gui4j, numberOfWorkerThreads);
56 }
57
58 /**
59 * Sets the maximum number of worker threads. The value <code>-1</code> represents an unlimited number
60 * of threads. Value <code>0</code> implies always using the Swing GUI Thread. Any number greater than
61 * <code>0</code> really sets the maximum number of worker threads. If there is work to do and no worker
62 * is free, then the work is put into a FIFO queue and handled when worker gets free.
63 * @param numberOfWorkerThreads
64 */
65 public void setNumberOfWorkerThreads(int numberOfWorkerThreads)
66 {
67 // it is not allowd to dynamically change the number of worker threads
68 assert mWorkerCount == 0;
69
70 mMaxNumberOfWorkerThreads = numberOfWorkerThreads;
71 mUseWorkerThreads = mMaxNumberOfWorkerThreads != 0;
72 }
73
74 /**
75 * Performs the given work. Dependant of the number of maximum worker threads, the work is either
76 * performed in the same thread, or by a new worker, or put into a FIFO queue.
77 * @param gui4jController
78 * @param work
79 * @param paramMap
80 */
81 public void performWork(final Gui4jCallBase gui4jController, final Gui4jGetValue[] work, final Map paramMap)
82 {
83 performWork(gui4jController, work, paramMap, null);
84 }
85
86 /**
87 * Performs the given work. Dependant of the number of maximum worker threads, the work is either
88 * performed in the same thread, or by a new worker, or put into a FIFO queue.
89 * @param gui4jController
90 * @param work
91 * @param paramMap
92 * @param forceExecutionInCurrentThread
93 */
94 public void performWork(
95 final Gui4jCallBase gui4jController,
96 final Gui4jGetValue[] work,
97 final Map paramMap,
98 boolean forceExecutionInCurrentThread)
99 {
100 performWork(gui4jController, work, paramMap, null, forceExecutionInCurrentThread);
101 }
102
103 public void performWork(
104 final Gui4jCallBase gui4jController,
105 final Gui4jGetValue[] work,
106 final Map paramMap,
107 final Gui4jComponentInstance actionHandler)
108 {
109 performWork(gui4jController, work, paramMap, actionHandler, false);
110 }
111
112 public void performPriorityWork(
113 final Gui4jCallBase gui4jController,
114 final Gui4jGetValue[] work,
115 final Map paramMap,
116 final Gui4jComponentInstance actionHandler)
117 {
118 performPriorityWork(gui4jController, work, paramMap, actionHandler, false);
119 }
120
121 /**
122 * Performs the given work. Dependant of the number of maximum worker threads, the work is either
123 * performed in the same thread, or by a new worker, or put into a FIFO queue.
124 * @param gui4jController
125 * @param work
126 * @param paramMap
127 * @param actionHandler
128 * @param forceExecutionInCurrentThread
129 */
130 public void performWork(
131 final Gui4jCallBase gui4jController,
132 final Gui4jGetValue[] work,
133 final Map paramMap,
134 final Gui4jComponentInstance actionHandler,
135 boolean forceExecutionInCurrentThread)
136 {
137 performWork(gui4jController, work, paramMap, actionHandler, forceExecutionInCurrentThread, false);
138 }
139
140 /**
141 * Performs the given work. Dependant of the number of maximum worker threads, the work is either
142 * performed in the same thread, or by a new worker, or put into a FIFO queue.
143 * @param gui4jController
144 * @param work
145 * @param paramMap
146 * @param actionHandler
147 * @param forceExecutionInCurrentThread
148 */
149 public void performPriorityWork(
150 final Gui4jCallBase gui4jController,
151 final Gui4jGetValue[] work,
152 final Map paramMap,
153 final Gui4jComponentInstance actionHandler,
154 boolean forceExecutionInCurrentThread)
155 {
156 performWork(gui4jController, work, paramMap, actionHandler, forceExecutionInCurrentThread, true);
157 }
158
159 /**
160 * Performs the given work. Dependant of the number of maximum worker threads, the work is either
161 * performed in the same thread, or by a new worker, or put into a FIFO queue.
162 * @param gui4jController
163 * @param work
164 * @param paramMap
165 * @param actionHandler
166 * @param forceExecutionInCurrentThread
167 * @param isHighPriorityThread
168 */
169 private void performWork(
170 final Gui4jCallBase gui4jController,
171 final Gui4jGetValue[] work,
172 final Map paramMap,
173 final Gui4jComponentInstance actionHandler,
174 boolean forceExecutionInCurrentThread,
175 final boolean isHighPriorityThread)
176 {
177 if (mUseWorkerThreads && SwingUtilities.isEventDispatchThread() && !forceExecutionInCurrentThread)
178 {
179 final InvokerCallStack callStack = mGui4j.traceWorkerInvocation() ? new InvokerCallStack(
180 Thread.currentThread().getName()) : null;
181 // use another thread to perform task
182 Runnable run = new Runnable()
183 {
184 public void run()
185 {
186 WorkerThread thread = getWorkerThread(
187 gui4jController,
188 work,
189 paramMap,
190 actionHandler,
191 callStack,
192 isHighPriorityThread);
193 if (thread != null)
194 {
195 // mLogger.trace("Notifying " + thread);
196 synchronized (thread)
197 {
198 thread.notify();
199 }
200 }
201 else
202 {
203 mLogger.debug("Currently no free worker, work put on stack");
204 }
205 }
206 };
207 SwingUtilities.invokeLater(run);
208 }
209 else
210 {
211 for (int i = 0; i < work.length; i++)
212 {
213 if (work[i] != null)
214 {
215 work[i].getValue(gui4jController, paramMap, null);
216 }
217 }
218 }
219 }
220
221 /**
222 * Perform the given work.
223 * @see org.gui4j.core.Gui4jThreadManager#performWork(Gui4jCallBase,Gui4jGetValue[],Map)
224 * @param gui4jController
225 * @param action
226 * @param paramMap
227 */
228 public void performWork(Gui4jCallBase gui4jController, Gui4jGetValue action, Map paramMap)
229 {
230 Gui4jGetValue[] work = { action };
231 performWork(gui4jController, work, paramMap);
232 }
233
234 /**
235 * Perform the given work.
236 * @see org.gui4j.core.Gui4jThreadManager#performWork(Gui4jCallBase,Gui4jGetValue[],Map)
237 * @param gui4jController
238 * @param action
239 * @param paramMap
240 * @param forceExecutionInCurrentThread
241 */
242 public void performWork(
243 Gui4jCallBase gui4jController,
244 Gui4jGetValue action,
245 Map paramMap,
246 boolean forceExecutionInCurrentThread)
247 {
248 Gui4jGetValue[] work = { action };
249 performWork(gui4jController, work, paramMap, forceExecutionInCurrentThread);
250 }
251
252 protected WorkerThread getWorkerThread(
253 Gui4jCallBase gui4jController,
254 Gui4jGetValue[] work,
255 Map paramMap,
256 Gui4jComponentInstance actionHandler,
257 InvokerCallStack callStack,
258 boolean isHighPriorityThread)
259 {
260 WorkerThread worker;
261 synchronized (mThreadPool)
262 {
263 if (!mHighPriorityThreadActive && mFreeThreadCount > 0)
264 {
265 mFreeThreadCount--;
266 worker = (WorkerThread) mThreadPool.removeFirst();
267 worker.handleWork(
268 gui4jController,
269 work,
270 paramMap,
271 actionHandler,
272 callStack,
273 isHighPriorityThread);
274 }
275 else
276 {
277 if (mWorkerCount == mMaxNumberOfWorkerThreads || mHighPriorityThreadActive)
278 {
279 mWorkPackages.add(new WorkPackage(
280 gui4jController,
281 work,
282 paramMap,
283 actionHandler,
284 callStack,
285 isHighPriorityThread));
286 mWorkPackageCount++;
287 worker = null;
288 }
289 else
290 {
291 worker = new WorkerThread(++mWorkerCount);
292 worker.start();
293 worker.handleWork(
294 gui4jController,
295 work,
296 paramMap,
297 actionHandler,
298 callStack,
299 isHighPriorityThread);
300 }
301 }
302 }
303 return worker;
304 }
305
306 protected void putThreadBackIntoPool(WorkerThread thread)
307 {
308 thread.cleanUp();
309 synchronized (mThreadPool)
310 {
311 if (mWorkPackageCount > 0)
312 {
313 WorkPackage workPackage = (WorkPackage) mWorkPackages.removeFirst();
314 mWorkPackageCount--;
315 thread.handleWork(
316 workPackage.mGui4jController,
317 workPackage.mWork,
318 workPackage.mParamMap,
319 workPackage.mActionHandler,
320 workPackage.mCallStack,
321 workPackage.mIsHighPriorityThread);
322 }
323 else
324 {
325 mFreeThreadCount++;
326 mThreadPool.add(thread);
327 }
328 }
329 }
330
331 public void clearThreadsInPool()
332 {
333 synchronized (mThreadPool)
334 {
335 mThreadPool.clear();
336 mFreeThreadCount = 0;
337 }
338 }
339
340 private static class WorkPackage
341 {
342 public final Gui4jCallBase mGui4jController;
343 public final Gui4jGetValue[] mWork;
344 public final Gui4jComponentInstance mActionHandler;
345 public final Map mParamMap;
346 public final InvokerCallStack mCallStack;
347 public final boolean mIsHighPriorityThread;
348
349 public WorkPackage(
350 Gui4jCallBase gui4jController,
351 Gui4jGetValue[] work,
352 Map paramMap,
353 Gui4jComponentInstance actionHandler,
354 InvokerCallStack callStack,
355 boolean isHighPriorityThread)
356 {
357 mGui4jController = gui4jController;
358 mWork = work;
359 mParamMap = paramMap;
360 mActionHandler = actionHandler;
361 mCallStack = callStack;
362 mIsHighPriorityThread = isHighPriorityThread;
363 }
364 }
365
366 public class WorkerThread extends Thread
367 {
368
369 private Gui4jCallBase mGui4jController;
370 private Gui4jGetValue[] mWork;
371 private Gui4jComponentInstance mActionHandler;
372 private Map mParamMap;
373 private boolean mWorkAvailable;
374 private final int mId;
375 private InvokerCallStack mCallStack;
376 private boolean mIsHighPriorityThread;
377
378 public WorkerThread(int n)
379 {
380 mId = n;
381 mLogger.debug(toString() + ": created");
382 setName(toString());
383 }
384
385 public Throwable getCallStack()
386 {
387 return mCallStack;
388 }
389
390 public String toString()
391 {
392 return "Worker " + mId;
393 }
394
395 protected void handleWork(
396 Gui4jCallBase gui4jController,
397 Gui4jGetValue[] work,
398 Map paramMap,
399 Gui4jComponentInstance actionHandler,
400 InvokerCallStack callStack,
401 boolean isHighPriorityThread)
402 {
403 mGui4jController = gui4jController;
404 mWork = work;
405 mParamMap = paramMap;
406 mActionHandler = actionHandler;
407 mWorkAvailable = true;
408 mCallStack = callStack;
409 mIsHighPriorityThread = isHighPriorityThread;
410 }
411
412 public void cleanUp()
413 {
414 mGui4jController = null;
415 mWork = null;
416 mParamMap = null;
417 mActionHandler = null;
418 mCallStack = null;
419 mIsHighPriorityThread = false;
420 }
421
422 public void run()
423 {
424 while (true)
425 {
426 if (mWorkAvailable && !mHighPriorityThreadActive)
427 {
428 try
429 {
430 if (mIsHighPriorityThread)
431 {
432 mHighPriorityThreadActive = true;
433 }
434 // mLogger.trace(toString() + ": performing work");
435 for (int i = 0; i < mWork.length; i++)
436 {
437 final Gui4jGetValue work = mWork[i];
438 if (work == null)
439 {
440 continue;
441 }
442 // mLogger.trace(toString() + ": performing work " +work);
443 try
444 {
445 work.getValueNoErrorChecking(mGui4jController, mParamMap, null);
446 // Falls der ActionHandler angegeben wurde, rufen wir
447 // (nur) nach dem ersten Aufruf die <code>handleSuccess</code>
448 // Methode auf.
449 // Beim Edit-Feld wird damit im Ok-Fall der Inhalt nochmals
450 // angezeigt. Außerdem kann damit Validierung gemacht werden.
451 if (i == 0 && mActionHandler != null)
452 {
453 mActionHandler.handleSuccess();
454 }
455 }
456 catch (Throwable t)
457 {
458 // Analog zum Ok-Fall, rufen wir im Fehlerfall nach dem ersten
459 // Aufruf die <code>handleException</code> Methode auf. Damit
460 // kann beispielsweise Validierung gemacht werden.
461 if (i == 0 && mActionHandler != null)
462 {
463 mActionHandler.handleException(t);
464 }
465 else
466 {
467 // Falls kein ActionHandler definiert wurde, oder
468 // es sich nicht um den ersten Aufruf handelt,
469 // erfolgt die normale Fehlerbehandlung.
470 mGui4j.handleException(mGui4jController, t, null);
471 }
472 }
473 }
474 // mLogger.trace(toString() + ": work finished");
475 mWorkAvailable = false;
476 }
477 finally
478 {
479 if (mIsHighPriorityThread)
480 {
481 mHighPriorityThreadActive = false;
482 mIsHighPriorityThread = false;
483 }
484 }
485 putThreadBackIntoPool(this);
486 }
487 if (!mWorkAvailable || mHighPriorityThreadActive)
488 {
489 try
490 {
491 synchronized (this)
492 {
493 wait();
494 }
495 // mLogger.trace(toString() + ": got notified");
496 }
497 catch (InterruptedException e)
498 {
499 mLogger.error("Thread interrupted", e);
500 return;
501 }
502 }
503 }
504 }
505
506 }
507
508 private static class InvokerCallStack extends Throwable
509 {
510 public InvokerCallStack(String threadName)
511 {
512 super(threadName);
513 }
514
515 public String toString()
516 {
517 return "Thread [" + getMessage() + "]";
518 }
519
520 }
521
522 /**
523 * Insertes the given <code>Runnable</code> into the task queue of the GUI Thread. If the calling thread
524 * is the GUI thread, this call immediately returns. If the calling thread is not the GUI thread, this
525 * call does not return until the GUI thread has completed the task.
526 * @param run task to be scheduled in the GUI thread
527 */
528 public static void executeInSwingThreadAndWait(Runnable run)
529 {
530 try
531 {
532 if (SwingUtilities.isEventDispatchThread())
533 {
534 // run.run();
535 SwingUtilities.invokeLater(run);
536 }
537 else
538 {
539 SwingUtilities.invokeAndWait(run);
540 }
541 }
542 catch (InterruptedException ex)
543 {
544 mLogger.warn("Interrupted", ex);
545 }
546 catch (InvocationTargetException ex)
547 {
548 Gui4jReflectionManager.handleInvocationTargetException(ex);
549 throw new Gui4jUncheckedException.ProgrammingError(
550 PROGRAMMING_ERROR_invocation_target_exception,
551 ex);
552 }
553 }
554
555 /**
556 * The given task is executed in the GUI thread as soon as possible. There are two cases: <br>
557 * If this thread is not the GUI thread, the given <code>Runnable</code> is inserted into the task queue
558 * of the GUI thread. This call then returns immediately without waiting for the scheduled task to be
559 * finished. <br>
560 * If this thread is the GUI thread itself, the given task is executed immediately and synchronously, i.e.
561 * this call will not return until the task is completed.
562 * @param run task to be scheduled in the GUI thread
563 */
564 public static void executeInSwingThreadAndContinue(final Runnable run)
565 {
566 if (SwingUtilities.isEventDispatchThread())
567 {
568 run.run();
569 }
570 else
571 {
572 SwingUtilities.invokeLater(run);
573 }
574 }
575 }