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}