/***********************************************************************************************************************
 *
 * Copyright (C) 2010 by the Stratosphere project (http://stratosphere.eu)
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 *
 **********************************************************************************************************************/

package eu.stratosphere.nephele.services.iomanager;


import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;


/**
 * A base class for readers and writers that read data from I/O manager channels, or write data to them.
 * Requests handled by channels that inherit from this class are executed asynchronously, which allows
 * write-behind for writers and pre-fetching for readers.
 * 
 * @author Alexander Alexandrov
 * @author Stephan Ewen
 * 
 * @param <T> The buffer type used for the underlying IO operations.
 */
public abstract class ChannelAccess<T, R extends IORequest>
{
	/**
	 * The ID of the underlying channel.
	 */
	protected final Channel.ID id;

	/**
	 * A file channel for NIO access to the file.
	 */
	protected final FileChannel fileChannel;
	
	/**
	 * A request queue for submitting asynchronous requests to the corresponding
	 * IO worker thread.
	 */
	protected final RequestQueue<R> requestQueue;
	
	/**
	 * An exception that was encountered by the asynchronous request handling thread.
	 */
	protected volatile IOException exception;
	
	
	// --------------------------------------------------------------------------------------------
	
	/**
	 * Creates a new channel to the path indicated by the given ID. The channel hands IO requests to
	 * the given request queue to be processed.
	 * 
	 * @param channelID The id describing the path of the file that the channel accessed.
	 * @param requestQueue The queue that this channel hands its IO requests to.
	 * @param writeEnabled Flag describing whether the channel should be opened in read/write mode, rather
	 *                     than in read-only mode.
	 * @throws IOException Thrown, if the channel could no be opened.
	 */
	protected ChannelAccess(Channel.ID channelID, RequestQueue<R> requestQueue, boolean writeEnabled)
	throws IOException
	{
		if (channelID == null || requestQueue == null) {
			throw new NullPointerException();
		}
		
		this.id = channelID;
		this.requestQueue = requestQueue;
		
		try {
			RandomAccessFile file = new RandomAccessFile(id.getPath(), writeEnabled ? "rw" : "r");
			this.fileChannel = file.getChannel();
		}
		catch (IOException e) {
			throw new IOException("Channel to path '" + channelID.getPath() + "' could not be opened.", e);
		}
	}
	
	// --------------------------------------------------------------------------------------------
	
	/**
	 * Checks, whether this channel has been closed;
	 * 
	 * @return True, if the channel has been closed, false otherwise.
	 */
	public abstract boolean isClosed();
	
	/**
	 * This method is invoked by the asynchronous I/O thread to return a buffer after the I/O request
	 * completed.
	 * 
	 * @param buffer The buffer to be returned.
	 */
	protected abstract void returnBuffer(T buffer); 
	
	// --------------------------------------------------------------------------------------------
	
	/**
	 * Gets the channel ID of this channel.
	 * 
	 * @return This channel's ID.
	 */
	public final Channel.ID getChannelID()
	{
		return this.id;
	}
	
	/**
	 * Checks the exception state of this channel. The channel is erroneous, if one of its requests could not
	 * be processed correctly.
	 * 
	 * @throws IOException Thrown, if the channel is erroneous. The thrown exception contains the original exception
	 *                     that defined the erroneous state as its cause.
	 */
	public final void checkErroneous() throws IOException
	{
		if (this.exception != null) {
			throw new IOException("The channel is erroneous.", this.exception);
		}
	}
	
	/**
	 * Deletes this channel by physically removing the file beneath it.
	 * This method may only be called on a closed channel.
	 */
	public void deleteChannel()
	{
		if (this.fileChannel.isOpen()) {
			throw new IllegalStateException("Cannot delete a channel that is open.");
		}
	
		// make a best effort to delete the file. Don't report exceptions.
		try {
			File f = new File(this.id.getPath());
			if (f.exists()) {
				f.delete();
			}
		} catch (Throwable t) {}
	}
	
	/**
	 * Handles a processed <tt>Buffer</tt>. This method is invoked by the
	 * asynchronous IO worker threads upon completion of the IO request with the
	 * provided buffer and/or an exception that occurred while processing the request
	 * for that buffer.
	 * 
	 * @param buffer The buffer to be processed.
	 * @param ex The exception that occurred in the I/O threads when processing the buffer's request.
	 */
	final void handleProcessedBuffer(T buffer, IOException ex) {
		
		if (ex != null && this.exception == null) {
			this.exception = ex;
		}
		
		returnBuffer(buffer);
	}
}
