Merge pull request #32 from Tillerino/injection

Dependency injection instead of static instances
This commit is contained in:
Sebastian Stenzel 2015-02-14 16:34:19 +01:00
commit 09c26f5e86
16 changed files with 465 additions and 167 deletions

View File

@ -38,16 +38,11 @@ public final class WebDavServer {
private static final int MAX_THREADS = 200;
private static final int MIN_THREADS = 4;
private static final int THREAD_IDLE_SECONDS = 20;
private static final WebDavServer INSTANCE = new WebDavServer();
private final Server server;
private final ServerConnector localConnector;
private final ContextHandlerCollection servletCollection;
public static WebDavServer getInstance() {
return INSTANCE;
}
private WebDavServer() {
public WebDavServer() {
final BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(MAX_PENDING_REQUESTS);
final ThreadPool tp = new QueuedThreadPool(MAX_THREADS, MIN_THREADS, THREAD_IDLE_SECONDS, queue);
server = new Server(tp);

View File

@ -34,7 +34,6 @@
<commons-codec.version>1.10</commons-codec.version>
<jackson-databind.version>2.4.4</jackson-databind.version>
<mockito.version>1.10.19</mockito.version>
<axetDesktop.version>2.2.3</axetDesktop.version>
</properties>
<dependencyManagement>
@ -126,12 +125,6 @@
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.axet</groupId>
<artifactId>desktop</artifactId>
<version>${axetDesktop.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

View File

@ -60,7 +60,14 @@
<dependency>
<groupId>com.github.axet</groupId>
<artifactId>desktop</artifactId>
<version>2.2.3</version>
</dependency> -->
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>3.0</version>
</dependency>
</dependencies>
<build>

View File

@ -14,7 +14,6 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ResourceBundle;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javafx.application.Application;
import javafx.application.Platform;
@ -25,14 +24,16 @@ import javafx.stage.Stage;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.ui.model.Vault;
import org.cryptomator.ui.settings.Settings;
import org.cryptomator.ui.MainModule.ControllerFactory;
import org.cryptomator.ui.util.ActiveWindowStyleSupport;
import org.cryptomator.ui.util.DeferredCloser;
import org.cryptomator.ui.util.SingleInstanceManager;
import org.cryptomator.ui.util.SingleInstanceManager.LocalInstance;
import org.cryptomator.ui.util.TrayIconUtil;
import org.cryptomator.webdav.WebDavServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.inject.Guice;
import com.google.inject.Injector;
public class MainApplication extends Application {
@ -40,7 +41,38 @@ public class MainApplication extends Application {
private static final Logger LOG = LoggerFactory.getLogger(MainApplication.class);
private ExecutorService executorService;
private final CleanShutdownPerformer cleanShutdownPerformer = new CleanShutdownPerformer();
private final ExecutorService executorService;
private final ControllerFactory controllerFactory;
private final DeferredCloser closer;
public MainApplication() {
this(getInjector());
}
private static Injector getInjector() {
try {
return Guice.createInjector(new MainModule());
} catch (Exception e) {
throw e;
}
}
public MainApplication(Injector injector) {
this(injector.getInstance(ExecutorService.class),
injector.getInstance(ControllerFactory.class),
injector.getInstance(DeferredCloser.class));
}
public MainApplication(ExecutorService executorService, ControllerFactory controllerFactory, DeferredCloser closer) {
super();
this.executorService = executorService;
this.controllerFactory = controllerFactory;
this.closer = closer;
}
@Override
public void start(final Stage primaryStage) throws IOException {
@ -55,12 +87,12 @@ public class MainApplication extends Application {
}
});
executorService = Executors.newCachedThreadPool();
Runtime.getRuntime().addShutdownHook(cleanShutdownPerformer);
WebDavServer.getInstance().start();
chooseNativeStylesheet();
final ResourceBundle rb = ResourceBundle.getBundle("localization");
final FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/main.fxml"), rb);
loader.setControllerFactory(controllerFactory);
final Parent root = loader.load();
final MainController ctrl = loader.getController();
ctrl.setStage(primaryStage);
@ -83,14 +115,10 @@ public class MainApplication extends Application {
Main.OPEN_FILE_HANDLER.complete(file -> handleCommandLineArg(ctrl, file.getAbsolutePath()));
}
final LocalInstance cryptomatorGuiInstance = SingleInstanceManager.startLocalInstance(APPLICATION_KEY, executorService);
cryptomatorGuiInstance.registerListener(arg -> handleCommandLineArg(ctrl, arg));
LocalInstance cryptomatorGuiInstance = closer.closeLater(
SingleInstanceManager.startLocalInstance(APPLICATION_KEY, executorService), LocalInstance::close).get().get();
Main.addShutdownTask(() -> {
cryptomatorGuiInstance.close();
Settings.save();
executorService.shutdown();
});
cryptomatorGuiInstance.registerListener(arg -> handleCommandLineArg(ctrl, arg));
}
void handleCommandLineArg(final MainController ctrl, String arg) {
@ -131,11 +159,27 @@ public class MainApplication extends Application {
private void quit() {
Platform.runLater(() -> {
WebDavServer.getInstance().stop();
Settings.save();
stop();
Platform.exit();
System.exit(0);
});
}
@Override
public void stop() {
closer.close();
try {
Runtime.getRuntime().removeShutdownHook(cleanShutdownPerformer);
} catch (Exception e) {
}
}
private class CleanShutdownPerformer extends Thread {
@Override
public void run() {
closer.close();
}
}
}

View File

@ -39,6 +39,7 @@ import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import org.cryptomator.ui.InitializeController.InitializationListener;
import org.cryptomator.ui.MainModule.ControllerFactory;
import org.cryptomator.ui.UnlockController.UnlockListener;
import org.cryptomator.ui.UnlockedController.LockListener;
import org.cryptomator.ui.controls.DirectoryListCell;
@ -47,6 +48,8 @@ import org.cryptomator.ui.settings.Settings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.inject.Inject;
public class MainController implements Initializable, InitializationListener, UnlockListener, LockListener {
private static final Logger LOG = LoggerFactory.getLogger(MainController.class);
@ -73,11 +76,22 @@ public class MainController implements Initializable, InitializationListener, Un
private ResourceBundle rb;
private final ControllerFactory controllerFactory;
private final Settings settings;
@Inject
public MainController(ControllerFactory controllerFactory, Settings settings) {
super();
this.controllerFactory = controllerFactory;
this.settings = settings;
}
@Override
public void initialize(URL url, ResourceBundle rb) {
this.rb = rb;
final ObservableList<Vault> items = FXCollections.observableList(Settings.load().getDirectories());
final ObservableList<Vault> items = FXCollections.observableList(settings.getDirectories());
directoryList.setItems(items);
directoryList.setCellFactory(this::createDirecoryListCell);
directoryList.getSelectionModel().getSelectedItems().addListener(this::selectedDirectoryDidChange);
@ -202,6 +216,7 @@ public class MainController implements Initializable, InitializationListener, Un
private <T> T showView(String fxml) {
try {
final FXMLLoader loader = new FXMLLoader(getClass().getResource(fxml), rb);
loader.setControllerFactory(controllerFactory);
final Parent root = loader.load();
contentPane.getChildren().clear();
contentPane.getChildren().add(root);

View File

@ -0,0 +1,68 @@
/*******************************************************************************
* Copyright (c) 2014 cryptomator.org
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
* Contributors:
* Tillmann Gaida - initial implementation
******************************************************************************/
package org.cryptomator.ui;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.inject.Singleton;
import org.cryptomator.ui.settings.Settings;
import org.cryptomator.ui.util.DeferredCloser;
import org.cryptomator.ui.util.DeferredCloser.Closer;
import org.cryptomator.webdav.WebDavServer;
import javafx.util.Callback;
import com.google.inject.AbstractModule;
import com.google.inject.Injector;
import com.google.inject.Provides;
public class MainModule extends AbstractModule {
DeferredCloser deferredCloser = new DeferredCloser();
public static interface ControllerFactory extends Callback<Class<?>, Object> {
}
@Override
protected void configure() {
bind(DeferredCloser.class).toInstance(deferredCloser);
}
@Provides
@Singleton
ControllerFactory getControllerFactory(Injector injector) {
return cls -> injector.getInstance(cls);
}
@Provides
@Singleton
ExecutorService getExec() {
return closeLater(Executors.newCachedThreadPool(), ExecutorService::shutdown);
}
@Provides
@Singleton
Settings getSettings() {
return closeLater(Settings.load(), Settings::save);
}
@Provides
@Singleton
WebDavServer getServer() {
final WebDavServer webDavServer = new WebDavServer();
webDavServer.start();
return closeLater(webDavServer, WebDavServer::stop);
}
<T> T closeLater(T object, Closer<T> closer) {
return deferredCloser.closeLater(object, closer).get().get();
}
}

View File

@ -16,6 +16,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ResourceBundle;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import javafx.application.Platform;
@ -40,9 +41,13 @@ import org.cryptomator.ui.controls.SecPasswordField;
import org.cryptomator.ui.model.Vault;
import org.cryptomator.ui.util.FXThreads;
import org.cryptomator.ui.util.MasterKeyFilter;
import org.cryptomator.ui.util.DeferredCloser;
import org.cryptomator.webdav.WebDavServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.inject.Inject;
public class UnlockController implements Initializable {
private static final Logger LOG = LoggerFactory.getLogger(UnlockController.class);
@ -72,6 +77,20 @@ public class UnlockController implements Initializable {
@FXML
private Label messageLabel;
private final WebDavServer server;
private final ExecutorService exec;
private final DeferredCloser closer;
@Inject
public UnlockController(WebDavServer server, ExecutorService exec, DeferredCloser closer) {
super();
this.server = server;
this.exec = exec;
this.closer = closer;
}
@Override
public void initialize(URL url, ResourceBundle rb) {
this.rb = rb;
@ -107,15 +126,15 @@ public class UnlockController implements Initializable {
masterKeyInputStream = Files.newInputStream(masterKeyPath, StandardOpenOption.READ);
directory.setVerifyFileIntegrity(checkIntegrity.isSelected());
directory.getCryptor().decryptMasterKey(masterKeyInputStream, password);
if (!directory.startServer()) {
if (!directory.startServer(server, closer)) {
messageLabel.setText(rb.getString("unlock.messageLabel.startServerFailed"));
directory.getCryptor().swipeSensitiveData();
return;
}
directory.setUnlocked(true);
final Future<Boolean> futureMount = FXThreads.runOnBackgroundThread(directory::mount);
FXThreads.runOnMainThreadWhenFinished(futureMount, this::didUnlockAndMount);
FXThreads.runOnMainThreadWhenFinished(futureMount, (result) -> {
final Future<Boolean> futureMount = exec.submit(() -> directory.mount(closer));
FXThreads.runOnMainThreadWhenFinished(exec, futureMount, this::didUnlockAndMount);
FXThreads.runOnMainThreadWhenFinished(exec, futureMount, (result) -> {
setControlsDisabled(false);
});
} catch (DecryptFailedException | IOException ex) {

View File

@ -29,6 +29,8 @@ import org.cryptomator.crypto.CryptorIOSampling;
import org.cryptomator.ui.model.Vault;
import org.cryptomator.webdav.WebDavServer;
import com.google.inject.Inject;
public class UnlockedController implements Initializable {
private static final int IO_SAMPLING_STEPS = 100;
@ -47,6 +49,14 @@ public class UnlockedController implements Initializable {
@FXML
private NumberAxis xAxis;
private final WebDavServer server;
@Inject
public UnlockedController(WebDavServer server) {
super();
this.server = server;
}
@Override
public void initialize(URL url, ResourceBundle rb) {
this.rb = rb;
@ -124,7 +134,7 @@ public class UnlockedController implements Initializable {
public void setDirectory(Vault directory) {
this.directory = directory;
final String msg = String.format(rb.getString("unlocked.messageLabel.runningOnPort"), WebDavServer.getInstance().getPort());
final String msg = String.format(rb.getString("unlocked.messageLabel.runningOnPort"), server.getPort());
messageLabel.setText(msg);
if (directory.getCryptor() instanceof CryptorIOSampling) {

View File

@ -6,6 +6,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.text.Normalizer;
import java.text.Normalizer.Form;
import java.util.Optional;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
@ -14,8 +15,9 @@ import org.apache.commons.lang3.StringUtils;
import org.cryptomator.crypto.Cryptor;
import org.cryptomator.crypto.SamplingDecorator;
import org.cryptomator.crypto.aes256.Aes256Cryptor;
import org.cryptomator.ui.Main;
import org.cryptomator.ui.util.MasterKeyFilter;
import org.cryptomator.ui.util.DeferredClosable;
import org.cryptomator.ui.util.DeferredCloser;
import org.cryptomator.ui.util.mount.CommandFailedException;
import org.cryptomator.ui.util.mount.WebDavMount;
import org.cryptomator.ui.util.mount.WebDavMounter;
@ -38,12 +40,11 @@ public class Vault implements Serializable {
private final Cryptor cryptor = SamplingDecorator.decorate(new Aes256Cryptor());
private final ObjectProperty<Boolean> unlocked = new SimpleObjectProperty<Boolean>(this, "unlocked", Boolean.FALSE);
private final Runnable shutdownTask = new ShutdownTask();
private final Path path;
private boolean verifyFileIntegrity;
private String mountName;
private ServletLifeCycleAdapter webDavServlet;
private WebDavMount webDavMount;
private DeferredClosable<ServletLifeCycleAdapter> webDavServlet = DeferredClosable.empty();
private DeferredClosable<WebDavMount> webDavMount = DeferredClosable.empty();
public Vault(final Path vaultDirectoryPath) {
if (!Files.isDirectory(vaultDirectoryPath) || !vaultDirectoryPath.getFileName().toString().endsWith(VAULT_FILE_EXTENSION)) {
@ -62,34 +63,32 @@ public class Vault implements Serializable {
return MasterKeyFilter.filteredDirectory(path).iterator().hasNext();
}
public synchronized boolean startServer() {
if (webDavServlet != null && webDavServlet.isRunning()) {
public synchronized boolean startServer(WebDavServer server, DeferredCloser closer) {
Optional<ServletLifeCycleAdapter> o = webDavServlet.get();
if (o.isPresent() && o.get().isRunning()) {
return false;
}
webDavServlet = WebDavServer.getInstance().createServlet(path, verifyFileIntegrity, cryptor, getMountName());
if (webDavServlet.start()) {
Main.addShutdownTask(shutdownTask);
ServletLifeCycleAdapter servlet = server.createServlet(path, verifyFileIntegrity, cryptor, getMountName());
if (servlet.start()) {
webDavServlet = closer.closeLater(servlet, ServletLifeCycleAdapter::stop);
return true;
} else {
return false;
}
return false;
}
public void stopServer() {
if (webDavServlet != null && webDavServlet.isRunning()) {
Main.removeShutdownTask(shutdownTask);
this.unmount();
webDavServlet.stop();
cryptor.swipeSensitiveData();
}
unmount();
webDavServlet.close();
cryptor.swipeSensitiveData();
}
public boolean mount() {
if (webDavServlet == null || !webDavServlet.isRunning()) {
public boolean mount(DeferredCloser closer) {
Optional<ServletLifeCycleAdapter> o = webDavServlet.get();
if (!o.isPresent() || !o.get().isRunning()) {
return false;
}
try {
webDavMount = WebDavMounter.mount(webDavServlet.getServletUri(), getMountName());
webDavMount = closer.closeLater(WebDavMounter.mount(o.get().getServletUri(), getMountName()), WebDavMount::unmount);
return true;
} catch (CommandFailedException e) {
LOG.warn("mount failed", e);
@ -97,17 +96,8 @@ public class Vault implements Serializable {
}
}
public boolean unmount() {
try {
if (webDavMount != null) {
webDavMount.unmount();
webDavMount = null;
}
return true;
} catch (CommandFailedException e) {
LOG.warn("unmount failed", e);
return false;
}
public void unmount() {
webDavMount.close();
}
/* Getter/Setter */
@ -208,15 +198,4 @@ public class Vault implements Serializable {
}
}
/* graceful shutdown */
private class ShutdownTask implements Runnable {
@Override
public void run() {
stopServer();
}
}
}

View File

@ -36,7 +36,6 @@ public class Settings implements Serializable {
private static final Path SETTINGS_DIR;
private static final String SETTINGS_FILE = "settings.json";
private static final ObjectMapper JSON_OM = new ObjectMapper();
private static Settings INSTANCE = null;
static {
final String appdata = System.getenv("APPDATA");
@ -61,31 +60,25 @@ public class Settings implements Serializable {
}
public static synchronized Settings load() {
if (INSTANCE == null) {
try {
Files.createDirectories(SETTINGS_DIR);
final Path settingsFile = SETTINGS_DIR.resolve(SETTINGS_FILE);
final InputStream in = Files.newInputStream(settingsFile, StandardOpenOption.READ);
INSTANCE = JSON_OM.readValue(in, Settings.class);
return INSTANCE;
} catch (IOException e) {
LOG.warn("Failed to load settings, creating new one.");
INSTANCE = Settings.defaultSettings();
}
try {
Files.createDirectories(SETTINGS_DIR);
final Path settingsFile = SETTINGS_DIR.resolve(SETTINGS_FILE);
final InputStream in = Files.newInputStream(settingsFile, StandardOpenOption.READ);
return JSON_OM.readValue(in, Settings.class);
} catch (IOException e) {
LOG.warn("Failed to load settings, creating new one.");
return Settings.defaultSettings();
}
return INSTANCE;
}
public static synchronized void save() {
if (INSTANCE != null) {
try {
Files.createDirectories(SETTINGS_DIR);
final Path settingsFile = SETTINGS_DIR.resolve(SETTINGS_FILE);
final OutputStream out = Files.newOutputStream(settingsFile, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE);
JSON_OM.writeValue(out, INSTANCE);
} catch (IOException e) {
LOG.error("Failed to save settings.", e);
}
public synchronized void save() {
try {
Files.createDirectories(SETTINGS_DIR);
final Path settingsFile = SETTINGS_DIR.resolve(SETTINGS_FILE);
final OutputStream out = Files.newOutputStream(settingsFile, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE);
JSON_OM.writeValue(out, this);
} catch (IOException e) {
LOG.error("Failed to save settings.", e);
}
}

View File

@ -0,0 +1,43 @@
/*******************************************************************************
* Copyright (c) 2014 cryptomator.org
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
* Contributors:
* Tillmann Gaida - initial implementation
******************************************************************************/
package org.cryptomator.ui.util;
import java.util.Optional;
/**
* Wrapper around an object, which should be closed later - explicitly or by a
* {@link DeferredCloser}. The wrapped object can be accessed as long as the
* resource has not been closed.
*
* @author Tillmann Gaida
*
* @param <T>
* any type
*/
public interface DeferredClosable<T> extends AutoCloseable {
/**
* Returns the wrapped Object.
*
* @return empty if the object has been closed.
*/
public Optional<T> get();
/**
* Quietly closes the Object. If the object was closed before, nothing
* happens.
*/
public void close();
/**
* @return an empty object.
*/
public static <T> DeferredClosable<T> empty() {
return DeferredCloser.empty();
}
}

View File

@ -0,0 +1,123 @@
/*******************************************************************************
* Copyright (c) 2014 cryptomator.org
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
* Contributors:
* Tillmann Gaida - initial implementation
******************************************************************************/
package org.cryptomator.ui.util;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import org.cryptomator.ui.MainController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>
* Tries to bring open-close symmetry in contexts where the resource outlives
* the current scope by introducing a manager, which closes the resources if
* they haven't been closed before.
* </p>
*
* <p>
* If you have a {@link DeferredCloser} instance present, call
* {@link #closeLater(Object, Closer)} immediately after you have opened the
* resource and return a resource handle. If {@link #close()} is called, the
* resource will be closed. Calling {@link DeferredClosable#close()} on the resource
* handle will also close the resource and prevent a second closing by
* {@link #close()}.
* </p>
*
* @author Tillmann Gaida
*/
public class DeferredCloser implements AutoCloseable {
public static interface Closer<T> {
void close(T object) throws Exception;
}
static class EmptyResource<T> implements DeferredClosable<T> {
@Override
public Optional<T> get() {
return Optional.empty();
}
@Override
public void close() {
}
}
private static final Logger LOG = LoggerFactory.getLogger(MainController.class);
final Map<Long, ManagedResource<?>> cleanups = new ConcurrentSkipListMap<>();
final AtomicLong counter = new AtomicLong();
public class ManagedResource<T> implements DeferredClosable<T> {
private final long number = counter.incrementAndGet();
private final AtomicReference<T> object = new AtomicReference<>();
private final Closer<T> closer;
ManagedResource(T object, Closer<T> closer) {
super();
this.object.set(object);
this.closer = closer;
}
public void close() {
final T oldObject = object.getAndSet(null);
if (oldObject != null) {
cleanups.remove(number);
try {
closer.close(oldObject);
} catch (Exception e) {
LOG.error("exception closing resource", e);
}
}
}
public Optional<T> get() throws IllegalStateException {
return Optional.ofNullable(object.get());
}
}
/**
* Closes all added objects which have not been closed before.
*/
public void close() {
for (ManagedResource<?> closableProvider : cleanups.values()) {
closableProvider.close();
}
}
public <T> DeferredClosable<T> closeLater(T object, Closer<T> closer) {
Objects.requireNonNull(object);
Objects.requireNonNull(closer);
final ManagedResource<T> resource = new ManagedResource<T>(object, closer);
cleanups.put(resource.number, resource);
return resource;
}
public <T extends AutoCloseable> DeferredClosable<T> closeLater(T object) {
Objects.requireNonNull(object);
final ManagedResource<T> resource = new ManagedResource<T>(object, AutoCloseable::close);
cleanups.put(resource.number, resource);
return resource;
}
private static final EmptyResource<?> EMPTY_RESOURCE = new EmptyResource<>();
@SuppressWarnings("unchecked")
public static <T> DeferredClosable<T> empty() {
return (DeferredClosable<T>) EMPTY_RESOURCE;
}
}

View File

@ -10,9 +10,8 @@
******************************************************************************/
package org.cryptomator.ui.util;
import java.util.concurrent.Callable;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import javafx.application.Platform;
@ -48,61 +47,14 @@ import javafx.application.Platform;
*/
public final class FXThreads {
private static final ExecutorService EXECUTOR = Executors.newCachedThreadPool();
private static final CallbackWhenTaskFailed DUMMY_EXCEPTION_CALLBACK = (e) -> {
// ignore.
};
private FXThreads() {
throw new AssertionError("Not instantiable.");
}
/**
* Executes the given task on a background thread. If you want to react on the result on your JavaFX main thread, use
* {@link #runOnMainThreadWhenFinished(Future, CallbackWhenTaskFinished)}.
*
* <pre>
* // examples:
*
* Future&lt;String&gt; futureBookName1 = runOnBackgroundThread(restResource::getBookName);
*
* Future&lt;String&gt; futureBookName2 = runOnBackgroundThread(() -&gt; {
* return restResource.getBookName();
* });
* </pre>
*
* @param task The task to be executed on a background thread.
* @return A future result object, which you can use in {@link #runOnMainThreadWhenFinished(Future, CallbackWhenTaskFinished)}.
*/
public static <T> Future<T> runOnBackgroundThread(Callable<T> task) {
return EXECUTOR.submit(task);
}
/**
* Executes the given task on a background thread. If you want to react on the result on your JavaFX main thread, use
* {@link #runOnMainThreadWhenFinished(Future, CallbackWhenTaskFinished)}.
*
* <pre>
* // examples:
*
* Future&lt;?&gt; futureDone1 = runOnBackgroundThread(this::doSomeComplexCalculation);
*
* Future&lt;?&gt; futureDone2 = runOnBackgroundThread(() -&gt; {
* doSomeComplexCalculation();
* });
* </pre>
*
* @param task The task to be executed on a background thread.
* @return A future result object, which you can use in {@link #runOnMainThreadWhenFinished(Future, CallbackWhenTaskFinished)}.
*/
public static Future<?> runOnBackgroundThread(Runnable task) {
return EXECUTOR.submit(task);
}
/**
* Waits for the given task to complete and notifies the given successCallback. If an exception occurs, the callback will never be
* called. If you are interested in the exception, use
* {@link #runOnMainThreadWhenFinished(Future, CallbackWhenTaskFinished, CallbackWhenTaskFailed)} instead.
* {@link #runOnMainThreadWhenFinished(ExecutorService, Future, CallbackWhenTaskFinished, CallbackWhenTaskFailed)} instead.
*
* <pre>
* // example:
@ -111,21 +63,21 @@ public final class FXThreads {
* myLabel.setText(bookName);
* });
* </pre>
*
* @param executor
* @param task The task to wait for.
* @param successCallback The action to perform, when the task finished.
*/
public static <T> void runOnMainThreadWhenFinished(Future<T> task, CallbackWhenTaskFinished<T> successCallback) {
runOnBackgroundThread(() -> {
public static <T> void runOnMainThreadWhenFinished(ExecutorService executor, Future<T> task, CallbackWhenTaskFinished<T> successCallback) {
executor.submit(() -> {
return "asd";
});
FXThreads.runOnMainThreadWhenFinished(task, successCallback, DUMMY_EXCEPTION_CALLBACK);
runOnMainThreadWhenFinished(executor, task, successCallback, DUMMY_EXCEPTION_CALLBACK);
}
/**
* Waits for the given task to complete and notifies the given successCallback. If an exception occurs, the callback will never be
* called. If you are interested in the exception, use
* {@link #runOnMainThreadWhenFinished(Future, CallbackWhenTaskFinished, CallbackWhenTaskFailed)} instead.
* {@link #runOnMainThreadWhenFinished(ExecutorService, Future, CallbackWhenTaskFinished, CallbackWhenTaskFailed)} instead.
*
* <pre>
* // example:
@ -137,14 +89,17 @@ public final class FXThreads {
* });
* </pre>
*
* @param executor
* The service to execute the background task on
* @param task The task to wait for.
* @param successCallback The action to perform, when the task finished.
* @param exceptionCallback
*/
public static <T> void runOnMainThreadWhenFinished(Future<T> task, CallbackWhenTaskFinished<T> successCallback, CallbackWhenTaskFailed exceptionCallback) {
assertParamNotNull(task, "task must not be null.");
assertParamNotNull(successCallback, "successCallback must not be null.");
assertParamNotNull(exceptionCallback, "exceptionCallback must not be null.");
EXECUTOR.execute(() -> {
public static <T> void runOnMainThreadWhenFinished(ExecutorService executor, Future<T> task, CallbackWhenTaskFinished<T> successCallback, CallbackWhenTaskFailed exceptionCallback) {
Objects.requireNonNull(task, "task must not be null.");
Objects.requireNonNull(successCallback, "successCallback must not be null.");
Objects.requireNonNull(exceptionCallback, "exceptionCallback must not be null.");
executor.execute(() -> {
try {
final T result = task.get();
Platform.runLater(() -> {
@ -158,12 +113,6 @@ public final class FXThreads {
});
}
private static void assertParamNotNull(Object param, String msg) {
if (param == null) {
throw new IllegalArgumentException(msg);
}
}
public interface CallbackWhenTaskFinished<T> {
void taskFinished(T result);
}

View File

@ -1,10 +1,10 @@
/*******************************************************************************
* Copyright (c) 2014 Sebastian Stenzel
* Copyright (c) 2014 cryptomator.org
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
* Tillmann Gaida - initial implementation
******************************************************************************/
package org.cryptomator.ui.util;

View File

@ -0,0 +1,12 @@
package org.cryptomator.ui;
import static org.junit.Assert.*;
import org.junit.Test;
public class MainApplicationTest {
@Test
public void testInjection() throws Exception {
new MainApplication();
}
}

View File

@ -0,0 +1,48 @@
package org.cryptomator.ui.util;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.io.Closeable;
import org.junit.Test;
public class DeferredCloserTest {
@Test
public void testBasicFunctionality() throws Exception {
DeferredCloser closer = new DeferredCloser();
final Closeable obj = mock(Closeable.class);
final DeferredClosable<Closeable> resource = closer.closeLater(obj);
assertTrue(resource.get().isPresent());
assertTrue(resource.get().get() == obj);
closer.close();
assertFalse(resource.get().isPresent());
verify(obj).close();
}
@Test
public void testAutoremoval() throws Exception {
DeferredCloser closer = new DeferredCloser();
final DeferredClosable<Closeable> resource = closer.closeLater(mock(Closeable.class));
final DeferredClosable<Closeable> resource2 = closer.closeLater(mock(Closeable.class));
resource.close();
assertFalse(resource.get().isPresent());
assertEquals(1, closer.cleanups.size());
assertTrue(resource2.get().isPresent());
closer.close();
assertFalse(resource2.get().isPresent());
assertEquals(0, closer.cleanups.size());
}
}