001 /*
002 * Copyright 2009-2013 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package griffon.core;
017
018 import griffon.util.*;
019 import groovy.lang.*;
020 import org.codehaus.griffon.runtime.util.CallableWithArgsMetaMethod;
021 import org.codehaus.griffon.runtime.util.ExecutorServiceHolder;
022 import org.codehaus.griffon.runtime.util.RunnableWithArgsMetaMethod;
023 import org.slf4j.Logger;
024 import org.slf4j.LoggerFactory;
025
026 import java.util.concurrent.Callable;
027 import java.util.concurrent.ExecutorService;
028 import java.util.concurrent.Executors;
029 import java.util.concurrent.Future;
030
031 /**
032 * Helper class that can execute code inside the UI thread.
033 *
034 * @author Andres Almiray
035 */
036 public final class UIThreadManager {
037 // Shouldn't need to synchronize access to this field as setting its value
038 // should be done at boot time
039 private UIThreadHandler uiThreadHandler;
040 private static final ExecutorService DEFAULT_EXECUTOR_SERVICE = ExecutorServiceHolder.add(Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()));
041 private static final Logger LOG = LoggerFactory.getLogger(UIThreadManager.class);
042
043 private static final UIThreadManager INSTANCE = new UIThreadManager();
044
045 public static UIThreadManager getInstance() {
046 return INSTANCE;
047 }
048
049 private UIThreadManager() {
050 }
051
052 private static abstract class RunnableRunner extends RunnableWithArgs {
053 private final String methodName;
054
055 protected RunnableRunner(String methodName) {
056 this.methodName = methodName;
057 }
058
059 public void run(Object[] args) {
060 if (args != null && args.length == 1) {
061 if (args[0] instanceof Runnable) {
062 withRunnable((Runnable) args[0]);
063 return;
064 }
065 }
066 throw new MissingMethodException(methodName, UIThreadManager.class, args);
067 }
068
069 protected abstract void withRunnable(Runnable runnable);
070 }
071
072 private static Class[] EXEC_METHOD_ARGS = new Class[]{Runnable.class};
073
074 private static final String EXECUTE_INSIDE_UI_SYNC = "execInsideUISync";
075 private static final RunnableWithArgs EXECUTE_INSIDE_UI_SYNC_RUNNER = new RunnableRunner(EXECUTE_INSIDE_UI_SYNC) {
076 protected void withRunnable(Runnable runnable) {
077 INSTANCE.executeSync(runnable);
078 }
079 };
080 private static final Closure EXECUTE_INSIDE_UI_SYNC_CLOSURE = new RunnableWithArgsClosure(
081 INSTANCE,
082 EXECUTE_INSIDE_UI_SYNC_RUNNER);
083 private static final MetaMethod EXECUTE_INSIDE_UI_SYNC_METHOD = new RunnableWithArgsMetaMethod(
084 EXECUTE_INSIDE_UI_SYNC,
085 UIThreadManager.class,
086 EXECUTE_INSIDE_UI_SYNC_RUNNER,
087 EXEC_METHOD_ARGS);
088
089 private static final String EXECUTE_INSIDE_UI_ASYNC = "execInsideUIAsync";
090 private static final RunnableWithArgs EXECUTE_INSIDE_UI_ASYNC_RUNNER = new RunnableRunner(EXECUTE_INSIDE_UI_ASYNC) {
091 protected void withRunnable(Runnable runnable) {
092 INSTANCE.executeAsync(runnable);
093 }
094 };
095 private static final Closure EXECUTE_INSIDE_UI_ASYNC_CLOSURE = new RunnableWithArgsClosure(
096 INSTANCE,
097 EXECUTE_INSIDE_UI_ASYNC_RUNNER);
098 private static final MetaMethod EXECUTE_INSIDE_UI_ASYNC_METHOD = new RunnableWithArgsMetaMethod(
099 EXECUTE_INSIDE_UI_ASYNC,
100 UIThreadManager.class,
101 EXECUTE_INSIDE_UI_ASYNC_RUNNER,
102 EXEC_METHOD_ARGS);
103
104 private static final String EXECUTE_OUTSIDE_UI = "execOutsideUI";
105 private static final RunnableWithArgs EXECUTE_OUTSIDE_UI_RUNNER = new RunnableRunner(EXECUTE_OUTSIDE_UI) {
106 protected void withRunnable(Runnable runnable) {
107 INSTANCE.executeOutside(runnable);
108 }
109 };
110 private static final Closure EXECUTE_OUTSIDE_UI_CLOSURE = new RunnableWithArgsClosure(
111 INSTANCE,
112 EXECUTE_OUTSIDE_UI_RUNNER);
113 private static final MetaMethod EXECUTE_OUTSIDE_UI_METHOD = new RunnableWithArgsMetaMethod(
114 EXECUTE_OUTSIDE_UI,
115 UIThreadManager.class,
116 EXECUTE_OUTSIDE_UI_RUNNER,
117 EXEC_METHOD_ARGS);
118
119 private static final String IS_UITHREAD = "isUIThread";
120 private static final CallableWithArgs IS_UITHREAD_CALLABLE = new CallableWithArgs<Boolean>() {
121 public Boolean call(Object[] args) {
122 if (args.length == 0) {
123 return INSTANCE.isUIThread();
124 }
125 throw new MissingMethodException(IS_UITHREAD, UIThreadManager.class, args);
126 }
127 };
128 private static final Closure IS_UITHREAD_CLOSURE = new CallableWithArgsClosure(
129 INSTANCE,
130 IS_UITHREAD_CALLABLE);
131 private static final MetaMethod IS_UITHREAD_METHOD = new CallableWithArgsMetaMethod(
132 IS_UITHREAD,
133 UIThreadManager.class,
134 IS_UITHREAD_CALLABLE,
135 new Class[0]);
136
137 private static final String EXECUTE_FUTURE = "execFuture";
138 private static final Closure EXECUTE_FUTURE_CLOSURE = new CallableWithArgsClosure(
139 new CallableWithArgs<Future>() {
140 public Future call(Object[] args) {
141 if (args.length == 1 && args[0] instanceof Callable) {
142 return INSTANCE.executeFuture((Callable) args[0]);
143 } else if (args.length == 2 && args[0] instanceof ExecutorService && args[1] instanceof Callable) {
144 return INSTANCE.executeFuture((ExecutorService) args[0], (Callable) args[1]);
145 }
146 throw new MissingMethodException(EXECUTE_FUTURE, UIThreadManager.class, args);
147 }
148 }
149 );
150 private static final MetaMethod EXECUTE_FUTURE_METHOD1 = new CallableWithArgsMetaMethod(
151 EXECUTE_FUTURE,
152 UIThreadManager.class,
153 new CallableWithArgs<Future>() {
154 public Future call(Object[] args) {
155 if (args.length == 1 && args[0] instanceof Callable) {
156 return INSTANCE.executeFuture((Callable) args[0]);
157 }
158 throw new MissingMethodException(EXECUTE_FUTURE, UIThreadManager.class, args);
159 }
160 },
161 new Class[]{Callable.class});
162 private static final MetaMethod EXECUTE_FUTURE_METHOD2 = new CallableWithArgsMetaMethod(
163 EXECUTE_FUTURE,
164 UIThreadManager.class,
165 new CallableWithArgs<Future>() {
166 public Future call(Object[] args) {
167 if (args.length == 2 && args[0] instanceof ExecutorService && args[1] instanceof Callable) {
168 return INSTANCE.executeFuture((ExecutorService) args[0], (Callable) args[1]);
169 }
170 throw new MissingMethodException(EXECUTE_FUTURE, UIThreadManager.class, args);
171 }
172 },
173 new Class[]{ExecutorService.class, Callable.class});
174
175 public static final String[] THREADING_METHOD_NAMES = new String[]{
176 EXECUTE_INSIDE_UI_SYNC,
177 EXECUTE_INSIDE_UI_SYNC,
178 EXECUTE_OUTSIDE_UI,
179 IS_UITHREAD,
180 EXECUTE_FUTURE
181 };
182
183 public static void enhance(Script script) {
184 if (script instanceof ThreadingHandler) return;
185 if (LOG.isTraceEnabled()) {
186 LOG.trace("Enhancing script " + script);
187 }
188
189 script.getBinding().setVariable(EXECUTE_INSIDE_UI_SYNC, EXECUTE_INSIDE_UI_SYNC_CLOSURE);
190 script.getBinding().setVariable(EXECUTE_INSIDE_UI_ASYNC, EXECUTE_INSIDE_UI_ASYNC_CLOSURE);
191 script.getBinding().setVariable(EXECUTE_OUTSIDE_UI, EXECUTE_OUTSIDE_UI_CLOSURE);
192 script.getBinding().setVariable(IS_UITHREAD, IS_UITHREAD_CLOSURE);
193 script.getBinding().setVariable(EXECUTE_FUTURE, EXECUTE_FUTURE_CLOSURE);
194 }
195
196 public static void enhance(MetaClass mc) {
197 if (null == mc) return;
198
199 ExpandoMetaClass emc = null;
200 if (mc instanceof DelegatingMetaClass) {
201 mc = ((DelegatingMetaClass) mc).getAdaptee();
202 }
203 if (mc instanceof ExpandoMetaClass) {
204 emc = (ExpandoMetaClass) mc;
205 }
206 if (null == emc) return;
207
208 if (LOG.isTraceEnabled()) {
209 LOG.trace("Enhancing metaClass " + emc);
210 }
211
212 emc.registerInstanceMethod(EXECUTE_INSIDE_UI_SYNC_METHOD);
213 emc.registerInstanceMethod(EXECUTE_INSIDE_UI_ASYNC_METHOD);
214 emc.registerInstanceMethod(EXECUTE_OUTSIDE_UI_METHOD);
215 emc.registerInstanceMethod(IS_UITHREAD_METHOD);
216 emc.registerInstanceMethod(EXECUTE_FUTURE_METHOD1);
217 emc.registerInstanceMethod(EXECUTE_FUTURE_METHOD2);
218 }
219
220 public void setUIThreadHandler(UIThreadHandler threadHandler) {
221 this.uiThreadHandler = threadHandler;
222 }
223
224 public UIThreadHandler getUIThreadHandler() {
225 if (this.uiThreadHandler == null) {
226 try {
227 // attempt loading of default UIThreadHandler -> Swing
228 setUIThreadHandler((UIThreadHandler) ApplicationClassLoader.get().loadClass("griffon.swing.SwingUIThreadHandler").newInstance());
229 } catch (ClassNotFoundException e) {
230 throw new IllegalStateException("Can't locate a suitable UIThreadHandler.", e);
231 } catch (InstantiationException e) {
232 throw new IllegalStateException("Can't locate a suitable UIThreadHandler.", e);
233 } catch (IllegalAccessException e) {
234 throw new IllegalStateException("Can't locate a suitable UIThreadHandler.", e);
235 }
236 }
237 return this.uiThreadHandler;
238 }
239
240 /**
241 * True if the current thread is the UI thread.
242 *
243 * @return true if the current thread is the UI thread, false otherwise.
244 */
245 public boolean isUIThread() {
246 return getUIThreadHandler().isUIThread();
247 }
248
249 /**
250 * Executes a code block asynchronously on the UI thread.
251 *
252 * @param runnable a code block to be executed
253 */
254 public void executeAsync(Runnable runnable) {
255 getUIThreadHandler().executeAsync(runnable);
256 }
257
258 /**
259 * Executes a code block asynchronously on the UI thread.
260 *
261 * @param script a code block to be executed
262 */
263 public void executeAsync(final Script script) {
264 getUIThreadHandler().executeAsync(new Runnable() {
265 public void run() {
266 script.run();
267 }
268 });
269 }
270
271 /**
272 * Executes a code block synchronously on the UI thread.
273 *
274 * @param runnable a code block to be executed
275 */
276 public void executeSync(Runnable runnable) {
277 getUIThreadHandler().executeSync(runnable);
278 }
279
280 /**
281 * Executes a code block synchronously on the UI thread.
282 *
283 * @param script a code block to be executed
284 */
285 public void executeSync(final Script script) {
286 getUIThreadHandler().executeSync(new Runnable() {
287 public void run() {
288 script.run();
289 }
290 });
291 }
292
293 /**
294 * Executes a code block outside of the UI thread.
295 *
296 * @param runnable a code block to be executed
297 */
298 public void executeOutside(Runnable runnable) {
299 getUIThreadHandler().executeOutside(runnable);
300 }
301
302 /**
303 * Executes a code block outside of the UI thread.
304 *
305 * @param script a code block to be executed
306 */
307 public void executeOutside(final Script script) {
308 getUIThreadHandler().executeOutside(new Runnable() {
309 public void run() {
310 script.run();
311 }
312 });
313 }
314
315 /**
316 * Executes a code block as a Future on an ExecutorService.
317 *
318 * @param callable a code block to be executed
319 * @return a Future that contains the result of the execution
320 */
321 public <R> Future<R> executeFuture(Callable<R> callable) {
322 return executeFuture(DEFAULT_EXECUTOR_SERVICE, callable);
323 }
324
325 /**
326 * Executes a code block as a Future on an ExecutorService.
327 *
328 * @param executorService the ExecutorService to use. Will use the default ExecutorService if null.
329 * @param callable a code block to be executed
330 * @return a Future that contains the result of the execution
331 */
332 public <R> Future<R> executeFuture(ExecutorService executorService, Callable<R> callable) {
333 executorService = executorService != null ? executorService : DEFAULT_EXECUTOR_SERVICE;
334 return executorService.submit(callable);
335 }
336 }
|