package org.netbeans.gradle.model.util;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
public final class TemporaryFileManager {
private static final Logger LOGGER = Logger.getLogger(TemporaryFileManager.class.getName());
private static final TemporaryFileManager DEFAULT = new TemporaryFileManager();
private final Lock mainLock;
private final Map<BinaryContent, FileReference> files;
public TemporaryFileManager() {
this.mainLock = new ReentrantLock();
this.files = new HashMap<BinaryContent, FileReference>();
}
public static TemporaryFileManager getDefault() {
return DEFAULT;
}
private TemporaryFileRef tryGetExisting(BinaryContent content) throws IOException {
mainLock.lock();
try {
FileReference fileRef = files.get(content);
if (fileRef != null) {
fileRef.useOne();
return new SingleFileReference(fileRef.key, fileRef);
}
} finally {
mainLock.unlock();
}
return null;
}
private TemporaryFileRef tryGetExisting(byte[] content) throws IOException {
return tryGetExisting(new BinaryContent(content, false));
}
private static void closeAndDelete(LockedFile file) throws IOException {
try {
file.close();
} finally {
file.file.delete();
}
}
private TemporaryFileRef createFileGuessUncached(
String preferredPrefix,
BinaryContent content) throws IOException {
TemporaryFileRef result;
LockedFile file = new LockedFile(preferredPrefix, content);
mainLock.lock();
try {
FileReference fileRef = files.get(content);
if (fileRef != null) {
fileRef.useOne();
result = new SingleFileReference(fileRef.key, fileRef);
}
else {
fileRef = new FileReference(content, file, 1);
result = new SingleFileReference(content, fileRef);
file = null;
files.put(content, fileRef);
}
} finally {
mainLock.unlock();
if (file != null) {
closeAndDelete(file);
}
}
return result;
}
public TemporaryFileRef createFile(String preferredPrefix, String strContent, Charset charset) throws IOException {
BinaryContent content = new BinaryContent(strContent.getBytes(charset.name()), false);
return createFile(preferredPrefix, content);
}
public TemporaryFileRef createFile(String preferredPrefix, String strContent, String charsetName) throws IOException {
BinaryContent content = new BinaryContent(strContent.getBytes(charsetName), false);
return createFile(preferredPrefix, content);
}
public TemporaryFileRef createFileFromSerialized(String preferredPrefix, Object contentObj) throws IOException {
BinaryContent content = new BinaryContent(SerializationUtils.serializeObject(contentObj), false);
return createFile(preferredPrefix, content);
}
private TemporaryFileRef createFile(String preferredPrefix, BinaryContent content) throws IOException {
TemporaryFileRef result = tryGetExisting(content);
if (result != null) {
return result;
}
return createFileGuessUncached(preferredPrefix, content);
}
public TemporaryFileRef createFile(String preferredPrefix, byte[] content) throws IOException {
TemporaryFileRef result = tryGetExisting(content);
if (result != null) {
return result;
}
return createFileGuessUncached(preferredPrefix, new BinaryContent(content));
}
private static final class LockedFile implements Closeable {
public final File file;
private final RandomAccessFile lockedRef;
public LockedFile(String namePrefix, BinaryContent content) throws IOException {
file = BasicFileUtils.createTmpFile(
namePrefix + "-" + BasicFileUtils.getMD5(content.content), ".tmp");
try {
lockedRef = new RandomAccessFile(file, "rw");
lockedRef.write(content.content);
lockedRef.getFD().sync();
} catch (Throwable ex) {
if (!file.delete()) {
LOGGER.log(Level.WARNING, "Failed to remove temporary file: {0}", file);
}
throw Exceptions.throwUncheckedIO(ex);
}
}
public void close() throws IOException {
lockedRef.close();
}
}
private final class SingleFileReference implements TemporaryFileRef {
private final BinaryContent content;
private final FileReference fileRef;
private final ObjectFinalizer finalizer;
public SingleFileReference(BinaryContent content, FileReference fileRef) {
this.content = content;
this.fileRef = fileRef;
this.finalizer = new ObjectFinalizer(new Runnable() {
public void run() {
doClose();
}
}, "SingleFileReference{" + fileRef.getFile() + "}");
}
public File getFile() {
return fileRef.getFile();
}
private void doClose() {
boolean delete = false;
mainLock.lock();
try {
if (fileRef.releaseOne()) {
files.remove(content);
delete = true;
}
} finally {
mainLock.unlock();
}
if (delete) {
try {
closeAndDelete(fileRef.getLockedFile());
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
public void close() throws IOException {
finalizer.doFinalize();
}
@Override
public String toString() {
return "TmpFileRef{" + fileRef.getFile() + "}";
}
}
private static final class FileReference {
public final BinaryContent key;
private final LockedFile file;
private int useCount;
public FileReference(BinaryContent key, LockedFile file, int useCount) {
this.key = key;
this.file = file;
this.useCount = useCount;
}
public File getFile() {
return file.file;
}
public LockedFile getLockedFile() {
return file;
}
public void useOne() {
useCount++;
}
public boolean releaseOne() {
useCount--;
return useCount == 0;
}
}
private static final class BinaryContent {
private final byte[] content;
private final int hash;
public BinaryContent(byte[] content) {
this(content, true);
}
public BinaryContent(byte[] content, boolean clone) {
this.content = clone ? content.clone() : content;
this.hash = 679 + Arrays.hashCode(this.content);
}
@Override
public int hashCode() {
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj == null) return false;
if (obj == this) return true;
if (getClass() != obj.getClass()) return false;
final BinaryContent other = (BinaryContent)obj;
return Arrays.equals(this.content, other.content);
}
}
}