001: package org.gui4j.core;
002:
003: import java.awt.EventQueue;
004: import java.io.Serializable;
005: import java.lang.reflect.InvocationTargetException;
006: import java.util.LinkedList;
007: import java.util.Map;
008:
009: import javax.swing.SwingUtilities;
010:
011: import org.apache.commons.logging.Log;
012: import org.apache.commons.logging.LogFactory;
013:
014: import org.gui4j.Gui4jCallBase;
015: import org.gui4j.Gui4jGetValue;
016: import org.gui4j.exception.ErrorTags;
017: import org.gui4j.exception.Gui4jUncheckedException;
018:
019: /**
020: * The Thread Manager deals with worker threads used to perform GUI actions. The
021: * intention is to take a thread from a pool, use this thread to perform the
022: * necessary action and then put the thread back into the pool.
023: */
024: public final class Gui4jThreadManager implements ErrorTags,
025: Serializable {
026: protected static Log mLogger = LogFactory
027: .getLog(Gui4jThreadManager.class);
028:
029: private LinkedList mWorkPackagesNormal = new LinkedList();
030: private LinkedList mWorkPackagesHighPriority = new LinkedList();
031: private Object mSyncObjectCount = new Object();
032: private Object mSyncObjectWorkerWait = new Object();
033: protected final Gui4jInternal mGui4j;
034: private int mWorkPackageCountWaitingNormal;
035: private int mWorkPackageCountWaitingHighPriority;
036: private int mWorkPackageCountRunningNormal;
037: private int mWorkPackageCountRunningHighPriority;
038: private int mWorkerCount;
039: private int mMaxWorkerId;
040: private int mMaxNumberOfWorkerThreads;
041: private boolean mUseWorkerThreads;
042: private boolean mShutdownThreads;
043:
044: /**
045: * Constructor for Gui4jThreadManager.
046: *
047: * @param gui4j
048: * @param numberOfWorkerThreads
049: */
050: private Gui4jThreadManager(Gui4jInternal gui4j,
051: int numberOfWorkerThreads) {
052: super ();
053: mGui4j = gui4j;
054: setNumberOfWorkerThreads(numberOfWorkerThreads);
055: }
056:
057: /**
058: * @param gui4j
059: * @param numberOfWorkerThreads
060: * @return a new instance of the Thread Manager. This method is used only by
061: * the class <code>Gui4j</code>.
062: */
063: public static Gui4jThreadManager getNewInstance(
064: Gui4jInternal gui4j, int numberOfWorkerThreads) {
065: return new Gui4jThreadManager(gui4j, numberOfWorkerThreads);
066: }
067:
068: /**
069: * Sets the maximum number of worker threads. The value <code>-1</code>
070: * represents an unlimited number of threads. Value <code>0</code> implies
071: * always using the Swing GUI Thread. Any number greater than <code>0</code>
072: * really sets the maximum number of worker threads. If there is work to do
073: * and no worker is free, then the work is put into a FIFO queue and handled
074: * when worker gets free.
075: *
076: * @param numberOfWorkerThreads
077: */
078: public void setNumberOfWorkerThreads(int numberOfWorkerThreads) {
079: // it is not allowd to dynamically change the number of worker threads
080: assert mWorkerCount == 0;
081:
082: mMaxNumberOfWorkerThreads = numberOfWorkerThreads;
083: mUseWorkerThreads = mMaxNumberOfWorkerThreads != 0;
084: }
085:
086: /**
087: * Performs the given work. Dependant of the number of maximum worker
088: * threads, the work is either performed in the same thread, or by a new
089: * worker, or put into a FIFO queue.
090: *
091: * @param gui4jController
092: * @param work
093: * @param paramMap
094: */
095: public void performWork(final Gui4jCallBase gui4jController,
096: final Gui4jGetValue[] work, final Map paramMap) {
097: performWork(gui4jController, work, paramMap, null);
098: }
099:
100: /**
101: * Performs the given work. Dependant of the number of maximum worker
102: * threads, the work is either performed in the same thread, or by a new
103: * worker, or put into a FIFO queue.
104: *
105: * @param gui4jController
106: * @param work
107: * @param paramMap
108: * @param forceExecutionInCurrentThread
109: */
110: public void performWork(final Gui4jCallBase gui4jController,
111: final Gui4jGetValue[] work, final Map paramMap,
112: boolean forceExecutionInCurrentThread) {
113: performWork(gui4jController, work, paramMap, null,
114: forceExecutionInCurrentThread);
115: }
116:
117: public void performWork(final Gui4jCallBase gui4jController,
118: final Gui4jGetValue[] work, final Map paramMap,
119: final Gui4jComponentInstance componentInstance) {
120: performWork(gui4jController, work, paramMap, componentInstance,
121: false);
122: }
123:
124: public void performWorkHighPriority(
125: final Gui4jCallBase gui4jController,
126: final Gui4jGetValue[] work, final Map paramMap,
127: final Gui4jComponentInstance componentInstance) {
128: performWorkHighPriority(gui4jController, work, paramMap,
129: componentInstance, false);
130: }
131:
132: /**
133: * Performs the given work. Dependant of the number of maximum worker
134: * threads, the work is either performed in the same thread, or by a new
135: * worker, or put into a FIFO queue.
136: *
137: * @param gui4jController
138: * @param work
139: * @param paramMap
140: * @param componentInstance
141: * @param forceExecutionInCurrentThread
142: */
143: public void performWork(final Gui4jCallBase gui4jController,
144: final Gui4jGetValue[] work, final Map paramMap,
145: final Gui4jComponentInstance componentInstance,
146: boolean forceExecutionInCurrentThread) {
147: performWork(gui4jController, work, paramMap, componentInstance,
148: forceExecutionInCurrentThread, false);
149: }
150:
151: /**
152: * Performs the specified work and instructs the worker process to use the
153: * special success handling used to implement instant validation, for
154: * example.
155: *
156: * @param gui4jController
157: * @param work
158: * @param paramMap
159: * @param componentInstance
160: * @param forceExecutionInCurrentThread
161: */
162: public void performWorkSpecialSuccessHandling(
163: final Gui4jCallBase gui4jController,
164: final Gui4jGetValue[] work, final Map paramMap,
165: final Gui4jComponentInstance componentInstance,
166: boolean forceExecutionInCurrentThread) {
167: performWork(gui4jController, work, paramMap, componentInstance,
168: forceExecutionInCurrentThread, false, true);
169: }
170:
171: /**
172: * Performs the given work. Dependant of the number of maximum worker
173: * threads, the work is either performed in the same thread, or by a new
174: * worker, or put into a FIFO queue.
175: *
176: * Note (KKB, 12.7.2005): This method implicitly instructs the worker thread
177: * to perform special success handling as in
178: * {@link #performWorkSpecialSuccessHandling(Gui4jCallBase, Gui4jGetValue[], Map, Gui4jComponentInstance, boolean)}.
179: *
180: * @param gui4jController
181: * @param work
182: * @param paramMap
183: * @param componentInstance
184: * @param forceExecutionInCurrentThread
185: */
186: public void performWorkHighPriority(
187: final Gui4jCallBase gui4jController,
188: final Gui4jGetValue[] work, final Map paramMap,
189: final Gui4jComponentInstance componentInstance,
190: boolean forceExecutionInCurrentThread) {
191: performWork(gui4jController, work, paramMap, componentInstance,
192: forceExecutionInCurrentThread, true, true);
193: }
194:
195: private void performWork(final Gui4jCallBase gui4jController,
196: final Gui4jGetValue[] work, final Map paramMap,
197: final Gui4jComponentInstance componentInstance,
198: final boolean forceExecutionInCurrentThread,
199: final boolean isHighPriorityThread) {
200: performWork(gui4jController, work, paramMap, componentInstance,
201: forceExecutionInCurrentThread, isHighPriorityThread,
202: false);
203: }
204:
205: /**
206: * Perform the given work.
207: *
208: * @see org.gui4j.core.Gui4jThreadManager#performWork(Gui4jCallBase,Gui4jGetValue[],Map)
209: * @param gui4jController
210: * @param action
211: * @param paramMap
212: */
213: public void performWork(Gui4jCallBase gui4jController,
214: Gui4jGetValue action, Map paramMap) {
215: Gui4jGetValue[] work = { action };
216: performWork(gui4jController, work, paramMap);
217: }
218:
219: /**
220: * Perform the given work.
221: *
222: * @param gui4jController
223: * @param action
224: * @param paramMap
225: * @param componentInstance
226: */
227: public void performWork(Gui4jCallBase gui4jController,
228: Gui4jGetValue action, Map paramMap,
229: Gui4jComponentInstance componentInstance) {
230: Gui4jGetValue[] work = { action };
231: performWork(gui4jController, work, paramMap, componentInstance);
232: }
233:
234: /**
235: * Performs the given work. Dependant of the number of maximum worker
236: * threads, the work is either performed in the same thread, or by a new
237: * worker, or put into a FIFO queue.
238: *
239: * @param gui4jController
240: * @param work
241: * @param paramMap
242: * @param actionHandler
243: * @param forceExecutionInCurrentThread
244: * @param isHighPriorityThread
245: * @param specialSuccessHandling
246: */
247: private void performWork(final Gui4jCallBase gui4jController,
248: final Gui4jGetValue[] work, final Map paramMap,
249: final Gui4jComponentInstance componentInstance,
250: final boolean forceExecutionInCurrentThread,
251: final boolean isHighPriorityThread,
252: final boolean specialSuccessHandling) {
253: if (mUseWorkerThreads && SwingUtilities.isEventDispatchThread()
254: && !forceExecutionInCurrentThread) {
255: final InvokerCallStack callStack = mGui4j
256: .traceWorkerInvocation() ? new InvokerCallStack(
257: Thread.currentThread().getName()) : null;
258: final WorkPackage workPackage = new WorkPackage(
259: gui4jController, work, paramMap, componentInstance,
260: callStack, isHighPriorityThread,
261: specialSuccessHandling);
262: // put work on working list
263: Runnable run = new Runnable() {
264:
265: public void run() {
266: if (isHighPriorityThread) {
267: synchronized (mSyncObjectCount) {
268: mWorkPackagesHighPriority.add(workPackage);
269: mWorkPackageCountWaitingHighPriority++;
270: checkWorkerThreadCount();
271: }
272: } else {
273: synchronized (mSyncObjectCount) {
274: mWorkPackagesNormal.add(workPackage);
275: mWorkPackageCountWaitingNormal++;
276: checkWorkerThreadCount();
277: }
278: }
279: // Any worker should do the work
280: wakeupWorker();
281: }
282: };
283: SwingUtilities.invokeLater(run);
284: } else {
285: for (int i = 0; i < work.length; i++) {
286: if (work[i] != null) {
287: work[i].getValue(gui4jController, paramMap, null);
288: }
289: }
290: }
291: }
292:
293: public void shutdown() {
294: mShutdownThreads = true;
295: wakeupWorkers();
296: }
297:
298: private void wakeupWorkers() {
299: synchronized (mSyncObjectWorkerWait) {
300: mSyncObjectWorkerWait.notifyAll();
301: }
302: }
303:
304: private void wakeupWorker() {
305: synchronized (mSyncObjectWorkerWait) {
306: mSyncObjectWorkerWait.notify();
307: }
308: }
309:
310: protected void checkWorkerThreadCount() {
311: boolean createNewWorker = false;
312: int workPackageCountTotal;
313: synchronized (mSyncObjectCount) {
314: workPackageCountTotal = mWorkPackageCountWaitingHighPriority
315: + mWorkPackageCountWaitingNormal
316: + mWorkPackageCountRunningHighPriority
317: + mWorkPackageCountRunningNormal;
318: if (workPackageCountTotal > mWorkerCount) {
319: // There is more work to do than we have worker threads
320: if (mWorkerCount < mMaxNumberOfWorkerThreads
321: || mMaxNumberOfWorkerThreads == -1) {
322: createNewWorker = true;
323: mWorkerCount++;
324: }
325: }
326: }
327: if (createNewWorker) {
328: WorkerThread workerThread = new WorkerThread(mMaxWorkerId++);
329: if (mLogger.isDebugEnabled()) {
330: mLogger.debug("Created " + workerThread
331: + ". Current work queue: RunningHighPriority: "
332: + mWorkPackageCountRunningHighPriority
333: + ", WaitingHighPriority: "
334: + mWorkPackageCountWaitingHighPriority
335: + ", RunningNormal: "
336: + mWorkPackageCountRunningNormal
337: + ", WaitingNormal: "
338: + mWorkPackageCountWaitingNormal);
339: }
340: workerThread.start();
341: }
342: }
343:
344: private static class WorkPackage {
345: public final Gui4jCallBase mGui4jController;
346: public final Gui4jGetValue[] mWork;
347: public final Gui4jComponentInstance mComponentInstance;
348: public final Map mParamMap;
349: public final InvokerCallStack mCallStack;
350: public final boolean mSpecialSuccessHandling;
351: public final boolean mIsHighPriority;
352:
353: public WorkPackage(Gui4jCallBase gui4jController,
354: Gui4jGetValue[] work, Map paramMap,
355: Gui4jComponentInstance componentInstance,
356: InvokerCallStack callStack, boolean isHighPriority,
357: boolean specialSuccessHandling) {
358: mGui4jController = gui4jController;
359: mWork = work;
360: mParamMap = paramMap;
361: mComponentInstance = componentInstance;
362: mCallStack = callStack;
363: mIsHighPriority = isHighPriority;
364: mSpecialSuccessHandling = specialSuccessHandling;
365: }
366: }
367:
368: public class WorkerThread extends Thread {
369: private WorkPackage mWorkPackage;
370: private final int mId;
371:
372: public WorkerThread(int n) {
373: mId = n;
374:
375: // This thread will be created by the AWT thread and we have to make
376: // sure we don't use the AWT thread's privileged priority.
377: setPriority(NORM_PRIORITY);
378:
379: setName("Gui4j-Worker " + mId);
380: mLogger.debug(this + ": created");
381: }
382:
383: public Throwable getCallStack() {
384: return mWorkPackage != null ? mWorkPackage.mCallStack
385: : null;
386: }
387:
388: public String toString() {
389: return getName();
390: }
391:
392: public void run() {
393: try {
394: boolean decHighPriority = false;
395: boolean decNormal = false;
396: while (!mShutdownThreads) {
397: mWorkPackage = null;
398: boolean wakeUpOtherWorkers = false;
399: synchronized (mSyncObjectCount) {
400: if (decHighPriority) {
401: mWorkPackageCountRunningHighPriority--;
402: decHighPriority = false;
403: if (mWorkPackageCountWaitingHighPriority
404: + mWorkPackageCountRunningHighPriority == 0
405: && mWorkPackageCountWaitingNormal > 1) {
406: // nun kann die restliche Arbeit von anderen
407: // Worken eventuell übernommen werden
408: wakeUpOtherWorkers = true;
409: }
410: }
411: if (decNormal) {
412: mWorkPackageCountRunningNormal--;
413: decNormal = false;
414: }
415:
416: // Check for new work
417: if (mWorkPackageCountRunningHighPriority > 0) {
418: // We have to wait until high priority has finished
419: } else {
420: if (mWorkPackageCountWaitingHighPriority > 0) {
421: mWorkPackage = (WorkPackage) mWorkPackagesHighPriority
422: .removeFirst();
423: mWorkPackageCountWaitingHighPriority--;
424: mWorkPackageCountRunningHighPriority++;
425: } else if (mWorkPackageCountWaitingNormal > 0) {
426: mWorkPackage = (WorkPackage) mWorkPackagesNormal
427: .removeFirst();
428: mWorkPackageCountWaitingNormal--;
429: mWorkPackageCountRunningNormal++;
430: } else {
431: // no work available
432: // Terminate Thread
433: }
434: }
435: }
436: if (wakeUpOtherWorkers) {
437: wakeupWorkers();
438: }
439: if (mWorkPackage == null) {
440: synchronized (mSyncObjectWorkerWait) {
441: try {
442: mSyncObjectWorkerWait.wait();
443: } catch (InterruptedException e) {
444: return;
445: }
446: }
447: } else {
448: for (int i = 0; i < mWorkPackage.mWork.length; i++) {
449: doWork(mWorkPackage.mWork[i], mWorkPackage,
450: i);
451: }
452: if (mLogger.isDebugEnabled()) {
453: mLogger.debug(this + ": work finished.");
454: }
455: if (mWorkPackage.mIsHighPriority) {
456: decHighPriority = true;
457: } else {
458: decNormal = true;
459: }
460: }
461: }
462: } finally {
463: synchronized (mSyncObjectCount) {
464: mWorkerCount--;
465: }
466: }
467: }
468:
469: private void doWork(Gui4jGetValue work,
470: WorkPackage workPackage, int i) {
471: if (work == null) {
472: return;
473: }
474: if (mLogger.isDebugEnabled()) {
475: mLogger.trace(this
476: + ": performing work "
477: + (workPackage.mIsHighPriority ? "(high prio)"
478: : "") + ": " + work);
479: }
480: try {
481: work.getValueNoErrorChecking(
482: workPackage.mGui4jController,
483: workPackage.mParamMap,
484: workPackage.mComponentInstance);
485: // Falls verlangt, rufen wir
486: // (nur nach dem ersten Aufruf) die
487: // <code>handleSuccess</code>
488: // Methode auf.
489: // Beim Edit-Feld wird damit im Ok-Fall der
490: // Inhalt nochmals
491: // angezeigt. Außerdem kann damit Validierung
492: // gemacht werden.
493: if (i == 0 && workPackage.mSpecialSuccessHandling
494: && workPackage.mComponentInstance != null) {
495: workPackage.mComponentInstance.handleSuccess();
496: }
497: } catch (Throwable t) {
498: // Analog zum Ok-Fall, rufen wir im Fehlerfall
499: // nach dem ersten
500: // Aufruf die <code>handleException</code>
501: // Methode auf. Damit
502: // kann beispielsweise Validierung gemacht
503: // werden.
504: if (i == 0 && workPackage.mSpecialSuccessHandling
505: && workPackage.mComponentInstance != null) {
506: workPackage.mComponentInstance.handleException(t);
507: } else {
508: // Falls kein ActionHandler definiert wurde,
509: // oder
510: // es sich nicht um den ersten Aufruf
511: // handelt,
512: // erfolgt die normale Fehlerbehandlung.
513: mGui4j.handleException(
514: workPackage.mGui4jController, t, null);
515: }
516: }
517: }
518:
519: }
520:
521: private static class InvokerCallStack extends Throwable {
522: public InvokerCallStack(String threadName) {
523: super (threadName);
524: }
525:
526: public String toString() {
527: return "Thread [" + getMessage() + "]";
528: }
529:
530: }
531:
532: /**
533: * If the calling thread is the AWT Event Dispatch Thread (EDT) the
534: * specified command is executed and this method returns after completion of
535: * the command. If the calling thread is not the EDT, the given
536: * <code>Runnable</code> is inserted into the AWT EventQueue via
537: * <code>EventQueue.invokeAndWait()</code>. In this case, this call does
538: * not return until the EDT has completed the queued task.
539: *
540: * @param run
541: * task to be scheduled in the GUI thread
542: */
543: public static void executeInSwingThreadAndWait(Runnable run) {
544: try {
545: if (EventQueue.isDispatchThread()) {
546: // if we already are in the EDT we simply execute the command
547: run.run();
548: } else {
549: EventQueue.invokeAndWait(run);
550: }
551: } catch (InterruptedException ex) {
552: mLogger.warn("Interrupted", ex);
553: } catch (InvocationTargetException ex) {
554: Gui4jReflectionManager.handleInvocationTargetException(ex);
555: throw new Gui4jUncheckedException.ProgrammingError(
556: PROGRAMMING_ERROR_invocation_target_exception, ex);
557: }
558: }
559:
560: /**
561: * The given task is executed in the GUI thread as soon as possible. There
562: * are two cases: <br>
563: * If this thread is not the GUI thread, the given <code>Runnable</code>
564: * is inserted into the task queue of the GUI thread. This call then returns
565: * immediately without waiting for the scheduled task to be finished. <br>
566: * If this thread is the GUI thread itself, the given task is executed
567: * immediately and synchronously, i.e. this call will not return until the
568: * task is completed.
569: *
570: * @param run
571: * task to be scheduled in the GUI thread
572: */
573: public static void executeInSwingThreadAndContinue(
574: final Runnable run) {
575: if (EventQueue.isDispatchThread()) {
576: run.run();
577: } else {
578: EventQueue.invokeLater(run);
579: }
580: }
581: }
|