001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2017
005 * Space Science and Engineering Center (SSEC)
006 * University of Wisconsin - Madison
007 * 1225 W. Dayton Street, Madison, WI 53706, USA
008 * https://www.ssec.wisc.edu/mcidas
009 *
010 * All Rights Reserved
011 *
012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013 * some McIDAS-V source code is based on IDV and VisAD source code.
014 *
015 * McIDAS-V is free software; you can redistribute it and/or modify
016 * it under the terms of the GNU Lesser Public License as published by
017 * the Free Software Foundation; either version 3 of the License, or
018 * (at your option) any later version.
019 *
020 * McIDAS-V is distributed in the hope that it will be useful,
021 * but WITHOUT ANY WARRANTY; without even the implied warranty of
022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023 * GNU Lesser Public License for more details.
024 *
025 * You should have received a copy of the GNU Lesser Public License
026 * along with this program.  If not, see http://www.gnu.org/licenses.
027 */
028package edu.wisc.ssec.mcidasv.util.nativepathchooser;
029
030import javax.swing.SwingUtilities;
031
032import javafx.application.Platform;
033
034import java.util.concurrent.Callable;
035import java.util.concurrent.CountDownLatch;
036import java.util.concurrent.ExecutionException;
037import java.util.concurrent.FutureTask;
038import java.util.concurrent.TimeUnit;
039import java.util.concurrent.atomic.AtomicBoolean;
040
041// taken from http://stackoverflow.com/a/30004156
042
043/**
044 * A utility class to execute a Callable synchronously
045 * on the JavaFX event thread.
046 * 
047 * @param <T> Return type of the {@link Callable}.
048 */
049public class SynchronousJFXCaller<T> {
050    private final Callable<T> callable;
051
052    /**
053     * Constructs a new caller that will execute the provided callable.
054     * 
055     * The callable is accessed from the JavaFX event thread, so it should either
056     * be immutable or at least its state shouldn't be changed randomly while
057     * the call() method is in progress.
058     * 
059     * @param callable Action to execute on the JFX event thread.
060     */
061    public SynchronousJFXCaller(Callable<T> callable) {
062        this.callable = callable;
063    }
064
065    /**
066     * Executes the Callable.
067     * <p>
068     * A specialized task is run using Platform.runLater(). The calling thread
069     * then waits first for the task to start, then for it to return a result.
070     * Any exception thrown by the Callable will be rethrown in the calling
071     * thread.
072     * </p>
073     * @param startTimeout time to wait for Platform.runLater() to <em>start</em>
074     * the dialog-showing task
075     * @param startTimeoutUnit the time unit of the startTimeout argument
076     * @return whatever the Callable returns
077     * @throws IllegalStateException if Platform.runLater() fails to start
078     * the task within the given timeout
079     * @throws InterruptedException if the calling (this) thread is interrupted
080     * while waiting for the task to start or to get its result (note that the
081     * task will still run anyway and its result will be ignored)
082     */
083    public T call(long startTimeout, TimeUnit startTimeoutUnit)
084            throws Exception {
085        final CountDownLatch taskStarted = new CountDownLatch(1);
086        // Can't use volatile boolean here because only finals can be accessed
087        // from closures like the lambda expression below.
088        final AtomicBoolean taskCancelled = new AtomicBoolean(false);
089        // disabling the modality emulation (for now?)
090        // a trick to emulate modality:
091        // final JDialog modalBlocker = new JDialog();
092        // modalBlocker.setModal(true);
093        // modalBlocker.setModalityType(Dialog.ModalityType.DOCUMENT_MODAL);
094        // modalBlocker.setUndecorated(true);
095        // modalBlocker.setOpacity(0.0f);
096        // modalBlocker.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
097        // final CountDownLatch modalityLatch = new CountDownLatch(1);
098        final FutureTask<T> task = new FutureTask<T>(() -> {
099            synchronized (taskStarted) {
100                if (taskCancelled.get()) {
101                    return null;
102                } else {
103                    taskStarted.countDown();
104                }
105            }
106            try {
107                return callable.call();
108            } finally {
109                // Wait until the Swing thread is blocked in setVisible():
110                // modalityLatch.await();
111                // and unblock it:
112                // SwingUtilities.invokeLater(() ->
113                //         modalBlocker.setVisible(false));
114            }
115        });
116        Platform.runLater(task);
117        if (!taskStarted.await(startTimeout, startTimeoutUnit)) {
118            synchronized (taskStarted) {
119                // the last chance, it could have been started just now
120                if (!taskStarted.await(0, TimeUnit.MILLISECONDS)) {
121                    // Can't use task.cancel() here because it would
122                    // interrupt the JavaFX thread, which we don't own.
123                    taskCancelled.set(true);
124                    throw new IllegalStateException("JavaFX was shut down"
125                            + " or is unresponsive");
126                }
127            }
128        }
129        // a trick to notify the task AFTER we have been blocked
130        // in setVisible()
131        SwingUtilities.invokeLater(() -> {
132            // notify that we are ready to get the result:
133//            modalityLatch.countDown();
134        });
135//        modalBlocker.setVisible(true); // blocks
136//        modalBlocker.dispose(); // release resources
137        try {
138            return task.get();
139        } catch (ExecutionException ex) {
140            Throwable ec = ex.getCause();
141            if (ec instanceof Exception) {
142                throw (Exception) ec;
143            } else if (ec instanceof Error) {
144                throw (Error) ec;
145            } else {
146                throw new AssertionError("Unexpected exception type", ec);
147            }
148        }
149    }
150
151}