blob: 3ce3620658103b21667c65babc0089644c225eea [file] [log] [blame]
package org.robolectric.util;
import static java.util.concurrent.TimeUnit.SECONDS;
import java.io.IOException;
import java.io.Writer;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@SuppressWarnings({"NewApi", "AndroidJdkLibsChecker"})
public class TempDirectory {
/**
* The number of concurrent deletions which should take place, too high and it'll become I/O
* bound, to low and it'll take a long time to complete. 5 is an estimate of a decent balance,
* feel free to experiment.
*/
private static final int DELETE_THREAD_POOL_SIZE = 5;
/** Set to track the undeleted TempDirectory instances which we need to erase. */
private static final Set<TempDirectory> tempDirectoriesToDelete = new HashSet<>();
private final Path basePath;
public TempDirectory() {
this("test-dir");
}
public TempDirectory(String name) {
try {
basePath = Files.createTempDirectory("robolectric-" + name);
} catch (IOException e) {
throw new RuntimeException(e);
}
synchronized (tempDirectoriesToDelete) {
// If we haven't initialised the shutdown hook we should set everything up.
if (tempDirectoriesToDelete.size() == 0) {
// Use a manual hook that actually clears the directory
// This is necessary because File.deleteOnExit won't delete non empty directories
Runtime.getRuntime().addShutdownHook(new Thread(TempDirectory::clearAllDirectories));
}
tempDirectoriesToDelete.add(this);
}
}
public Path getBasePath() {
return basePath;
}
static void clearAllDirectories() {
ExecutorService deletionExecutorService = Executors.newFixedThreadPool(DELETE_THREAD_POOL_SIZE);
synchronized (tempDirectoriesToDelete) {
for (TempDirectory undeletedDirectory : tempDirectoriesToDelete) {
deletionExecutorService.execute(undeletedDirectory::destroy);
}
}
deletionExecutorService.shutdown();
try {
deletionExecutorService.awaitTermination(5, SECONDS);
} catch (InterruptedException e) {
deletionExecutorService.shutdownNow();
// Preserve interrupt status
Thread.currentThread().interrupt();
}
}
public Path createFile(String name, String contents) {
Path path = basePath.resolve(name);
try (Writer out = Files.newBufferedWriter(path)) {
out.write(contents);
} catch (IOException e) {
throw new RuntimeException("failed writing to " + name, e);
}
return path;
}
public Path create(String name) {
Path path = basePath.resolve(name);
try {
Files.createDirectory(path);
} catch (IOException e) {
throw new RuntimeException(e);
}
return path;
}
public Path createIfNotExists(String name) {
Path path = basePath.resolve(name);
try {
Files.createDirectory(path);
} catch (FileAlreadyExistsException e) {
// that's ok
return path;
} catch (IOException e) {
throw new RuntimeException(e);
}
return path;
}
public void destroy() {
try {
clearDirectory(basePath);
Files.delete(basePath);
} catch (IOException ignored) {
Logger.error("Failed to destroy temp directory", ignored);
}
}
private void clearDirectory(final Path directory) throws IOException {
Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
if (!dir.equals(directory)) {
Files.delete(dir);
}
return FileVisitResult.CONTINUE;
}
});
}
}