001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: /*
043: * File : EventHandler.java
044: * Version : 1.0
045: * Description : Queues runnable objects and calls their run() methods in
046: * sequence.
047: * Author : Darshan
048: */
049: package org.netbeans.modules.uml.integration.ide.events;
050:
051: import org.netbeans.modules.uml.core.support.umlsupport.Log;
052: import org.netbeans.modules.uml.integration.ide.UMLSupport;
053: import org.netbeans.modules.uml.integration.ide.dialogs.IProgressIndicator;
054: import org.netbeans.modules.uml.integration.ide.dialogs.IProgressIndicatorFactory;
055: import java.util.Vector;
056: import org.netbeans.modules.uml.core.roundtripframework.RTMode;
057: import org.netbeans.modules.uml.ui.support.ProductHelper;
058:
059: /**
060: * Queues runnable objects and calls their run() methods in sequence from a
061: * separate worker thread.
062: *
063: * Revision History
064: * No. Date Who What
065: * --- ---- --- ----
066: * 1 2002-05-28 Darshan Synchronized on EventHandler instance to allow
067: * synch between source-model and model-source
068: * threads.
069: *
070: * @author Darshan
071: */
072: public class EventHandler {
073: /**
074: * The number of runnable objects that must be queued before we show a
075: * progress indicator to the user.
076: */
077: private static final short PROGRESS_THRESHOLD = 3;
078:
079: private static final short PRESET_MAXIMUM = 200;
080:
081: /**
082: * Queue of runnable objects to be executed in sequence.
083: */
084: private Vector runnableQueue = new Vector();
085:
086: /**
087: * The worker thread that runs the runnable objects in sequence.
088: */
089: private WorkerThread worker = new WorkerThread();
090:
091: /**
092: * The thread that displays and manages the progress indicator.
093: */
094: private ProgressThread progress = null;
095:
096: /**
097: * An object that knows how to execute a queued runnable object. Note that
098: * the queued runnable objects need not be java.lang.Runnable objects if you
099: * provide your own executor.
100: */
101: private ITaskExecutor executor = defaultExecutor;
102:
103: /**
104: * The default executor; executes Runnable objects' run() methods.
105: */
106: public static final ITaskExecutor defaultExecutor = new ITaskExecutor() {
107: public void executeTask(Object task) {
108: // conover - only execute events if RT is enabled
109: if (ProductHelper.getCoreProduct().getRoundTripController()
110: .getMode() == RTMode.RTM_LIVE) {
111: ((Runnable) task).run();
112: }
113: }
114: };
115:
116: /**
117: * A factory that knows how to create progress indicators. If left null,
118: * progress indicators won't be shown.
119: */
120: private IProgressIndicatorFactory progressFactory = null;
121:
122: /**
123: * <code>true</code> if the worker thread is processing a job on the queue.
124: */
125: private boolean working = false;
126:
127: /**
128: * Creates an EventHandler, but does not start the worker thread.
129: *
130: * @param queueName The thread name of the worker.
131: */
132: public EventHandler(String queueName) {
133: if (queueName != null)
134: worker.setName(queueName);
135: }
136:
137: /**
138: * Sets the executor used to run the objects in the queue.
139: *
140: * @param executor An <code>EventHandler.ITaskExecutor</code> replacement
141: * for the default executor. Note that nulling the executor
142: * will create a do-nothing queue that simply discards the
143: * jobs on the queue.
144: */
145: public void setTaskExecutor(EventHandler.ITaskExecutor executor) {
146: this .executor = executor;
147: }
148:
149: /**
150: * Retrieves the executor currently in use.
151: * @return The <code>EventHandler.ITaskExecutor</code> that's in use; if
152: * null, this queue is a do-nothing queue.
153: */
154: public EventHandler.ITaskExecutor getTaskExecutor() {
155: return executor;
156: }
157:
158: /**
159: * Retrieves the progress indicator currently in use.
160: * @return An <code>IProgressIndicatorFactory</code>.
161: */
162: public IProgressIndicatorFactory getProgressFactory() {
163: return progressFactory;
164: }
165:
166: /**
167: * Sets the progress indicator factory to be used.
168: * @param factory An <code>IProgressIndicatorFactory</code>; can be null to
169: * suppress progress monitoring.
170: */
171: public void setProgressFactory(IProgressIndicatorFactory factory) {
172: if ((progressFactory = factory) != null) {
173: if (progress == null)
174: progress = new ProgressThread();
175: if (!progress.isAlive())
176: progress.start();
177: }
178: }
179:
180: /**
181: * Starts the worker thread to begin processing events from the queue.
182: * Existing queued events will be processed.
183: */
184: public void startWorker() {
185: worker.start();
186: }
187:
188: /**
189: * Stops the worker thread as soon as the currently running Runnable
190: * is finished. Pending queued events will be discarded and any open
191: * progress indicator will be closed.
192: */
193: public void stopWorker() {
194: worker.interrupt();
195: clear();
196: }
197:
198: /**
199: * Empties the queue of pending events. Any event that is currently being
200: * processed will not be affected.
201: */
202: public void clear() {
203: synchronized (runnableQueue) {
204: runnableQueue.clear();
205: if (progress != null && progress.isAlive())
206: progress.killProgress();
207: }
208: }
209:
210: /**
211: * Adds a job to the queue, usually a Runnable job (if the default
212: * executor is in use).
213: *
214: * @param r The job, usually a Runnable.
215: */
216: public void queueRunnable(Object r) {
217: queueRunnable(r, -1);
218: }
219:
220: /**
221: * Adds a job to the queue at the specified position. The job is usually a
222: * Runnable job (if the default executor is in use).
223: *
224: * @param r The job, usually a Runnable.
225: * @param index The index on the queue at which to insert the Runnable.
226: */
227: public void queueRunnable(Object r, int index) {
228: synchronized (runnableQueue) {
229: if (index <= -1 || index > runnableQueue.size())
230: runnableQueue.add(r);
231: else
232: runnableQueue.add(index, r);
233:
234: if (progress != null && progressFactory != null
235: && !progress.isWorking()) {
236: if (runnableQueue.size() > PROGRESS_THRESHOLD)
237: showProgressIndicator(runnableQueue.size() + 1);
238: } else if (worker.isAlive() && progress != null
239: && progress.isWorking()) {
240: progress.addTasks(1);
241: }
242: runnableQueue.notify();
243: }
244: }
245:
246: public void showProgressIndicator(int size) {
247: if (progressFactory == null || progress == null)
248: throw new IllegalStateException(
249: "Cannot show progress indicator");
250: progress.startProgress(size);
251: }
252:
253: /**
254: * Returns the first job in the queue; blocks if the queue is empty.
255: * @throws InterruptedException If the thread is interrupted
256: */
257: private Object getNextRunnable() throws InterruptedException {
258: synchronized (runnableQueue) {
259: while (runnableQueue.size() == 0) {
260: /*if(enableWSSaves && !wasWSSavedLast) {
261: wasWSSavedLast = !wasWSSavedLast;
262: queueWSSave();
263: } else {
264: wasWSSavedLast = !wasWSSavedLast;
265: runnableQueue.wait();
266: }*/
267: runnableQueue.wait();
268: }
269: Object r = runnableQueue.elementAt(0);
270: runnableQueue.remove(0);
271: return r;
272: }
273: }
274:
275: /**
276: * Determines if the queue contains items for processing or is currently
277: * processing an item.
278: *
279: * @return <code>true</code> If the queue is busy.
280: */
281: public boolean isBusy() {
282: synchronized (runnableQueue) {
283: return runnableQueue.size() > 0 || working;
284: }
285: }
286:
287: /**
288: * An interface for classes that know how to execute an arbitrary task.
289: */
290: public static interface ITaskExecutor {
291: /**
292: * Runs the given task. It is assumed that the task executor knows
293: * (or can find out) what kind of task the given object is.
294: */
295: public void executeTask(Object taskObj) throws Exception;
296: }
297:
298: /**
299: * Runs queued Runnables.
300: */
301: private class WorkerThread extends Thread {
302: /**
303: * True if this thread has been interrupted.
304: */
305: private boolean interrupted = false;
306:
307: public void run() {
308: interrupted = false;
309:
310: try {
311: while (!interrupted) {
312: Object runnable = getNextRunnable();
313:
314: try {
315: working = true;
316: if (executor != null)
317: executor.executeTask(runnable);
318: } catch (Throwable t) {
319: Log.stackTrace(t);
320: } finally {
321: try {
322: if (progress != null
323: && progress.isWorking()) {
324: progress.incrementProgress();
325: if (runnableQueue.size() == 0)
326: progress.endProgress();
327: }
328: } catch (Exception ignored) {
329: }
330: working = false;
331: }
332: }
333: } catch (InterruptedException e) {
334: }
335: }
336:
337: /**
338: * Interrupts the worker thread - the interrupt will be immediate
339: * if the worker was waiting on an empty queue, otherwise the
340: * currently running Runnable will be allowed to finish first.
341: */
342: public void interrupt() {
343: interrupted = true;
344: super .interrupt();
345: }
346: }
347:
348: private class ProgressThread extends Thread {
349: private IProgressIndicator indicator = null;
350: private boolean needed = true;
351: private int taskCount = 0;
352: private int position = 0, completedTasks = 0;
353:
354: public void incrementProgress() {
355: if (indicator == null)
356: return;
357:
358: int newPos = ++completedTasks * PRESET_MAXIMUM / taskCount;
359: if (newPos > position)
360: indicator.setProgress(null, position = newPos);
361: }
362:
363: public void setTaskCount(int tasks) {
364: taskCount = tasks;
365: }
366:
367: public void addTasks(int taskInc) {
368: taskCount += taskInc;
369: }
370:
371: public void setNeeded(boolean needed) {
372: this .needed = needed;
373: }
374:
375: public IProgressIndicator getIndicator() {
376: return indicator;
377: }
378:
379: /**
380: * Starts a new progress indicator, if no indicator is already open.
381: * If an indicator is already open, returns silently.
382: */
383: public void startProgress(int initialTaskCount) {
384: if (progressFactory != null && indicator == null) {
385: synchronized (this ) {
386: // Double-checked locking style synchronization problems
387: // here, perhaps? Happily, this is not mission-critical
388: // stuff.
389: if (indicator != null)
390: return;
391:
392: indicator = progressFactory.getProgressIndicator();
393: if (indicator != null) {
394: indicator.setMaxRange(PRESET_MAXIMUM);
395: taskCount = initialTaskCount;
396: completedTasks = 0;
397: indicator
398: .setProgress(
399: UMLSupport
400: .getString("Dialog.RoundtripProgress.Text"),
401: position = 3);
402: Log.out("Done starting progress indicator");
403:
404: if (isAlive())
405: notify();
406: else
407: start();
408: }
409: }
410: }
411: }
412:
413: public boolean isWorking() {
414: return indicator != null && isAlive();
415: }
416:
417: public void endProgress() {
418: if (indicator != null)
419: indicator.done();
420: }
421:
422: public void killProgress() {
423: setNeeded(false);
424: if (indicator != null)
425: indicator.done();
426: else if (isAlive())
427: interrupt();
428: }
429:
430: synchronized public void run() {
431: while (needed) {
432: try {
433: while (indicator == null)
434: wait();
435: Log.out("Showing progress indicator");
436: indicator.show();
437: indicator = null;
438: } catch (InterruptedException e) {
439: // Precaution
440: indicator = null;
441: break;
442: } catch (Exception e) {
443: Log.stackTrace(e);
444: }
445: }
446: }
447: }
448: }
|