package com.yoursway.utils.storage.atomic;
import static com.yoursway.utils.io.HashingInputStreamDecorator.md5HashingDecorator;
import static com.yoursway.utils.io.StreamCloseBehavior.IGNORE;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileLock;
import com.yoursway.utils.YsDigest;
import com.yoursway.utils.io.HashingInputStreamDecorator;
import com.yoursway.utils.io.InputStreamConsumer;
import com.yoursway.utils.io.OutputStreamConsumer;
import com.yoursway.utils.io.RandomAccessFileInputStream;
import com.yoursway.utils.io.RandomAccessFileOutputStream;
public class AtomicFile {
private final File file;
public AtomicFile(File file) {
if (file == null)
throw new NullPointerException("file is null");
this.file = file;
}
public AtomicFileState read(InputStreamConsumer consumer) throws IOException {
FileInputStream in = new FileInputStream(file);
String hash;
try {
FileLock lock = in.getChannel().lock(0L, Long.MAX_VALUE, true);
try {
HashingInputStreamDecorator in2 = md5HashingDecorator(in, IGNORE);
consumer.run(in2);
hash = in2.getHexHash();
} finally {
lock.release();
}
} finally {
in.close();
}
return new AtomicFileStateImpl(hash);
}
class AtomicFileStateImpl implements AtomicFileState {
private final String hash;
public AtomicFileStateImpl(String hash) {
if (hash == null)
throw new NullPointerException("hash is null");
this.hash = hash;
}
public void write(OutputStreamConsumer consumer) throws IOException,
ConcurrentFileModificationException {
RandomAccessFile ra = new RandomAccessFile(file, "rw");
try {
FileLock lock = ra.getChannel().lock();
try {
String hash = YsDigest.md5(new RandomAccessFileInputStream(ra, IGNORE));
if (!hash.equals(this.hash))
throw new ConcurrentFileModificationException(file, this.hash, hash);
ra.seek(0);
consumer.run(new RandomAccessFileOutputStream(ra, IGNORE));
ra.setLength(ra.getFilePointer());
} finally {
lock.release();
}
} finally {
ra.close();
}
}
}
public void overwrite(OutputStreamConsumer consumer) throws IOException {
RandomAccessFile ra = new RandomAccessFile(file, "rw");
try {
FileLock lock = ra.getChannel().lock();
try {
consumer.run(new RandomAccessFileOutputStream(ra, IGNORE));
ra.setLength(ra.getFilePointer());
} finally {
lock.release();
}
} finally {
ra.close();
}
}
}