UIThreadManager.java
001 /*
002  * Copyright 2009-2012 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.ExpandoMetaClass;
020 import groovy.lang.MetaClass;
021 import groovy.lang.MissingMethodException;
022 import groovy.lang.Script;
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 = 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         if (LOG.isDebugEnabled()) {
051             LOG.debug("Default Executor set to run with " + Runtime.getRuntime().availableProcessors() " processors");
052         }
053     }
054 
055     private static abstract class ScriptOrRunnableRunner extends RunnableWithArgs {
056         public void run(Object[] args) {
057             if (args != null && args.length == 1) {
058                 if (args[0instanceof Script) {
059                     withScript((Scriptargs[0]);
060                     return;
061                 else if (args[0instanceof Runnable) {
062                     withRunnable((Runnableargs[0]);
063                     return;
064                 }
065             }
066             throw new MissingMethodException(getMethodName(), UIThreadManager.class, args);
067         }
068 
069         protected abstract String getMethodName();
070 
071         protected abstract void withScript(Script script);
072 
073         protected abstract void withRunnable(Runnable runnable);
074     }
075 
076     private static final String EXECUTE_INSIDE_UI_SYNC = "execInsideUISync";
077     private static final RunnableWithArgsClosure EXECUTE_INSIDE_UI_SYNC_RUNNER = new RunnableWithArgsClosure(INSTANCE,
078             new ScriptOrRunnableRunner() {
079                 protected String getMethodName() {
080                     return EXECUTE_INSIDE_UI_SYNC;
081                 }
082 
083                 protected void withScript(Script script) {
084                     INSTANCE.executeSync(script);
085                 }
086 
087                 protected void withRunnable(Runnable runnable) {
088                     INSTANCE.executeSync(runnable);
089                 }
090             });
091 
092     private static final String EXECUTE_INSIDE_UI_ASYNC = "execInsideUIAsync";
093     private static final RunnableWithArgsClosure EXECUTE_INSIDE_UI_ASYNC_RUNNER = new RunnableWithArgsClosure(INSTANCE,
094             new ScriptOrRunnableRunner() {
095                 protected String getMethodName() {
096                     return EXECUTE_INSIDE_UI_ASYNC;
097                 }
098 
099                 protected void withScript(Script script) {
100                     INSTANCE.executeAsync(script);
101                 }
102 
103                 protected void withRunnable(Runnable runnable) {
104                     INSTANCE.executeAsync(runnable);
105                 }
106             });
107 
108     private static final String EXECUTE_OUTSIDE_UI = "execOutsideUI";
109     private static final RunnableWithArgsClosure EXECUTE_OUTSIDE_UI_RUNNER = new RunnableWithArgsClosure(INSTANCE,
110             new ScriptOrRunnableRunner() {
111                 protected String getMethodName() {
112                     return EXECUTE_OUTSIDE_UI;
113                 }
114 
115                 protected void withScript(Script script) {
116                     INSTANCE.executeOutside(script);
117                 }
118 
119                 protected void withRunnable(Runnable runnable) {
120                     INSTANCE.executeOutside(runnable);
121                 }
122             });
123 
124     private static final String IS_UITHREAD = "isUIThread";
125     private static final CallableWithArgsClosure IS_UITHREAD_RUNNER = new CallableWithArgsClosure(INSTANCE,
126             new CallableWithArgs<Boolean>() {
127                 public Boolean call(Object[] args) {
128                     if (args.length == 0) {
129                         return INSTANCE.isUIThread();
130                     }
131                     throw new MissingMethodException(IS_UITHREAD, UIThreadManager.class, args);
132                 }
133             });
134 
135     private static final String EXECUTE_FUTURE = "executeFuture";
136     private static final CallableWithArgsClosure EXECUTE_FUTURE_RUNNER = new CallableWithArgsClosure(INSTANCE,
137             new CallableWithArgs<Future>() {
138                 public Future call(Object[] args) {
139                     if (args.length == && args[0instanceof Callable) {
140                         return INSTANCE.executeFuture((Callableargs[0]);
141                     else if (args.length == && args[0instanceof ExecutorService && args[1instanceof Callable) {
142                         return INSTANCE.executeFuture((ExecutorServiceargs[0](Callableargs[1]);
143                     }
144                     throw new MissingMethodException(EXECUTE_FUTURE, UIThreadManager.class, args);
145                 }
146             });
147 
148     public static void enhance(Script script) {
149         if (script instanceof ThreadingHandlerreturn;
150         if (LOG.isTraceEnabled()) {
151             LOG.trace("Enhancing script " + script);
152         }
153         // TODO @deprecated - remove before 1.0
154         script.getBinding().setVariable("execSync", EXECUTE_INSIDE_UI_SYNC_RUNNER);
155         script.getBinding().setVariable("execAsync", EXECUTE_INSIDE_UI_ASYNC_RUNNER);
156         script.getBinding().setVariable("execOutside", EXECUTE_OUTSIDE_UI_RUNNER);
157 
158         script.getBinding().setVariable(EXECUTE_INSIDE_UI_SYNC, EXECUTE_INSIDE_UI_SYNC_RUNNER);
159         script.getBinding().setVariable(EXECUTE_INSIDE_UI_SYNC, EXECUTE_INSIDE_UI_ASYNC_RUNNER);
160         script.getBinding().setVariable(EXECUTE_OUTSIDE_UI, EXECUTE_OUTSIDE_UI_RUNNER);
161         script.getBinding().setVariable(IS_UITHREAD, IS_UITHREAD_RUNNER);
162         script.getBinding().setVariable(EXECUTE_FUTURE, EXECUTE_FUTURE_RUNNER);
163     }
164 
165     public static void enhance(MetaClass metaClass) {
166         if (metaClass instanceof ExpandoMetaClass) {
167             ExpandoMetaClass mc = (ExpandoMetaClassmetaClass;
168             if (LOG.isTraceEnabled()) {
169                 LOG.trace("Enhancing metaClass " + metaClass);
170             }
171             // TODO @deprecated - remove before 1.0
172             mc.registerInstanceMethod("execSync", EXECUTE_INSIDE_UI_SYNC_RUNNER);
173             mc.registerInstanceMethod("execAsync", EXECUTE_INSIDE_UI_ASYNC_RUNNER);
174             mc.registerInstanceMethod("execOutside", EXECUTE_OUTSIDE_UI_RUNNER);
175 
176             mc.registerInstanceMethod(EXECUTE_INSIDE_UI_SYNC, EXECUTE_INSIDE_UI_SYNC_RUNNER);
177             mc.registerInstanceMethod(EXECUTE_INSIDE_UI_ASYNC, EXECUTE_INSIDE_UI_ASYNC_RUNNER);
178             mc.registerInstanceMethod(EXECUTE_OUTSIDE_UI, EXECUTE_OUTSIDE_UI_RUNNER);
179             mc.registerInstanceMethod(IS_UITHREAD, IS_UITHREAD_RUNNER);
180             mc.registerInstanceMethod(EXECUTE_FUTURE, EXECUTE_FUTURE_RUNNER);
181         }
182     }
183 
184     public void setUIThreadHandler(UIThreadHandler threadHandler) {
185         if (this.uiThreadHandler != null) {
186             if (LOG.isWarnEnabled()) {
187                 LOG.warn("UIThreadHandler is already set, it can't be changed!");
188             }
189         else {
190             this.uiThreadHandler = threadHandler;
191         }
192     }
193 
194     public UIThreadHandler getUIThreadHandler() {
195         if (this.uiThreadHandler == null) {
196             try {
197                 // attempt loading of default UIThreadHandler -> Swing
198                 setUIThreadHandler((UIThreadHandlergetClass().getClassLoader().loadClass("griffon.swing.SwingUIThreadHandler").newInstance());
199             catch (ClassNotFoundException e) {
200                 throw new IllegalStateException("Can't locate a suitable UIThreadHandler.", e);
201             catch (InstantiationException e) {
202                 throw new IllegalStateException("Can't locate a suitable UIThreadHandler.", e);
203             catch (IllegalAccessException e) {
204                 throw new IllegalStateException("Can't locate a suitable UIThreadHandler.", e);
205             }
206         }
207         return this.uiThreadHandler;
208     }
209 
210     /**
211      * True if the current thread is the UI thread.
212      *
213      @return true if the current thread is the UI thread, false otherwise.
214      */
215     public boolean isUIThread() {
216         return getUIThreadHandler().isUIThread();
217     }
218 
219     /**
220      * Executes a code block asynchronously on the UI thread.
221      *
222      @param runnable a code block to be executed
223      */
224     public void executeAsync(Runnable runnable) {
225         getUIThreadHandler().executeAsync(runnable);
226     }
227 
228     /**
229      * Executes a code block asynchronously on the UI thread.
230      *
231      @param script a code block to be executed
232      */
233     public void executeAsync(final Script script) {
234         getUIThreadHandler().executeAsync(new Runnable() {
235             public void run() {
236                 script.run();
237             }
238         });
239     }
240 
241     /**
242      * Executes a code block synchronously on the UI thread.
243      *
244      @param runnable a code block to be executed
245      */
246     public void executeSync(Runnable runnable) {
247         getUIThreadHandler().executeSync(runnable);
248     }
249 
250     /**
251      * Executes a code block synchronously on the UI thread.
252      *
253      @param script a code block to be executed
254      */
255     public void executeSync(final Script script) {
256         getUIThreadHandler().executeSync(new Runnable() {
257             public void run() {
258                 script.run();
259             }
260         });
261     }
262 
263     /**
264      * Executes a code block outside of the UI thread.
265      *
266      @param runnable a code block to be executed
267      */
268     public void executeOutside(Runnable runnable) {
269         getUIThreadHandler().executeOutside(runnable);
270     }
271 
272     /**
273      * Executes a code block outside of the UI thread.
274      *
275      @param script a code block to be executed
276      */
277     public void executeOutside(final Script script) {
278         getUIThreadHandler().executeOutside(new Runnable() {
279             public void run() {
280                 script.run();
281             }
282         });
283     }
284 
285     /**
286      * Executes a code block as a Future on an ExecutorService.
287      *
288      @param callable a code block to be executed
289      @return a Future that contains the result of the execution
290      */
291     public Future executeFuture(Callable<?> callable) {
292         return executeFuture(DEFAULT_EXECUTOR_SERVICE, callable);
293     }
294 
295     /**
296      * Executes a code block as a Future on an ExecutorService.
297      *
298      @param executorService the ExecutorService to use. Will use the default ExecutorService if null.
299      @param callable        a code block to be executed
300      @return a Future that contains the result of the execution
301      */
302     public Future executeFuture(ExecutorService executorService, Callable<?> callable) {
303         executorService = executorService != null ? executorService : DEFAULT_EXECUTOR_SERVICE;
304         return executorService.submit(callable);
305     }
306 }