001: /*
002: * uDig - User Friendly Desktop Internet GIS client
003: * http://udig.refractions.net
004: * (C) 2004, Refractions Research Inc.
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;
009: * version 2.1 of the License.
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: */
017: package net.refractions.udig.ui;
018:
019: import java.lang.reflect.InvocationTargetException;
020: import java.util.concurrent.BlockingQueue;
021: import java.util.concurrent.Callable;
022: import java.util.concurrent.ExecutorService;
023: import java.util.concurrent.Executors;
024: import java.util.concurrent.Future;
025: import java.util.concurrent.LinkedBlockingQueue;
026: import java.util.concurrent.atomic.AtomicBoolean;
027:
028: import net.refractions.udig.internal.ui.UiPlugin;
029: import net.refractions.udig.ui.internal.Messages;
030:
031: import org.eclipse.core.runtime.IProgressMonitor;
032: import org.eclipse.core.runtime.ISafeRunnable;
033: import org.eclipse.core.runtime.NullProgressMonitor;
034: import org.eclipse.jface.dialogs.IDialogConstants;
035: import org.eclipse.jface.dialogs.ProgressMonitorDialog;
036: import org.eclipse.jface.operation.IRunnableWithProgress;
037: import org.eclipse.swt.widgets.Composite;
038: import org.eclipse.swt.widgets.Display;
039: import org.eclipse.swt.widgets.Shell;
040: import org.eclipse.ui.PlatformUI;
041: import org.geotools.brewer.color.ColorBrewer;
042:
043: /**
044: * A facade into udig to simplify operations relating to performing platform operations.
045: *
046: * @author jeichar
047: * @since 1.1
048: */
049: public class PlatformGIS {
050:
051: private static ColorBrewer colorBrewer = ColorBrewer.instance();
052: private static ExecutorService executor = Executors
053: .newCachedThreadPool();
054:
055: /**
056: * Runs the given runnable in a separate thread, providing it a progress monitor. Exceptions
057: * thrown by the runnable are logged, and not rethrown.
058: */
059: public static void run(IRunnableWithProgress request) {
060: getRunner().addRequest(request);
061: }
062:
063: /**
064: * This method runs the runnable in a separate thread. It is useful in cases where a thread must
065: * wait for a long running and potentially blocking operation (for example an IO operation). If
066: * the IO is done in the UI thread then the user interface will lock up. This allows synchronous
067: * execution of a long running thread in the UI thread without locking the UI.
068: *
069: * @param runnable The runnable(operation) to run
070: * @param monitor the progress monitor to update.
071: * @throws InvocationTargetException
072: * @throws InterruptedException
073: */
074: public static void runBlockingOperation(
075: final IRunnableWithProgress runnable,
076: final IProgressMonitor monitor2)
077: throws InvocationTargetException, InterruptedException {
078:
079: final IProgressMonitor monitor = monitor2 == null ? new NullProgressMonitor()
080: : monitor2;
081: final InterruptedException[] interruptedException = new InterruptedException[1];
082: final InvocationTargetException[] invocationTargetException = new InvocationTargetException[1];
083: Display d = Display.getCurrent();
084: if (d == null)
085: d = Display.getDefault();
086: final Display display = d;
087: final AtomicBoolean done = new AtomicBoolean();
088: final Object mutex = new Object();
089: done.set(false);
090:
091: Future<Object> future = executor.submit(new Callable<Object>() {
092:
093: public Object call() throws Exception {
094: try {
095: runnable.run(new OffThreadProgressMonitor(
096: monitor != null ? monitor : ProgressManager
097: .instance().get(), display));
098: } catch (InvocationTargetException ite) {
099: invocationTargetException[0] = ite;
100: } catch (InterruptedException ie) {
101: interruptedException[0] = ie;
102: } finally {
103: done.set(true);
104: synchronized (mutex) {
105: mutex.notify();
106: }
107: }
108: return null;
109: }
110:
111: });
112: while (!monitor.isCanceled() && !done.get()
113: && !Thread.interrupted()) {
114: Thread.yield();
115: if (Display.getCurrent() == null) {
116: wait(mutex, 200);
117: } else {
118: try {
119: if (!d.readAndDispatch()) {
120: wait(mutex, 200);
121: }
122: } catch (Exception e) {
123: UiPlugin
124: .log(
125: "Error occurred net.refractions.udig.issues.internal while waiting for an operation to complete", e); //$NON-NLS-1$
126: }
127: }
128: }
129: if (monitor.isCanceled()) {
130: future.cancel(true);
131: }
132:
133: if (interruptedException[0] != null)
134: throw interruptedException[0];
135: else if (invocationTargetException[0] != null)
136: throw invocationTargetException[0];
137: }
138:
139: private static void wait(Object mutex, long waitTime) {
140: synchronized (mutex) {
141: try {
142: mutex.wait(waitTime);
143: } catch (InterruptedException e) {
144: return;
145: }
146: }
147: }
148:
149: /**
150: * Runs the given runnable in a protected mode. Exceptions thrown in the runnable are logged and
151: * passed to the runnable's exception handler. Such exceptions are not rethrown by this method.
152: */
153: public static void run(ISafeRunnable request) {
154: getRunner().addRequest(request);
155: }
156:
157: private static Runner getRunner() {
158: if (runner == null) {
159: synchronized (Runner.class) {
160: if (runner == null) {
161: runner = new Runner();
162: runner.start();
163: }
164: }
165: }
166: return runner;
167: }
168:
169: private volatile static Runner runner;
170:
171: private static class Runner extends Thread {
172: /**
173: * @param name
174: */
175: public Runner() {
176: super (""); //$NON-NLS-1$
177: setDaemon(true);
178: }
179:
180: BlockingQueue<Object> requests = new LinkedBlockingQueue<Object>();
181:
182: public void run() {
183: while (true && !PlatformUI.getWorkbench().isClosing()) {
184: try {
185: Object runnable = requests.take();
186: if (runnable == null)
187: continue;
188:
189: if (runnable instanceof ISafeRunnable)
190: run((ISafeRunnable) runnable);
191: else if (runnable instanceof IRunnableWithProgress)
192: run((IRunnableWithProgress) runnable,
193: ProgressManager.instance().get());
194: } catch (InterruptedException e) {
195: UiPlugin.log("Interrupted thread", e); //$NON-NLS-1$
196: }
197: }
198:
199: }
200:
201: /**
202: * Add a runnable object to be run.
203: *
204: * @param runnable
205: */
206: public void addRequest(Object runnable) {
207: requests.add(runnable);
208: }
209:
210: private void run(ISafeRunnable runnable) {
211: try {
212: runnable.run();
213: } catch (Throwable e) {
214: if (e.getMessage() != null) {
215: UiPlugin.log(e.getMessage(), e);
216: } else {
217: UiPlugin.log("", e); //$NON-NLS-1$
218: }
219: runnable.handleException(e);
220: }
221: }
222:
223: private void run(IRunnableWithProgress runnable,
224: IProgressMonitor monitor) {
225: try {
226: runnable.run(monitor);
227: } catch (Throwable t) {
228: UiPlugin.log("", t); //$NON-NLS-1$
229: }
230: }
231:
232: }
233:
234: public static ColorBrewer getColorBrewer() {
235: return colorBrewer;
236: }
237:
238: /**
239: * Acts as a safer alternative to Display.syncExec(). If readAndDispatch is being called from
240: * the display thread syncExec calls will not be executed only Display.asyncExec calls are
241: * executed. So this method uses Display.asyncExec and patiently waits for the result to be
242: * returned. Can be called from display thread or non-display thread. Runnable should not be
243: * blocking or it will block the display thread.
244: *
245: * @param runnable runnable to execute
246: */
247: public static void syncInDisplayThread(final Runnable runnable) {
248: syncInDisplayThread(Display.getDefault(), runnable);
249: }
250:
251: public static void syncInDisplayThread(Display display,
252: final Runnable runnable) {
253: if (Display.getCurrent() != display) {
254: final AtomicBoolean done = new AtomicBoolean(false);
255: final Object mutex = new Object();
256: display.asyncExec(new Runnable() {
257: public void run() {
258: try {
259: runnable.run();
260: } finally {
261: done.set(true);
262: synchronized (mutex) {
263: mutex.notify();
264: }
265: }
266: }
267: });
268: while (!done.get() && !Thread.interrupted()) {
269: synchronized (mutex) {
270: wait(mutex, 200);
271: }
272: }
273: } else {
274: runnable.run();
275: }
276: }
277:
278: /**
279: * Waits for the condition to become true. Will call Display#readAndDispatch() if currently in
280: * the display thread.
281: *
282: * @param interval the time to wait between testing of condition, in milliseconds. Must be a
283: * positive number and is recommended to be larger than 50
284: * @param timeout maximum time to wait. Will throw an {@link InterruptedException} if reached.
285: * If -1 then it will not timeout.
286: * @param condition condition to wait on.
287: * @param mutex if not null mutex will be waited on so that a notify will interrupt the wait.
288: * @throws InterruptedException
289: */
290: public static void wait(long interval, long timeout,
291: WaitCondition condition, Object mutex)
292: throws InterruptedException {
293: long start = System.currentTimeMillis();
294: Object mutex2 = mutex == null ? new Object() : mutex;
295:
296: Display current = Display.getCurrent();
297: if (current == null) {
298: while (!condition.isTrue()) {
299: if (timeout > 0
300: && System.currentTimeMillis() - start > timeout)
301: throw new InterruptedException(
302: "Timed out waiting for condition " + condition); //$NON-NLS-1$
303: synchronized (mutex2) {
304: mutex2.wait(interval);
305: }
306: }
307: } else {
308: while (!condition.isTrue()) {
309: Thread.yield();
310: if (timeout > 0
311: && System.currentTimeMillis() - start > timeout)
312: throw new InterruptedException(
313: "Timed out waiting for condition " + condition); //$NON-NLS-1$
314: if (!current.readAndDispatch())
315: synchronized (mutex2) {
316: mutex2.wait(interval);
317: }
318: }
319:
320: }
321: }
322:
323: /**
324: * Runs a blocking task in a ProgressDialog. It is ran in such a way that even if the task
325: * blocks it can be cancelled. This is unlike the normal ProgressDialog.run(...) method which
326: * requires that the {@link IProgressMonitor} be checked and the task to "nicely" cancel.
327: *
328: * @param dialogTitle The title of the Progress dialog
329: * @param showRunInBackground if true a button added to the dialog that will make the job be ran
330: * in the background.
331: * @param runnable the task to execute.
332: * @param runASync TODO
333: */
334: public static void runInProgressDialog(final String dialogTitle,
335: final boolean showRunInBackground,
336: final IRunnableWithProgress runnable, boolean runASync) {
337:
338: Runnable object = new Runnable() {
339: public void run() {
340: Shell shell = Display.getDefault().getActiveShell();
341: ProgressMonitorDialog dialog = new ProgressMonitorDialog(
342: shell) {
343: @Override
344: protected void configureShell(Shell shell) {
345: super .configureShell(shell);
346: shell.setText(dialogTitle);
347: }
348:
349: @Override
350: protected void createButtonsForButtonBar(
351: Composite parent) {
352: if (showRunInBackground)
353: createBackgroundButton(parent);
354: super .createButtonsForButtonBar(parent);
355: }
356:
357: private void createBackgroundButton(Composite parent) {
358: createButton(parent, IDialogConstants.BACK_ID,
359: Messages.PlatformGIS_background, true);
360: }
361:
362: @Override
363: protected void buttonPressed(int buttonId) {
364: if (buttonId == IDialogConstants.BACK_ID) {
365: getShell().setVisible(false);
366: } else
367: super .buttonPressed(buttonId);
368: }
369: };
370: try {
371:
372: dialog.run(true, true, new IRunnableWithProgress() {
373: public void run(IProgressMonitor monitor) {
374: try {
375: runBlockingOperation(
376: new IRunnableWithProgress() {
377:
378: public void run(
379: IProgressMonitor monitor)
380: throws InvocationTargetException,
381: InterruptedException {
382: runnable.run(monitor);
383: }
384: }, monitor);
385: } catch (Exception e) {
386: UiPlugin.log("", e); //$NON-NLS-1$
387: }
388:
389: }
390: });
391: } catch (Exception e) {
392: UiPlugin.log("", e); //$NON-NLS-1$
393: }
394: }
395: };
396:
397: if (runASync)
398: Display.getDefault().asyncExec(object);
399: else
400: syncInDisplayThread(object);
401: }
402: }
|