// This software is released into the Public Domain.  See copying.txt for details.
package org.openstreetmap.osmosis.core.util;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.logging.Logger;

import org.openstreetmap.osmosis.core.OsmosisRuntimeException;
import org.openstreetmap.osmosis.core.lifecycle.Closeable;


/**
 * This class provides a mechanism to use simple files as locks to prevent
 * multiple threads or processes from updating common files.
 * 
 * @author Brett Henderson
 */
public class FileBasedLock implements Closeable {
	
	private static final Logger LOG = Logger.getLogger(FileBasedLock.class.getName());
	
	private File lockFile;
	private FileOutputStream outputStream;
	private FileChannel fileChannel;
	private FileLock fileLock;
	private boolean initialized;
	
	
	/**
	 * Creates a new instance.
	 * 
	 * @param lockFile
	 *            The file to use for locking.
	 */
	public FileBasedLock(File lockFile) {
		this.lockFile = lockFile;
		
		initialized = false;
	}
	
	
	/**
	 * Creates the file resources used internally for implementing the lock.
	 */
	private void initialize() {
		if (!initialized) {
			try {
				outputStream = new FileOutputStream(lockFile);
			} catch (IOException e) {
				throw new OsmosisRuntimeException("Unable to open lock file " + lockFile + ".");
			}
			
			fileChannel = outputStream.getChannel();
			
			initialized = true;
		}
	}
	
	
	/**
	 * Obtain an exclusive lock. This will fail if another thread or process
	 * already has a lock.
	 */
	public void lock() {
		initialize();
		
		if (fileLock != null) {
			throw new OsmosisRuntimeException("A lock has already been obtained on file " + lockFile + ".");
		}
		
		try {
			fileLock = fileChannel.tryLock();
			
			if (fileLock == null) {
				throw new OsmosisRuntimeException("A exclusive lock already exists on file " + lockFile + ".");
			}
			
		} catch (IOException e) {
			throw new OsmosisRuntimeException(
					"An error occurred while trying to obtain an exclusive lock on file " + lockFile + ".");
		}
	}
	
	
	/**
	 * Release the lock.
	 */
	public void unlock() {
		initialize();
		
		try {
			fileLock.release();
			fileLock = null;
		} catch (IOException e) {
			throw new OsmosisRuntimeException("Unable to release lock on file " + lockFile + ".");
		}
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public void close() {
		if (outputStream != null) {
			try {
				outputStream.close();
			} catch (Exception e) {
				LOG.warning("Unable to close lock stream on file " + lockFile + ".");
			} finally {
				outputStream = null;
				fileChannel = null;
				fileLock = null;
				initialized = false;
			}
		}
	}
}
