UIThreadManager.java
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[0instanceof Runnable) {
062                     withRunnable((Runnableargs[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 == && args[0instanceof Callable) {
142                         return INSTANCE.executeFuture((Callableargs[0]);
143                     else if (args.length == && args[0instanceof ExecutorService && args[1instanceof Callable) {
144                         return INSTANCE.executeFuture((ExecutorServiceargs[0](Callableargs[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 == && args[0instanceof Callable) {
156                         return INSTANCE.executeFuture((Callableargs[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 == && args[0instanceof ExecutorService && args[1instanceof Callable) {
168                         return INSTANCE.executeFuture((ExecutorServiceargs[0](Callableargs[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 ThreadingHandlerreturn;
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 == mcreturn;
198 
199         ExpandoMetaClass emc = null;
200         if (mc instanceof DelegatingMetaClass) {
201             mc = ((DelegatingMetaClassmc).getAdaptee();
202         }
203         if (mc instanceof ExpandoMetaClass) {
204             emc = (ExpandoMetaClassmc;
205         }
206         if (null == emcreturn;
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((UIThreadHandlerApplicationClassLoader.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 }