001    /*
002     * Created on Aug 14, 2008
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
005     * in compliance with the License. You may obtain a copy of the License at
006     *
007     * http://www.apache.org/licenses/LICENSE-2.0
008     *
009     * Unless required by applicable law or agreed to in writing, software distributed under the License
010     * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
011     * or implied. See the License for the specific language governing permissions and limitations under
012     * the License.
013     *
014     * Copyright @2008-2010 the original author or authors.
015     */
016    package org.fest.swing.edt;
017    
018    import static javax.swing.SwingUtilities.invokeLater;
019    import static javax.swing.SwingUtilities.isEventDispatchThread;
020    import static org.fest.util.Throwables.appendCurrentThreadStackTraceToThrowable;
021    import static org.fest.swing.exception.UnexpectedException.unexpected;
022    
023    import java.util.concurrent.CountDownLatch;
024    
025    import net.jcip.annotations.GuardedBy;
026    import net.jcip.annotations.ThreadSafe;
027    
028    import org.fest.swing.exception.UnexpectedException;
029    
030    /**
031     * Understands running instances of <code>{@link GuiQuery}</code> and <code>{@link GuiTask}</code>.
032     *
033     * @author Alex Ruiz
034     */
035    @ThreadSafe
036    public class GuiActionRunner {
037    
038      @GuardedBy("this")
039      private static boolean executeInEDT = true;
040    
041      /**
042       * Indicates <code>{@link GuiActionRunner}</code> if instances of <code>{@link GuiQuery}</code> and
043       * <code>{@link GuiTask}</code> should be executed in the event dispatch thread or not.
044       * @param b if <code>true</code>, GUI actions are executed in the event dispatch thread. If <code>false</code>,
045       * GUI actions are executed in the current thread.
046       */
047      public static synchronized void executeInEDT(boolean b) {
048        executeInEDT = b;
049      }
050    
051      /**
052       * Returns whether instances of <code>{@link GuiQuery}</code> and <code>{@link GuiTask}</code> should be executed in
053       * the event dispatch thread or not.
054       * @return <code>true</code> if GUI actions are executed in the event dispatch thread, <code>false</code> otherwise.
055       */
056      public static synchronized boolean executeInEDT() {
057        return executeInEDT;
058      }
059    
060      /**
061       * Executes the given query in the event dispatch thread. This method waits until the query has finished its
062       * execution.
063       * @param <T> the generic type of the return value.
064       * @param query the query to execute.
065       * @return the result of the query executed in the main thread.
066       * @throws UnexpectedException wrapping any <b>checked</b> exception thrown when executing the given query in the
067       * event dispatch thread. Unchecked exceptions are re-thrown without any wrapping.
068       * @see #executeInEDT()
069       */
070      public static <T> T execute(GuiQuery<T> query) {
071        if (!executeInEDT) return executeInCurrentThread(query);
072        run(query);
073        return resultOf(query);
074      }
075    
076      private static <T> T executeInCurrentThread(GuiQuery<T> query) {
077        try {
078          return query.executeInEDT();
079        } catch (Throwable e) {
080          throw unexpected(e);
081        }
082      }
083    
084      /**
085       * Executes the given task in the event dispatch thread. This method waits until the task has finished its execution.
086       * @param task the task to execute.
087       * @throws UnexpectedException wrapping any <b>checked</b> exception thrown when executing the given query in the
088       * event dispatch thread. Unchecked exceptions are re-thrown without any wrapping.
089       * @see #executeInEDT()
090       */
091      public static void execute(GuiTask task) {
092        if (!executeInEDT) {
093          executeInCurrentThread(task);
094          return;
095        }
096        run(task);
097        rethrowCatchedExceptionIn(task);
098      }
099    
100      private static void executeInCurrentThread(GuiTask task) {
101        try {
102          task.executeInEDT();
103        } catch (Throwable e) {
104          throw unexpected(e);
105        }
106      }
107    
108      private static void run(final GuiAction action) {
109        if (isEventDispatchThread()) {
110          action.run();
111          return;
112        }
113        final CountDownLatch latch = new CountDownLatch(1);
114        action.executionNotification(latch);
115        invokeLater(action);
116        try {
117          latch.await();
118        } catch (final InterruptedException e) {
119          Thread.currentThread().interrupt();
120        }
121      }
122    
123      private static <T> T resultOf(GuiQuery<T> query) {
124        T result = query.result();
125        query.clearResult();
126        rethrowCatchedExceptionIn(query);
127        return result;
128      }
129    
130      /**
131       * Wraps (with a <code>{@link UnexpectedException}</code>) and retrows any catched exception in the given action.
132       * @param action the given action that may have a catched exception during its execution.
133       * @throws UnexpectedException wrapping any <b>checked</b> exception thrown when executing the given query in the
134       * event dispatch thread. Unchecked exceptions are rethrown without any wrapping.
135       */
136      private static void rethrowCatchedExceptionIn(GuiAction action) {
137        Throwable catchedException = action.catchedException();
138        action.clearCatchedException();
139        if (catchedException == null) return;
140        if (catchedException instanceof RuntimeException) {
141          appendCurrentThreadStackTraceToThrowable(catchedException, "execute");
142          throw (RuntimeException)catchedException;
143        }
144        if (catchedException instanceof Error) {
145          catchedException.fillInStackTrace();
146          throw (Error)catchedException;
147        }
148        throw unexpected(catchedException);
149      }
150    }