package org.prevayler.foundation;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileLock;

/** 
 * A utility class for locking files that works for multiple threads. Naive file locking behaves unpredictably if some
 * thread has already acquired a lock on a particular file; attempting another lock may succeed, may fail gracefully, or
 * may fail with an exception. Therefore this class is entirely static and synchronized to provide coordination between
 * all threads in a single JVM.
 */
public class FileLocker {
  private File _file;
  private RandomAccessFile _stream;
  private FileLock _lock;
  /** 
 * Attempt to acquire an exclusive lock on the given file. The file, and any parent directories, are created if they do
 * not exist; but the contents of the file are not harmed if it does exist. If the program needs to actually access the
 * file, it must use {@link #getStream()} exclusively <b>and not close it</b> since that may release the lock.
 * @throws IOException If the lock cannot be acquired.
 * @throws java.nio.channels.OverlappingFileLockExceptionIf the JVM detects that another thread already holds a lock on the file. This should never
 * happen if this method is used exclusively for file locking.
 */
  public FileLocker(  File file) throws IOException {
    _file=file.getCanonicalFile();
    if ("locked".equals(System.setProperty(propertyName(),"locked"))) {
      throw new IOException("Already locked internally");
    }
    _file.getParentFile().mkdirs();
    _file.createNewFile();
    _stream=new RandomAccessFile(_file,"rw");
    try {
      _lock=_stream.getChannel().tryLock();
    }
 catch (    IOException e) {
      _stream.close();
      throw e;
    }
    if (_lock == null) {
      _stream.close();
      System.setProperty(propertyName(),"");
      throw new IOException("Already locked externally");
    }
  }
  private String propertyName() throws IOException {
    return FileLocker.class.getName() + "-" + _file.getCanonicalPath();
  }
  /** 
 * Release the file lock and close the associated stream.
 */
  public void release() throws IOException {
    try {
      try {
        _lock.release();
      }
  finally {
        _stream.close();
      }
    }
  finally {
      System.setProperty(propertyName(),"");
    }
  }
  public RandomAccessFile getStream(){
    return _stream;
  }
}
