// CHECKSTYLE:OFF

/*---------------------------------------------------------------------------
 * Copyright (C) 2001 Maxim Integrated Products, All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL MAXIM INTEGRATED PRODUCTS BE LIABLE FOR ANY CLAIM, DAMAGES
 * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * Except as contained in this notice, the name of Maxim Integrated Products
 * shall not be used except as stated in the Maxim Integrated Products
 * Branding Policy.
 *---------------------------------------------------------------------------
 */

package com.dalsemi.onewire.application.file;

import java.io.IOException;
import java.io.InputStream;

import com.dalsemi.onewire.container.OneWireContainer;

/**
 * A <code>OWFileInputStream</code> obtains input bytes from a file in a 1-Wire
 * Filesystem. What files are available depends on the 1-Wire device.
 *
 * <p>
 * Note that the 1-Wire File system can reside across multiple 1-Wire devices.
 * In this case only one of the devices need be supplied to the constructor.
 * Each device in a multi-device file system contains information to reacquire
 * the entire list.
 *
 * <p>
 * File and directory <b> name </b> limitations
 * <ul>
 * <li>File/directory names limited to 4 characters not including extension
 * <li>File/directory names are not case sensitive and will be automatically
 * changed to all-CAPS
 * <li>Only files can have extensions
 * <li>Extensions are numerical in the range 0 to 125
 * <li>Extensions 100 to 125 are special purpose and not yet implemented or
 * allowed
 * <li>Files can have the read-only attribute
 * <li>Directories can have the hidden attribute
 * <li>It is recommended to limit directory depth to 10 levels to accommodate
 * legacy implementations
 * </ul>
 *
 * <H3>Usage</H3>
 * <H4>Example</H4> Read from a 1-Wire file on device 'owd':
 *
 * <PRE>
 *  <CODE>
 *   // get an input stream to the 1-Wire file
 *   OWFileInputStream instream = new OWFileInputStream(owd, "DEMO.0");
 *
 *   // read some data
 *   byte[] data = new byte[2000];
 *   int len = instream.read(data);
 *
 *   // close the stream to release system resources
 *   instream.close();
 *
 * </CODE>
 * </PRE>
 *
 * @author DS
 * @version 0.01, 1 June 2001
 * @see com.dalsemi.onewire.application.file.OWFile
 * @see com.dalsemi.onewire.application.file.OWFileDescriptor
 * @see com.dalsemi.onewire.application.file.OWFileOutputStream
 */
public class OWFileInputStream extends InputStream {

	// --------
	// -------- Variables
	// --------

	/**
	 * File descriptor.
	 */
	private OWFileDescriptor fd;

	// --------
	// -------- Constructors
	// --------

	/**
	 * Creates a <code>FileInputStream</code> by opening a connection to an actual
	 * file, the file named by the path name <code>name</code> in the Filesystem. A
	 * new <code>OWFileDescriptor</code> object is created to represent this file
	 * connection.
	 * <p>
	 * First, if there is a security manager, its <code>checkRead</code> method is
	 * called with the <code>name</code> argument as its argument.
	 * <p>
	 * If the named file does not exist, is a directory rather than a regular file,
	 * or for some other reason cannot be opened for reading then a
	 * <code>FileNotFoundException</code> is thrown.
	 *
	 * @param owd  OneWireContainer that this Filesystem resides on
	 * @param name the system-dependent file name.
	 * @exception OWFileNotFoundException if the file does not exist, is a directory
	 *                                    rather than a regular file, or for some
	 *                                    other reason cannot be opened for reading.
	 */
	public OWFileInputStream(OneWireContainer owd, String name) throws OWFileNotFoundException {
		this.fd = new OWFileDescriptor(owd, name);

		// open the file
		try {
			this.fd.open();
		} catch (OWFileNotFoundException e) {
			this.fd.free();
			this.fd = null;
			throw new OWFileNotFoundException(e.toString());
		}

		// make sure this is not directory
		if (!this.fd.isFile()) {
			this.fd.free();
			this.fd = null;
			throw new OWFileNotFoundException("Not a file");
		}
	}

	/**
	 * Creates a <code>FileInputStream</code> by opening a connection to an actual
	 * file, the file named by the path name <code>name</code> in the Filesystem. A
	 * new <code>OWFileDescriptor</code> object is created to represent this file
	 * connection.
	 * <p>
	 * First, if there is a security manager, its <code>checkRead</code> method is
	 * called with the <code>name</code> argument as its argument.
	 * <p>
	 * If the named file does not exist, is a directory rather than a regular file,
	 * or for some other reason cannot be opened for reading then a
	 * <code>FileNotFoundException</code> is thrown.
	 *
	 * @param owd  array of OneWireContainers that this Filesystem resides on
	 * @param name the system-dependent file name.
	 * @exception OWFileNotFoundException if the file does not exist, is a directory
	 *                                    rather than a regular file, or for some
	 *                                    other reason cannot be opened for reading.
	 */
	public OWFileInputStream(OneWireContainer[] owd, String name) throws OWFileNotFoundException {
		this.fd = new OWFileDescriptor(owd, name);

		// open the file
		try {
			this.fd.open();
		} catch (OWFileNotFoundException e) {
			this.fd.free();
			this.fd = null;
			throw new OWFileNotFoundException(e.toString());
		}

		// make sure this is not directory
		if (!this.fd.isFile()) {
			this.fd.free();
			this.fd = null;
			throw new OWFileNotFoundException("Not a file");
		}
	}

	/**
	 * Creates a <code>OWFileInputStream</code> by opening a connection to an actual
	 * file, the file named by the <code>File</code> object <code>file</code> in the
	 * Filesystem. A new <code>OWFileDescriptor</code> object is created to
	 * represent this file connection.
	 * <p>
	 * If the named file does not exist, is a directory rather than a regular file,
	 * or for some other reason cannot be opened for reading then a
	 * <code>FileNotFoundException</code> is thrown.
	 *
	 * @param file the file to be opened for reading.
	 * @exception OWFileNotFoundException if the file does not exist, is a directory
	 *                                    rather than a regular file, or for some
	 *                                    other reason cannot be opened for reading.
	 * @see com.dalsemi.onewire.application.file.OWFile#getPath()
	 */
	public OWFileInputStream(OWFile file) throws OWFileNotFoundException {
		// get the file descriptor
		try {
			this.fd = file.getFD();
		} catch (IOException e) {
			this.fd.free();
			this.fd = null;
			throw new OWFileNotFoundException(e.toString());
		}

		// open the file
		try {
			this.fd.open();
		} catch (OWFileNotFoundException e) {
			this.fd.free();
			this.fd = null;
			throw new OWFileNotFoundException(e.toString());
		}

		// make sure it is not a directory
		if (!this.fd.isFile()) {
			this.fd.free();
			this.fd = null;
			throw new OWFileNotFoundException("Not a file");
		}
	}

	/**
	 * Creates a <code>OWFileInputStream</code> by using the file descriptor
	 * <code>fdObj</code>, which represents an existing connection to an actual file
	 * in the Filesystem.
	 * <p>
	 * If <code>fdObj</code> is null then a <code>NullPointerException</code> is
	 * thrown.
	 *
	 * @param fdObj the file descriptor to be opened for reading.
	 */
	public OWFileInputStream(OWFileDescriptor fdObj) {
		if (fdObj == null) {
			throw new NullPointerException("OWFile provided is null");
		}

		this.fd = fdObj;
	}

	// --------
	// -------- Read Methods
	// --------

	/**
	 * Reads a byte of data from this input stream. This method blocks if no input
	 * is yet available.
	 *
	 * @return the next byte of data, or <code>-1</code> if the end of the file is
	 *         reached.
	 * @exception IOException if an I/O error occurs.
	 */
	@Override
	public int read() throws IOException {
		if (this.fd != null) {
			return this.fd.read();
		}
		throw new IOException("1-Wire FileDescriptor is null");
	}

	/**
	 * Reads up to <code>b.length</code> bytes of data from this input stream into
	 * an array of bytes. This method blocks until some input is available.
	 *
	 * @param b the buffer into which the data is read.
	 * @return the total number of bytes read into the buffer, or <code>-1</code> if
	 *         there is no more data because the end of the file has been reached.
	 * @exception IOException if an I/O error occurs.
	 */
	@Override
	public int read(byte b[]) throws IOException {
		if (this.fd != null) {
			return this.fd.read(b, 0, b.length);
		}
		throw new IOException("1-Wire FileDescriptor is null");
	}

	/**
	 * Reads up to <code>len</code> bytes of data from this input stream into an
	 * array of bytes. This method blocks until some input is available.
	 *
	 * @param b   the buffer into which the data is read.
	 * @param off the start offset of the data.
	 * @param len the maximum number of bytes read.
	 * @return the total number of bytes read into the buffer, or <code>-1</code> if
	 *         there is no more data because the end of the file has been reached.
	 * @exception IOException if an I/O error occurs.
	 */
	@Override
	public int read(byte b[], int off, int len) throws IOException {
		if (this.fd != null) {
			return this.fd.read(b, off, len);
		}
		throw new IOException("1-Wire FileDescriptor is null");
	}

	/**
	 * Skips over and discards <code>n</code> bytes of data from the input stream.
	 * The <code>skip</code> method may, for a variety of reasons, end up skipping
	 * over some smaller number of bytes, possibly <code>0</code>. The actual number
	 * of bytes skipped is returned.
	 *
	 * @param n the number of bytes to be skipped.
	 * @return the actual number of bytes skipped.
	 * @exception IOException if an I/O error occurs.
	 */
	@Override
	public long skip(long n) throws IOException {
		if (this.fd != null) {
			return this.fd.skip(n);
		}
		throw new IOException("1-Wire FileDescriptor is null");
	}

	/**
	 * Returns the number of bytes that can be read from this file input stream
	 * without blocking.
	 *
	 * @return the number of bytes that can be read from this file input stream
	 *         without blocking.
	 * @exception IOException if an I/O error occurs.
	 */
	@Override
	public int available() throws IOException {
		if (this.fd != null) {
			return this.fd.available();
		}
		throw new IOException("1-Wire FileDescriptor is null");
	}

	/**
	 * Closes this file input stream and releases any system resources associated
	 * with the stream.
	 *
	 * @exception IOException if an I/O error occurs.
	 */
	@Override
	public void close() throws IOException {
		if (this.fd == null) {
			throw new IOException("1-Wire FileDescriptor is null");
		}
		this.fd.close();

		this.fd = null;
	}

	/**
	 * Returns the <code>OWFileDescriptor</code> object that represents the
	 * connection to the actual file in the Filesystem being used by this
	 * <code>FileInputStream</code>.
	 *
	 * @return the file descriptor object associated with this stream.
	 * @exception IOException if an I/O error occurs.
	 * @see com.dalsemi.onewire.application.file.OWFileDescriptor
	 */
	public final OWFileDescriptor getFD() throws IOException {
		if (this.fd != null) {
			return this.fd;
		}
		throw new IOException("1-Wire FileDescriptor is null");
	}

	/**
	 * Ensures that the <code>close</code> method of this file input stream is
	 * called when there are no more references to it.
	 *
	 * @exception IOException if an I/O error occurs.
	 * @see com.dalsemi.onewire.application.file.OWFileInputStream#close()
	 */
	@Override
	public void finalize() throws IOException {
		if (this.fd != null) {
			this.fd.close();
		}
	}

	// --------
	// -------- Mark Methods
	// --------

	/**
	 * Marks the current position in this input stream. A subsequent call to the
	 * <code>reset</code> method repositions this stream at the last marked position
	 * so that subsequent reads re-read the same bytes.
	 *
	 * <p>
	 * The <code>readlimit</code> arguments tells this input stream to allow that
	 * many bytes to be read before the mark position gets invalidated.
	 *
	 * <p>
	 * The general contract of <code>mark</code> is that, if the method
	 * <code>markSupported</code> returns <code>true</code>, the stream somehow
	 * remembers all the bytes read after the call to <code>mark</code> and stands
	 * ready to supply those same bytes again if and whenever the method
	 * <code>reset</code> is called. However, the stream is not required to remember
	 * any data at all if more than <code>readlimit</code> bytes are read from the
	 * stream before <code>reset</code> is called.
	 *
	 * <p>
	 * The <code>mark</code> method of <code>InputStream</code> does nothing.
	 *
	 * @param readlimit the maximum limit of bytes that can be read before the mark
	 *                  position becomes invalid.
	 * @see java.io.InputStream#reset()
	 */
	@Override
	public void mark(int readlimit) {
		if (this.fd != null) {
			this.fd.mark(readlimit);
		}
	}

	/**
	 * Repositions this stream to the position at the time the <code>mark</code>
	 * method was last called on this input stream.
	 *
	 * <p>
	 * The general contract of <code>reset</code> is:
	 *
	 * <p>
	 * <ul>
	 *
	 * <li>If the method <code>markSupported</code> returns <code>true</code>, then:
	 *
	 * <ul>
	 * <li>If the method <code>mark</code> has not been called since the stream was
	 * created, or the number of bytes read from the stream since <code>mark</code>
	 * was last called is larger than the argument to <code>mark</code> at that last
	 * call, then an <code>IOException</code> might be thrown.
	 *
	 * <li>If such an <code>IOException</code> is not thrown, then the stream is
	 * reset to a state such that all the bytes read since the most recent call to
	 * <code>mark</code> (or since the start of the file, if <code>mark</code> has
	 * not been called) will be resupplied to subsequent callers of the
	 * <code>read</code> method, followed by any bytes that otherwise would have
	 * been the next input data as of the time of the call to <code>reset</code>.
	 * </ul>
	 *
	 * <li>If the method <code>markSupported</code> returns <code>false</code>,
	 * then:
	 *
	 * <ul>
	 * <li>The call to <code>reset</code> may throw an <code>IOException</code>.
	 *
	 * <li>If an <code>IOException</code> is not thrown, then the stream is reset to
	 * a fixed state that depends on the particular type of the input stream and how
	 * it was created. The bytes that will be supplied to subsequent callers of the
	 * <code>read</code> method depend on the particular type of the input stream.
	 * </ul>
	 * </ul>
	 *
	 * <p>
	 * The method <code>reset</code> for class <code>InputStream</code> does nothing
	 * and always throws an <code>IOException</code>.
	 *
	 * @exception IOException if this stream has not been marked or if the mark has
	 *                        been invalidated.
	 * @see java.io.InputStream#mark(int)
	 * @see java.io.IOException
	 */
	@Override
	public void reset() throws IOException {
		if (this.fd != null) {
			this.fd.reset();
		}
	}

	/**
	 * Tests if this input stream supports the <code>mark</code> and
	 * <code>reset</code> methods. The <code>markSupported</code> method of
	 * <code>InputStream</code> returns <code>false</code>.
	 *
	 * @return <code>true</code> if this true type supports the mark and reset
	 *         method; <code>false</code> otherwise.
	 * @see java.io.InputStream#mark(int)
	 * @see java.io.InputStream#reset()
	 */
	@Override
	public boolean markSupported() {
		return true;
	}
}
// CHECKSTYLE:ON
