This commit is contained in:
Sebastian Stenzel
2017-05-12 12:03:28 +02:00
parent deebd82ecd
commit fdccafaa68
33 changed files with 67 additions and 300 deletions

View File

@@ -8,13 +8,6 @@ public final class Optionals {
private Optionals() {
}
public static <T, E extends Exception> void ifPresent(Optional<T> optional, ConsumerThrowingException<T, E> consumer) throws E {
final T t = optional.orElse(null);
if (t != null) {
consumer.accept(t);
}
}
/**
* Returns a function that is equivalent to the input function but immediately gets the value of the returned optional when invoked.
*

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2016 Sebastian Stenzel and others.
* Copyright (c) 2016, 2017 Sebastian Stenzel and others.
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
@@ -37,7 +37,7 @@ import org.cryptomator.common.settings.Settings;
import org.cryptomator.jni.JniException;
import org.cryptomator.jni.MacApplicationUiState;
import org.cryptomator.jni.MacFunctions;
import org.cryptomator.ui.settings.Localization;
import org.cryptomator.ui.l10n.Localization;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2016 Sebastian Stenzel and others.
* Copyright (c) 2016, 2017 Sebastian Stenzel and others.
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
@@ -24,10 +24,7 @@ import org.cryptomator.jni.JniModule;
import org.cryptomator.keychain.KeychainModule;
import org.cryptomator.ui.controllers.ViewControllerModule;
import org.cryptomator.ui.model.VaultComponent;
import org.cryptomator.ui.util.DeferredCloser;
import org.fxmisc.easybind.EasyBind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import dagger.Module;
import dagger.Provides;
@@ -36,22 +33,6 @@ import javafx.beans.binding.Binding;
@Module(includes = {ViewControllerModule.class, CommonsModule.class, KeychainModule.class, JniModule.class}, subcomponents = {VaultComponent.class})
public class UiModule {
private static final Logger LOG = LoggerFactory.getLogger(UiModule.class);
@Provides
@Singleton
DeferredCloser provideDeferredCloser(@Named("shutdownTaskScheduler") Consumer<Runnable> shutdownTaskScheduler) {
DeferredCloser closer = new DeferredCloser();
shutdownTaskScheduler.accept(() -> {
try {
closer.close();
} catch (Exception e) {
LOG.error("Error during shutdown.", e);
}
});
return closer;
}
@Provides
@Singleton
Settings provideSettings(SettingsProvider settingsProvider) {
@@ -60,8 +41,10 @@ public class UiModule {
@Provides
@Singleton
ExecutorService provideExecutorService(DeferredCloser closer) {
return closer.closeLater(Executors.newCachedThreadPool(), ExecutorService::shutdown).get().orElseThrow(IllegalStateException::new);
ExecutorService provideExecutorService(@Named("shutdownTaskScheduler") Consumer<Runnable> shutdownTaskScheduler) {
ExecutorService executorService = Executors.newCachedThreadPool();
shutdownTaskScheduler.accept(executorService::shutdown);
return executorService;
}
@Provides

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2016 Sebastian Stenzel and others.
* Copyright (c) 2016, 2017 Sebastian Stenzel and others.
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
@@ -20,8 +20,8 @@ import javax.inject.Singleton;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException;
import org.cryptomator.ui.controls.SecPasswordField;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.ui.model.Vault;
import org.cryptomator.ui.settings.Localization;
import org.cryptomator.ui.util.PasswordStrengthUtil;
import org.fxmisc.easybind.EasyBind;
import org.slf4j.Logger;

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2014, 2016 Sebastian Stenzel
* Copyright (c) 2014, 2017 Sebastian Stenzel
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
@@ -19,8 +19,8 @@ import javax.inject.Inject;
import javax.inject.Singleton;
import org.cryptomator.ui.controls.SecPasswordField;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.ui.model.Vault;
import org.cryptomator.ui.settings.Localization;
import org.cryptomator.ui.util.PasswordStrengthUtil;
import org.fxmisc.easybind.EasyBind;
import org.slf4j.Logger;

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2014, 2016 Sebastian Stenzel
* Copyright (c) 2014, 2017 Sebastian Stenzel
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
@@ -29,13 +29,13 @@ import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.ui.ExitUtil;
import org.cryptomator.ui.controls.DirectoryListCell;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.ui.model.AutoUnlocker;
import org.cryptomator.ui.model.UpgradeStrategies;
import org.cryptomator.ui.model.UpgradeStrategy;
import org.cryptomator.ui.model.Vault;
import org.cryptomator.ui.model.VaultFactory;
import org.cryptomator.ui.model.VaultList;
import org.cryptomator.ui.settings.Localization;
import org.cryptomator.ui.util.DialogBuilderUtil;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2014, 2016 Sebastian Stenzel
* Copyright (c) 2014, 2017 Sebastian Stenzel
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
@@ -16,7 +16,7 @@ import javax.inject.Singleton;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.ui.settings.Localization;
import org.cryptomator.ui.l10n.Localization;
import com.google.common.base.CharMatcher;
import com.google.common.base.Strings;

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2014, 2016 Sebastian Stenzel
* Copyright (c) 2014, 2017 Sebastian Stenzel
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
@@ -24,9 +24,9 @@ import org.cryptomator.frontend.webdav.ServerLifecycleException;
import org.cryptomator.frontend.webdav.mount.Mounter.CommandFailedException;
import org.cryptomator.keychain.KeychainAccess;
import org.cryptomator.ui.controls.SecPasswordField;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.ui.model.Vault;
import org.cryptomator.ui.model.WindowsDriveLetters;
import org.cryptomator.ui.settings.Localization;
import org.cryptomator.ui.util.AsyncTaskService;
import org.cryptomator.ui.util.DialogBuilderUtil;
import org.fxmisc.easybind.EasyBind;

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2014, 2016 Sebastian Stenzel
* Copyright (c) 2014, 2017 Sebastian Stenzel
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
@@ -14,8 +14,8 @@ import java.util.Optional;
import javax.inject.Inject;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.ui.model.Vault;
import org.cryptomator.ui.settings.Localization;
import org.cryptomator.ui.util.AsyncTaskService;
import org.cryptomator.ui.util.DialogBuilderUtil;
import org.fxmisc.easybind.EasyBind;

View File

@@ -11,11 +11,11 @@ import java.util.Optional;
import javax.inject.Inject;
import org.cryptomator.ui.controls.SecPasswordField;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.ui.model.UpgradeStrategies;
import org.cryptomator.ui.model.UpgradeStrategy;
import org.cryptomator.ui.model.UpgradeStrategy.UpgradeFailedException;
import org.cryptomator.ui.model.Vault;
import org.cryptomator.ui.settings.Localization;
import org.cryptomator.ui.util.AsyncTaskService;
import org.fxmisc.easybind.EasyBind;

View File

@@ -1,3 +1,8 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.controllers;
import java.net.URL;

View File

@@ -1,3 +1,8 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.controllers;
import static java.lang.annotation.ElementType.METHOD;

View File

@@ -1,3 +1,8 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.controllers;
import java.io.IOException;
@@ -9,7 +14,7 @@ import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import org.cryptomator.ui.settings.Localization;
import org.cryptomator.ui.l10n.Localization;
import javafx.fxml.FXMLLoader;

View File

@@ -1,3 +1,8 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.controllers;
import dagger.Module;

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2014, 2016 Sebastian Stenzel
* Copyright (c) 2014, 2017 Sebastian Stenzel
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
@@ -30,7 +30,7 @@ import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.ui.settings.Localization;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.ui.util.AsyncTaskService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2016 Sebastian Stenzel and others.
* Copyright (c) 2016, 2017 Sebastian Stenzel and others.
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2016 Sebastian Stenzel and others.
* Copyright (c) 2016, 2017 Sebastian Stenzel and others.
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2014, 2016 Sebastian Stenzel
* Copyright (c) 2014, 2017 Sebastian Stenzel
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*

View File

@@ -3,7 +3,7 @@
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.settings;
package org.cryptomator.ui.l10n;
import java.io.IOException;
import java.io.InputStream;

View File

@@ -1,3 +1,8 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.model;
import java.nio.CharBuffer;

View File

@@ -18,7 +18,7 @@ import org.cryptomator.cryptolib.api.CryptorProvider;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.cryptolib.api.KeyFile;
import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException;
import org.cryptomator.ui.settings.Localization;
import org.cryptomator.ui.l10n.Localization;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@@ -15,7 +15,7 @@ import javax.inject.Singleton;
import org.apache.commons.lang3.StringUtils;
import org.cryptomator.cryptolib.Cryptors;
import org.cryptomator.cryptolib.api.Cryptor;
import org.cryptomator.ui.settings.Localization;
import org.cryptomator.ui.l10n.Localization;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@@ -28,7 +28,7 @@ import org.apache.commons.lang3.StringUtils;
import org.cryptomator.cryptolib.Cryptors;
import org.cryptomator.cryptolib.api.Cryptor;
import org.cryptomator.cryptolib.common.MessageDigestSupplier;
import org.cryptomator.ui.settings.Localization;
import org.cryptomator.ui.l10n.Localization;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@@ -24,7 +24,7 @@ import javax.inject.Singleton;
import org.cryptomator.cryptolib.Cryptors;
import org.cryptomator.cryptolib.api.Cryptor;
import org.cryptomator.cryptolib.api.FileHeader;
import org.cryptomator.ui.settings.Localization;
import org.cryptomator.ui.l10n.Localization;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2016 Sebastian Stenzel and others.
* Copyright (c) 2016, 2017 Sebastian Stenzel and others.
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
@@ -40,7 +40,6 @@ import org.cryptomator.frontend.webdav.mount.Mounter.Mount;
import org.cryptomator.frontend.webdav.mount.Mounter.UnmountOperation;
import org.cryptomator.frontend.webdav.servlet.WebDavServletController;
import org.cryptomator.ui.model.VaultModule.PerVault;
import org.cryptomator.ui.util.DeferredCloser;
import org.fxmisc.easybind.EasyBind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -60,7 +59,6 @@ public class Vault {
private final Settings settings;
private final VaultSettings vaultSettings;
private final WebDavServer server;
private final DeferredCloser closer;
private final BooleanProperty unlocked = new SimpleBooleanProperty();
private final BooleanProperty mounted = new SimpleBooleanProperty();
private final AtomicReference<CryptoFileSystem> cryptoFileSystem = new AtomicReference<>();
@@ -69,11 +67,10 @@ public class Vault {
private Mount mount;
@Inject
Vault(Settings settings, VaultSettings vaultSettings, WebDavServer server, DeferredCloser closer) {
Vault(Settings settings, VaultSettings vaultSettings, WebDavServer server) {
this.settings = settings;
this.vaultSettings = vaultSettings;
this.server = server;
this.closer = closer;
}
// ******************************************************************************
@@ -89,9 +86,7 @@ public class Vault {
.withPassphrase(passphrase) //
.withMasterkeyFilename(MASTERKEY_FILENAME) //
.build();
CryptoFileSystem fs = CryptoFileSystemProvider.newFileSystem(getPath(), fsProps);
closer.closeLater(fs);
return fs;
return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps);
}
public void create(CharSequence passphrase) throws IOException {
@@ -103,7 +98,7 @@ public class Vault {
}
}
if (!isValidVaultDirectory()) {
getCryptoFileSystem(passphrase); // implicitly creates a non-existing vault
createCryptoFileSystem(passphrase).close(); // implicitly creates a non-existing vault
} else {
throw new FileAlreadyExistsException(getPath().toString());
}

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2016 Sebastian Stenzel and others.
* Copyright (c) 2016, 2017 Sebastian Stenzel and others.
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*

View File

@@ -49,7 +49,7 @@ public class AsyncTaskService {
private ConsumerThrowingException<ResultType, ?> successHandler = value -> {
};
private List<ErrorHandler<Throwable>> errorHandlers = new ArrayList<>();
private final List<ErrorHandler<Throwable>> errorHandlers = new ArrayList<>();
private RunnableThrowingException<?> finallyHandler = () -> {
};

View File

@@ -1,37 +0,0 @@
/*******************************************************************************
* Copyright (c) 2014, 2016 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();
/**
* @return an empty object.
*/
public static <T> DeferredClosable<T> empty() {
return DeferredCloser.empty();
}
}

View File

@@ -1,136 +0,0 @@
/*******************************************************************************
* Copyright (c) 2014, 2016 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.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicLong;
import org.cryptomator.common.ConsumerThrowingException;
import com.google.common.annotations.VisibleForTesting;
/**
* <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, ConsumerThrowingException)} 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 {
@VisibleForTesting
final Map<Long, ManagedResource<?>> cleanups = new ConcurrentSkipListMap<>();
@VisibleForTesting
final AtomicLong counter = new AtomicLong();
private class ManagedResource<T> implements DeferredClosable<T> {
private final long number = counter.incrementAndGet();
private final T object;
private final ConsumerThrowingException<T, Exception> closer;
private boolean closed = false;
public ManagedResource(T object, ConsumerThrowingException<T, Exception> closer) {
super();
this.object = Objects.requireNonNull(object);
this.closer = Objects.requireNonNull(closer);
}
@Override
public synchronized void close() throws Exception {
closer.accept(object);
cleanups.remove(number);
closed = true;
}
@Override
public Optional<T> get() throws IllegalStateException {
if (closed) {
return Optional.empty();
} else {
return Optional.of(object);
}
}
}
/**
* Closes all added objects which have not been closed before and releases references.
*/
@Override
public void close() throws ExecutionException {
ExecutionException exception = null;
for (Iterator<ManagedResource<?>> iterator = cleanups.values().iterator(); iterator.hasNext();) {
final ManagedResource<?> closableProvider = iterator.next();
try {
closableProvider.close();
iterator.remove();
} catch (Exception e) {
if (exception == null) {
exception = new ExecutionException(e);
} else {
exception.addSuppressed(e);
}
}
}
if (exception != null) {
throw exception;
}
}
public <T> DeferredClosable<T> closeLater(T object, ConsumerThrowingException<T, Exception> 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;
}
static class EmptyResource<T> implements DeferredClosable<T> {
@Override
public Optional<T> get() {
return Optional.empty();
}
@Override
public void close() {
}
}
}

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2016 Sebastian Stenzel and others.
* Copyright (c) 2016, 2017 Sebastian Stenzel and others.
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2016 Sebastian Stenzel and others.
* Copyright (c) 2016, 2017 Sebastian Stenzel and others.
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
@@ -15,7 +15,7 @@ import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.commons.lang3.StringUtils;
import org.cryptomator.ui.settings.Localization;
import org.cryptomator.ui.l10n.Localization;
import com.nulabinc.zxcvbn.Zxcvbn;

View File

@@ -1,12 +1,12 @@
/*******************************************************************************
* Copyright (c) 2016 Sebastian Stenzel and others.
* Copyright (c) 2016, 2017 Sebastian Stenzel and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE.txt.
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
*******************************************************************************/
package org.cryptomator.ui.settings;
package org.cryptomator.ui.l10n;
import java.io.IOException;
import java.io.InputStream;

View File

@@ -1,56 +0,0 @@
/*******************************************************************************
* Copyright (c) 2016 Sebastian Stenzel and others.
* 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
*******************************************************************************/
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());
}
}