// 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.FileNotFoundException;
import java.io.IOException;

import com.dalsemi.onewire.OneWireException;
import com.dalsemi.onewire.container.OneWireContainer;
import com.dalsemi.onewire.container.PagedMemoryBank;

/**
 * An abstract representation of file and directory pathnames on 1-Wire devices.
 *
 * <p>
 * User interfaces and operating systems use system-dependent <em>pathname
 * strings</em> to name files and directories. This class presents an abstract,
 * system-independent view of hierarchical pathnames. An <em>abstract
 * pathname</em> has two components:
 *
 * <ol>
 * <li>An optional system-dependent <em>prefix</em> string,<br>
 * such as a disk-drive specifier, <code>"/"</code> for the UNIX root directory,
 * or <code>"\\"</code> for a Win32 UNC pathname, and
 * <li>A sequence of zero or more string <em>names</em>.
 * </ol>
 *
 * Each name in an abstract pathname except for the last denotes a directory;
 * the last name may denote either a directory or a file. The <em>empty</em>
 * abstract pathname has no prefix and an empty name sequence.
 *
 * <p>
 * The conversion of a pathname string to or from an abstract pathname is
 * inherently system-dependent. When an abstract pathname is converted into a
 * pathname string, each name is separated from the next by a single copy of the
 * default <em>separator character</em>. The default name-separator character is
 * defined by the system property <code>OWFile.separator</code>, and is made
 * available in the public static fields <code>{@link
 * #separator}</code> and <code>{@link #separatorChar}</code> of this class.
 * When a pathname string is converted into an abstract pathname, the names
 * within it may be separated by the default name-separator character or by any
 * other name-separator character that is supported by the underlying system.
 *
 * <p>
 * A pathname, whether abstract or in string form, may be either
 * <em>absolute</em> or <em>relative</em>. An absolute pathname is complete in
 * that no other information is required in order to locate the file that it
 * denotes. A relative pathname, in contrast, must be interpreted in terms of
 * information taken from some other pathname. By default the classes in the
 * <code>java.io</code> package always resolve relative pathnames against the
 * current user directory. This directory is named by the system property
 * <code>user.dir</code>, and is typically the directory in which the Java
 * virtual machine was invoked. The pathname provided to this OWFile however is
 * always <em>absolute</em>.
 *
 * <p>
 * The prefix concept is used to handle root directories on UNIX platforms, and
 * drive specifiers, root directories and UNC pathnames on Win32 platforms, as
 * follows:
 *
 * <ul>
 * <li>For 1-Wire the Filesystem , the prefix of an absolute pathname is always
 * <code>"/"</code>. The abstract pathname denoting the root directory has the
 * prefix <code>"/"</code> and an empty name sequence.
 *
 * </ul>
 *
 * <p>
 * Instances of the <code>OWFile</code> class are immutable; that is, once
 * created, the abstract pathname represented by a <code>OWFile</code> object
 * will never change.
 *
 * <H3>What is Different on the 1-Wire Filesystem</H3>
 * <p>
 * The methods in the class are the same as in the java.io.File version 1.2 with
 * the following exceptions
 * <p>
 * <p>
 * Methods provided but of <b> limited </b> functionality
 * <ul>
 * <li>public long lastModified() - always returns 0
 * <li>public boolean isAbsolute() - always true
 * <li>public boolean setLastModified(long time) - does nothing
 * <li>public boolean setReadOnly() - only for files
 * <li>public boolean isHidden() - only could be true for directories
 * </ul>
 *
 * <p>
 * Methods <b> not </b> provided or supported:
 * <ul>
 * <li>public void deleteOnExit()
 * <li>public String[] list(FilenameFilter filter)
 * <li>public File[] listFiles(FilenameFilter filter)
 * <li>public File[] listFiles(FileFilter filter)
 * <li>public static File createTempFile(String prefix, String suffix, File
 * directory)
 * <li>public static File createTempFile(String prefix, String suffix)
 * <li>public URL toURL()
 * </ul>
 *
 * <p>
 * <b> Extra </b> Methods (not usually in 1.2 java.io.File)
 * <ul>
 * <li>public OWFileDescriptor getFD()
 * <li>public void close()
 * <li>public OneWireContainer getOneWireContainer()
 * <li>public void format()
 * <li>public int getFreeMemory()
 * <li>public int[] getPageList()
 * <li>public PagedMemoryBank getMemoryBankForPage(int)
 * <li>public int getLocalPage(int)
 * </ul>
 *
 * <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>Tips</H3>
 * <ul>
 * <li><i> Writes </i> will not be flushed to the 1-Wire device Filesystem until
 * the <code> OWFile </code> instance is closed with the <code> close() </code>
 * method or the <code> sync() </code> method from the OWFileDescriptor
 * <li>The <code> sync() </code> method for flushing the changes to the
 * Filesystem is preferred since it can be called multiple times if there is a
 * connection problem
 * <li>New 1-Wire devices Filesystem must first be formatted with the
 * <code> format() </code> method in order for files or directories to be added
 * or changed.
 * <li>Multiple 1-Wire devices can be linked into a common Filesystem by using
 * the constructor that accepts an array of 1-Wire devices. The first device in
 * the list is the 'root' device and the rest will be designated 'satelite's.
 * Once the <code> format() </code> method is used to link these devices then
 * only the 'root' need be used in future constructors of this class or the
 * 1-Wire file stream classes.
 * <li>Only rewrittable 1-Wire memory devices can be used in multi-device file
 * systems. EPROM and write-once devices can only be used in single device file
 * systems.
 * <li>1-Wire devices have a limited amount of space. Use the
 * <code> getFreeMemory() </code> method to get an estimate of free memory
 * available.
 * <li>Call the <code> close() </code> method to release system resources
 * allocated when done with the <code> OWFile </code> instance
 * </ul>
 *
 * <H3>Usage</H3>
 * <DL>
 * <DD>
 * <H4>Example 1</H4> Format the Filesystem of the 1-Wire device 'owd':
 *
 * <PRE>
 *  <CODE>
 *   // create a 1-Wire file at root
 *   OWFile owfile = new OWFile(owd, "");
 *
 *   // format Filesystem
 *   owfile.format();
 *
 *   // get 1-Wire File descriptor to flush to device
 *   OWFileDescriptor owfd = owfile.getFD();
 *   // loop until sync is successful
 *   do
 *   {
 *      try
 *      {
 *         owfd.sync();
 *         done = true;
 *      }
 *      catch (SyncFailedException e)
 *      {
 *         // do something
 *         ...
 *         done = false;
 *      }
 *   }
 *   while (!done)
 *
 *   // close the 1-Wire file to release system resources
 *   owfile.close();
 * </CODE>
 * </PRE>
 *
 * <DD>
 * <H4>Example 2</H4> Make a multi-level directory structure on the 1-Wire
 * device 'owd':
 *
 * <PRE>
 *  <CODE>
 *   OWFile owfile = new OWFile(owd, "/doc/text/temp");
 *
 *   // make the directories
 *   if (owfile.mkdirs())
 *      System.out.println("Success!");
 *   else
 *      System.out.println("Out of memory or invalid file/directory");
 *
 *   // get 1-Wire File descriptor to flush to device
 *   ...
 * </CODE>
 * </PRE>
 * </DL>
 *
 * <H3>1-Wire File Structure Format</H3>
 * <DL>
 * <DD><A HREF="http://pdfserv.maxim-ic.com/arpdf/AppNotes/app114.pdf">
 * http://pdfserv.maxim-ic.com/arpdf/AppNotes/app114.pdf</A>
 * </DL>
 *
 * @see com.dalsemi.onewire.application.file.OWFileDescriptor
 * @see com.dalsemi.onewire.application.file.OWFileInputStream
 * @see com.dalsemi.onewire.application.file.OWFileOutputStream
 *
 * @author DS
 * @version 0.01, 1 June 2001
 */
public class OWFile {

	// --------
	// -------- Static Variables
	// --------

	/** Field separator */
	public static final String separator = "/";

	/** Field separatorChar */
	public static final char separatorChar = '/';

	/** Field pathSeparator */
	public static final String pathSeparator = ":";

	/** Field pathSeparatorChar */
	public static final char pathSeparatorChar = ':';

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

	/** Abstract file descriptor containing this file */
	private OWFileDescriptor fd;

	// --------
	// -------- Constructor
	// --------

	/**
	 * Creates a new <code>OWFile</code> instance by converting the given pathname
	 * string into an abstract pathname. If the given string is the empty string,
	 * then the result is the empty abstract pathname.
	 *
	 * @param owd      OneWireContainer that this Filesystem resides on
	 * @param pathname A pathname string
	 * @throws NullPointerException If the <code>pathname</code> argument is
	 *                              <code>null</code>
	 */
	public OWFile(OneWireContainer owd, String pathname) {
		this.fd = new OWFileDescriptor(owd, pathname);
	}

	/**
	 * Creates a new <code>OWFile</code> instance by converting the given pathname
	 * string into an abstract pathname. If the given string is the empty string,
	 * then the result is the empty abstract pathname.
	 *
	 * @param owd      ordered array of OneWireContainers that this Filesystem
	 *                 resides on
	 * @param pathname A pathname string
	 * @throws NullPointerException If the <code>pathname</code> argument is
	 *                              <code>null</code>
	 *
	 *                              Change the OWFileDescriptor to accept only an
	 *                              array of containers Change the local ref to be
	 *                              an array Create a single array in constructors
	 *                              with single passed owc
	 *
	 */
	public OWFile(OneWireContainer[] owd, String pathname) {
		this.fd = new OWFileDescriptor(owd, pathname);
	}

	/*
	 * Note: The two-argument File constructors do not interpret an empty parent
	 * abstract pathname as the current user directory. An empty parent instead
	 * causes the child to be resolved against the system-dependent directory
	 * defined by the FileSystem.getDefaultParent method. On Unix this default is
	 * "/", while on Win32 it is "\\". This is required for compatibility with the
	 * original behavior of this class.
	 */

	/**
	 * Creates a new <code>OWFile</code> instance from a parent pathname string and
	 * a child pathname string.
	 *
	 * <p>
	 * If <code>parent</code> is <code>null</code> then the new <code>OWFile</code>
	 * instance is created as if by invoking the single-argument <code>OWFile</code>
	 * constructor on the given <code>child</code> pathname string.
	 *
	 * <p>
	 * Otherwise the <code>parent</code> pathname string is taken to denote a
	 * directory, and the <code>child</code> pathname string is taken to denote
	 * either a directory or a file. If the <code>child</code> pathname string is
	 * absolute then it is converted into a relative pathname in a system-dependent
	 * way. If <code>parent</code> is the empty string then the new
	 * <code>OWFile</code> instance is created by converting <code>child</code> into
	 * an abstract pathname and resolving the result against a system-dependent
	 * default directory. Otherwise each pathname string is converted into an
	 * abstract pathname and the child abstract pathname is resolved against the
	 * parent.
	 *
	 * @param owd    OneWireContainer that this Filesystem resides on
	 * @param parent The parent pathname string
	 * @param child  The child pathname string
	 * @throws NullPointerException If <code>child</code> is <code>null</code>
	 */
	public OWFile(OneWireContainer owd, String parent, String child) {
		if (child == null) {
			throw new NullPointerException("child is null");
		}

		this.fd = new OWFileDescriptor(owd, parent + child);
	}

	/**
	 * Creates a new <code>OWFile</code> instance from a parent abstract pathname
	 * and a child pathname string.
	 *
	 * <p>
	 * If <code>parent</code> is <code>null</code> then the new <code>OWFile</code>
	 * instance is created as if by invoking the single-argument <code>OWFile</code>
	 * constructor on the given <code>child</code> pathname string.
	 *
	 * <p>
	 * Otherwise the <code>parent</code> abstract pathname is taken to denote a
	 * directory, and the <code>child</code> pathname string is taken to denote
	 * either a directory or a file. If the <code>child</code> pathname string is
	 * absolute then it is converted into a relative pathname in a system-dependent
	 * way. If <code>parent</code> is the empty abstract pathname then the new
	 * <code>OWFile</code> instance is created by converting <code>child</code> into
	 * an abstract pathname and resolving the result against a system-dependent
	 * default directory. Otherwise each pathname string is converted into an
	 * abstract pathname and the child abstract pathname is resolved against the
	 * parent.
	 *
	 * @param parent The parent abstract pathname
	 * @param child  The child pathname string
	 * @throws NullPointerException If <code>child</code> is <code>null</code>
	 */
	public OWFile(OWFile parent, String child) {
		if (child == null) {
			throw new NullPointerException("child is null");
		}

		String new_path;

		if (parent.getAbsolutePath().endsWith("/")) {
			new_path = parent.getAbsolutePath() + child;
		} else {
			new_path = parent.getAbsolutePath() + separator + child;
		}

		this.fd = new OWFileDescriptor(parent.getOneWireContainers(), new_path);
	}

	// --------
	// -------- Path 'get' Methods
	// --------

	/**
	 * Returns the name of the file or directory denoted by this abstract pathname.
	 * This is just the last name in the pathname's name sequence. If the pathname's
	 * name sequence is empty, then the empty string is returned.
	 *
	 * @return The name of the file or directory denoted by this abstract pathname,
	 *         or the empty string if this pathname's name sequence is empty
	 */
	public String getName() {
		return this.fd.getName();
	}

	/**
	 * Returns the pathname string of this abstract pathname's parent, or
	 * <code>null</code> if this pathname does not name a parent directory.
	 *
	 * <p>
	 * The <em>parent</em> of an abstract pathname consists of the pathname's
	 * prefix, if any, and each name in the pathname's name sequence except for the
	 * last. If the name sequence is empty then the pathname does not name a parent
	 * directory.
	 *
	 * @return The pathname string of the parent directory named by this abstract
	 *         pathname, or <code>null</code> if this pathname does not name a
	 *         parent
	 */
	public String getParent() {
		return this.fd.getParent();
	}

	/**
	 * Returns the abstract pathname of this abstract pathname's parent, or
	 * <code>null</code> if this pathname does not name a parent directory.
	 *
	 * <p>
	 * The <em>parent</em> of an abstract pathname consists of the pathname's
	 * prefix, if any, and each name in the pathname's name sequence except for the
	 * last. If the name sequence is empty then the pathname does not name a parent
	 * directory.
	 *
	 * @return The abstract pathname of the parent directory named by this abstract
	 *         pathname, or <code>null</code> if this pathname does not name a
	 *         parent
	 */
	public OWFile getParentFile() {
		return new OWFile(this.fd.getOneWireContainers(), this.fd.getParent());
	}

	/**
	 * Converts this abstract pathname into a pathname string. The resulting string
	 * uses the {@link #separator default name-separator character} to separate the
	 * names in the name sequence.
	 *
	 * @return The string form of this abstract pathname
	 */
	public String getPath() {
		return this.fd.getPath();
	}

	// --------
	// -------- Path Operations
	// --------

	/**
	 * Tests whether this abstract pathname is absolute. The definition of absolute
	 * pathname is system dependent. On UNIX systems, a pathname is absolute if its
	 * prefix is <code>"/"</code>. On Win32 systems, a pathname is absolute if its
	 * prefix is a drive specifier followed by <code>"\\"</code>, or if its prefix
	 * is <code>"\\"</code>.
	 *
	 * @return <code>true</code> if this abstract pathname is absolute,
	 *         <code>false</code> otherwise
	 */
	public boolean isAbsolute() {

		// always absolute
		return true;
	}

	/**
	 * Returns the absolute pathname string of this abstract pathname.
	 *
	 * <p>
	 * If this abstract pathname is already absolute, then the pathname string is
	 * simply returned as if by the <code>{@link #getPath}</code> method. If this
	 * abstract pathname is the empty abstract pathname then the pathname string of
	 * the current user directory, which is named by the system property
	 * <code>user.dir</code>, is returned. Otherwise this pathname is resolved in a
	 * system-dependent way. On UNIX systems, a relative pathname is made absolute
	 * by resolving it against the current user directory. On Win32 systems, a
	 * relative pathname is made absolute by resolving it against the current
	 * directory of the drive named by the pathname, if any; if not, it is resolved
	 * against the current user directory.
	 *
	 * @return The absolute pathname string denoting the same file or directory as
	 *         this abstract pathname
	 *
	 * @see java.io.File#isAbsolute()
	 */
	public String getAbsolutePath() {
		return this.fd.getPath();
	}

	/**
	 * Returns the absolute form of this abstract pathname. Equivalent to
	 * <code>new&nbsp;File(this.{@link #getAbsolutePath}())</code>.
	 *
	 * @return The absolute abstract pathname denoting the same file or directory as
	 *         this abstract pathname
	 */
	public OWFile getAbsoluteFile() {
		return new OWFile(this.fd.getOneWireContainers(), this.fd.getPath());
	}

	/**
	 * Returns the canonical pathname string of this abstract pathname.
	 *
	 * <p>
	 * The precise definition of canonical form is system-dependent, but canonical
	 * forms are always absolute. Thus if this abstract pathname is relative it will
	 * be converted to absolute form as if by the <code>{@link
	 * #getAbsoluteFile}</code> method.
	 *
	 * <p>
	 * Every pathname that denotes an existing file or directory has a unique
	 * canonical form. Every pathname that denotes a nonexistent file or directory
	 * also has a unique canonical form. The canonical form of the pathname of a
	 * nonexistent file or directory may be different from the canonical form of the
	 * same pathname after the file or directory is created. Similarly, the
	 * canonical form of the pathname of an existing file or directory may be
	 * different from the canonical form of the same pathname after the file or
	 * directory is deleted.
	 *
	 * @return The canonical pathname string denoting the same file or directory as
	 *         this abstract pathname
	 *
	 * @throws IOException If an I/O error occurs, which is possible because the
	 *                     construction of the canonical pathname may require
	 *                     filesystem queries
	 *
	 * @since JDK1.1
	 */
	public String getCanonicalPath() throws IOException {
		return this.fd.getPath();
	}

	/**
	 * Returns the canonical form of this abstract pathname. Equivalent to
	 * <code>new&nbsp;File(this.{@link #getCanonicalPath}())</code>.
	 *
	 * @return The canonical pathname string denoting the same file or directory as
	 *         this abstract pathname
	 *
	 * @throws IOException If an I/O error occurs, which is possible because the
	 *                     construction of the canonical pathname may require
	 *                     filesystem queries
	 */
	public OWFile getCanonicalFile() throws IOException {
		return new OWFile(this.fd.getOneWireContainers(), this.fd.getPath());
	}

	// --------
	// -------- Attribute 'get' Methods
	// --------

	/**
	 * Tests whether the application can read the file denoted by this abstract
	 * pathname.
	 *
	 * @return <code>true</code> if and only if the file specified by this abstract
	 *         pathname exists <em>and</em> can be read by the application;
	 *         <code>false</code> otherwise
	 */
	public boolean canRead() {
		return this.fd.canRead();
	}

	/**
	 * Tests whether the application can modify to the file denoted by this abstract
	 * pathname.
	 *
	 * @return <code>true</code> if and only if the Filesystem actually contains a
	 *         file denoted by this abstract pathname <em>and</em> the application
	 *         is allowed to write to the file; <code>false</code> otherwise.
	 *
	 */
	public boolean canWrite() {
		return this.fd.canWrite();
	}

	/**
	 * Tests whether the file denoted by this abstract pathname exists.
	 *
	 * @return <code>true</code> if and only if the file denoted by this abstract
	 *         pathname exists; <code>false</code> otherwise
	 *
	 */
	public boolean exists() {
		return this.fd.exists();
	}

	/**
	 * Tests whether the file denoted by this abstract pathname is a directory.
	 *
	 * @return <code>true</code> if and only if the file denoted by this abstract
	 *         pathname exists <em>and</em> is a directory; <code>false</code>
	 *         otherwise
	 */
	public boolean isDirectory() {
		return this.fd.isDirectory();
	}

	/**
	 * Tests whether the file denoted by this abstract pathname is a normal file. A
	 * file is <em>normal</em> if it is not a directory and, in addition, satisfies
	 * other system-dependent criteria. Any non-directory file created by a Java
	 * application is guaranteed to be a normal file.
	 *
	 * @return <code>true</code> if and only if the file denoted by this abstract
	 *         pathname exists <em>and</em> is a normal file; <code>false</code>
	 *         otherwise
	 */
	public boolean isFile() {
		return this.fd.isFile();
	}

	/**
	 * Tests whether the file named by this abstract pathname is a hidden file. The
	 * exact definition of <em>hidden</em> is system-dependent. On UNIX systems, a
	 * file is considered to be hidden if its name begins with a period character
	 * (<code>'.'</code>). On Win32 systems, a file is considered to be hidden if it
	 * has been marked as such in the filesystem.
	 *
	 * @return <code>true</code> if and only if the file denoted by this abstract
	 *         pathname is hidden according to the conventions of the underlying
	 *         platform
	 */
	public boolean isHidden() {
		return this.fd.isHidden();
	}

	/**
	 * Returns the time that the file denoted by this abstract pathname was last
	 * modified.
	 *
	 * @return A <code>long</code> value representing the time the file was last
	 *         modified, measured in milliseconds since the epoch (00:00:00 GMT,
	 *         January 1, 1970), or <code>0L</code> if the file does not exist or if
	 *         an I/O error occurs
	 */
	public long lastModified() {

		// not supported
		return 0;
	}

	/**
	 * Returns the length of the file denoted by this abstract pathname.
	 *
	 * @return The length, in bytes, of the file denoted by this abstract pathname,
	 *         or <code>0L</code> if the file does not exist
	 */
	public long length() {
		return this.fd.length();
	}

	// --------
	// -------- File Operation Methods
	// --------

	/**
	 * Atomically creates a new, empty file named by this abstract pathname if and
	 * only if a file with this name does not yet exist. The check for the existence
	 * of the file and the creation of the file if it does not exist are a single
	 * operation that is atomic with respect to all other filesystem activities that
	 * might affect the file.
	 *
	 * @return <code>true</code> if the named file does not exist and was
	 *         successfully created; <code>false</code> if the named file already
	 *         exists
	 *
	 * @throws IOException If an I/O error occurred
	 */
	public boolean createNewFile() throws IOException {
		return this.fd.createNewFile();
	}

	/**
	 * Deletes the file or directory denoted by this abstract pathname. If this
	 * pathname denotes a directory, then the directory must be empty in order to be
	 * deleted.
	 *
	 * @return <code>true</code> if and only if the file or directory is
	 *         successfully deleted; <code>false</code> otherwise
	 */
	public boolean delete() {
		return this.fd.delete();
	}

	/**
	 * Returns an array of strings naming the files and directories in the directory
	 * denoted by this abstract pathname.
	 *
	 * <p>
	 * If this abstract pathname does not denote a directory, then this method
	 * returns <code>null</code>. Otherwise an array of strings is returned, one for
	 * each file or directory in the directory. Names denoting the directory itself
	 * and the directory's parent directory are not included in the result. Each
	 * string is a file name rather than a complete path.
	 *
	 * <p>
	 * There is no guarantee that the name strings in the resulting array will
	 * appear in any specific order; they are not, in particular, guaranteed to
	 * appear in alphabetical order.
	 *
	 * @return An array of strings naming the files and directories in the directory
	 *         denoted by this abstract pathname. The array will be empty if the
	 *         directory is empty. Returns <code>null</code> if this abstract
	 *         pathname does not denote a directory, or if an I/O error occurs.
	 */
	public String[] list() {
		if (this.isFile() || !this.isDirectory()) {
			return null;
		}
		return this.fd.list();
	}

	/**
	 * Returns an array of abstract pathnames denoting the files in the directory
	 * denoted by this abstract pathname.
	 *
	 * <p>
	 * If this abstract pathname does not denote a directory, then this method
	 * returns <code>null</code>. Otherwise an array of <code>OWFile</code> objects
	 * is returned, one for each file or directory in the directory. Pathnames
	 * denoting the directory itself and the directory's parent directory are not
	 * included in the result. Each resulting abstract pathname is constructed from
	 * this abstract pathname using the
	 * <code>{@link #OWFile(com.dalsemi.onewire.application.file.OWFile, java.lang.String)
	 * OWFile(OWFile,&nbsp;String)}</code> constructor. Therefore if this pathname
	 * is absolute then each resulting pathname is absolute; if this pathname is
	 * relative then each resulting pathname will be relative to the same directory.
	 *
	 * <p>
	 * There is no guarantee that the name strings in the resulting array will
	 * appear in any specific order; they are not, in particular, guaranteed to
	 * appear in alphabetical order.
	 *
	 * @return An array of abstract pathnames denoting the files and directories in
	 *         the directory denoted by this abstract pathname. The array will be
	 *         empty if the directory is empty. Returns <code>null</code> if this
	 *         abstract pathname does not denote a directory, or if an I/O error
	 *         occurs.
	 */
	public OWFile[] listFiles() {
		if (this.isFile() || !this.isDirectory()) {
			return null;
		}
		String[] str_list;
		OWFile[] file_list;
		String new_path;

		str_list = this.fd.list();
		file_list = new OWFile[str_list.length];

		for (var i = 0; i < str_list.length; i++) {
			if (this.fd.getPath() == null || this.fd.getPath().endsWith("/")) {
				new_path = "/" + str_list[i];
			} else {
				new_path = this.fd.getPath() + separator + str_list[i];
			}

			file_list[i] = new OWFile(this.fd.getOneWireContainers(), new_path);
		}

		return file_list;
	}

	/**
	 * Creates the directory named by this abstract pathname.
	 *
	 * @return <code>true</code> if and only if the directory was created;
	 *         <code>false</code> otherwise
	 */
	public boolean mkdir() {
		try {
			this.fd.create(false, true, false, -1, -1);

			return true;
		} catch (OWFileNotFoundException e) {
			return false;
		}
	}

	/**
	 * Creates the directory named by this abstract pathname, including any
	 * necessary but nonexistent parent directories. Note that if this operation
	 * fails it may have succeeded in creating some of the necessary parent
	 * directories.
	 *
	 * @return <code>true</code> if and only if the directory was created, along
	 *         with all necessary parent directories; <code>false</code> otherwise
	 */
	public boolean mkdirs() {
		try {
			this.fd.create(false, true, true, -1, -1);

			return true;
		} catch (OWFileNotFoundException e) {
			return false;
		}
	}

	/**
	 * Renames the file denoted by this abstract pathname.
	 *
	 * @param dest The new abstract pathname for the named file
	 *
	 * @return <code>true</code> if and only if the renaming succeeded;
	 *         <code>false</code> otherwise
	 *
	 * @throws NullPointerException If parameter <code>dest</code> is
	 *                              <code>null</code>
	 */
	public boolean renameTo(OWFile dest) {
		return this.fd.renameTo(dest);
	}

	/**
	 * Sets the last-modified time of the file or directory named by this abstract
	 * pathname.
	 *
	 * <p>
	 * All platforms support file-modification times to the nearest second, but some
	 * provide more precision. The argument will be truncated to fit the supported
	 * precision. If the operation succeeds and no intervening operations on the
	 * file take place, then the next invocation of the
	 * <code>{@link #lastModified}</code> method will return the (possibly
	 * truncated) <code>time</code> argument that was passed to this method.
	 *
	 * @param time The new last-modified time, measured in milliseconds since the
	 *             epoch (00:00:00 GMT, January 1, 1970)
	 *
	 * @return <code>true</code> if and only if the operation succeeded;
	 *         <code>false</code> otherwise
	 *
	 * @throws IllegalArgumentException If the argument is negative
	 */
	public boolean setLastModified(long time) {
		return false; // not supported
	}

	/**
	 * Marks the file or directory named by this abstract pathname so that only read
	 * operations are allowed. After invoking this method the file or directory is
	 * guaranteed not to change until it is either deleted or marked to allow write
	 * access. Whether or not a read-only file or directory may be deleted depends
	 * upon the underlying system.
	 *
	 * @return <code>true</code> if and only if the operation succeeded;
	 *         <code>false</code> otherwise
	 */
	public boolean setReadOnly() {
		return this.fd.setReadOnly();
	}

	// --------
	// -------- Filesystem Interface Methods
	// --------

	/**
	 * List the available filesystem roots.
	 *
	 * <p>
	 * A particular Java platform may support zero or more hierarchically-organized
	 * Filesystems. Each Filesystem has a <code>root</code> directory from which all
	 * other files in that file system can be reached. Windows platforms, for
	 * example, have a root directory for each active drive; UNIX platforms have a
	 * single root directory, namely <code>"/"</code>. The set of available
	 * filesystem roots is affected by various system-level operations such the
	 * insertion or ejection of removable media and the disconnecting or unmounting
	 * of physical or virtual disk drives.
	 *
	 * <p>
	 * This method returns an array of <code>OWFile</code> objects that denote the
	 * root directories of the available filesystem roots. It is guaranteed that the
	 * canonical pathname of any file physically present on the local machine will
	 * begin with one of the roots returned by this method.
	 *
	 * <p>
	 * The canonical pathname of a file that resides on some other machine and is
	 * accessed via a remote-filesystem protocol such as SMB or NFS may or may not
	 * begin with one of the roots returned by this method. If the pathname of a
	 * remote file is syntactically indistinguishable from the pathname of a local
	 * file then it will begin with one of the roots returned by this method. Thus,
	 * for example, <code>OWFile</code> objects denoting the root directories of the
	 * mapped network drives of a Windows platform will be returned by this method,
	 * while <code>OWFile</code> objects containing UNC pathnames will not be
	 * returned by this method.
	 *
	 * @param owc OneWireContainer that this Filesystem resides on
	 *
	 * @return An array of <code>OWFile</code> objects denoting the available
	 *         filesystem roots, or <code>null</code> if the set of roots could not
	 *         be determined. The array will be empty if there are no filesystem
	 *         roots.
	 */
	public static OWFile[] listRoots(OneWireContainer owc) {
		var roots = new OWFile[1];

		roots[0] = new OWFile(owc, "/");

		return roots;
	}

	// --------
	// -------- Misc Methods
	// --------

	/**
	 * Compares two abstract pathnames lexicographically. The ordering defined by
	 * this method depends upon the underlying system. On UNIX systems, alphabetic
	 * case is significant in comparing pathnames; on Win32 systems it is not.
	 *
	 * @param pathname The abstract pathname to be compared to this abstract
	 *                 pathname
	 *
	 * @return Zero if the argument is equal to this abstract pathname, a value less
	 *         than zero if this abstract pathname is lexicographically less than
	 *         the argument, or a value greater than zero if this abstract pathname
	 *         is lexicographically - greater than the argument
	 */
	public int compareTo(OWFile pathname) {
		var owd = this.fd.getOneWireContainers();
		var this_path = owd[0].getAddressAsString() + this.getPath();
		var compare_path = pathname.getOneWireContainer().getAddressAsString() + pathname.getPath();

		return this_path.compareTo(compare_path);
	}

	/**
	 * Compares this abstract pathname to another object. If the other object is an
	 * abstract pathname, then this function behaves like <code>{@link
	 * #compareTo(OWFile)}</code>. Otherwise, it throws a
	 * <code>ClassCastException</code>, since abstract pathnames can only be
	 * compared to abstract pathnames.
	 *
	 * @param o The <code>Object</code> to be compared to this abstract pathname
	 *
	 * @return If the argument is an abstract pathname, returns zero if the argument
	 *         is equal to this abstract pathname, a value less than zero if this
	 *         abstract pathname is lexicographically less than the argument, or a
	 *         value greater than zero if this abstract pathname is
	 *         lexicographically greater than the argument
	 *
	 * @throws ClassCastException if the argument is not an abstract pathname
	 *
	 * @see java.lang.Comparable
	 */
	public int compareTo(Object o) {
		return this.compareTo((OWFile) o);
	}

	/**
	 * Tests this abstract pathname for equality with the given object. Returns
	 * <code>true</code> if and only if the argument is not <code>null</code> and is
	 * an abstract pathname that denotes the same file or directory as this abstract
	 * pathname. Whether or not two abstract pathnames are equal depends upon the
	 * underlying system. On UNIX systems, alphabetic case is significant in
	 * comparing pathnames; on Win32 systems it is not.
	 *
	 * @param obj The object to be compared with this abstract pathname
	 *
	 * @return <code>true</code> if and only if the objects are the same;
	 *         <code>false</code> otherwise
	 */
	@Override
	public boolean equals(Object obj) {
		if (obj == null) {
			return false;
		}

		if (!(obj instanceof OWFile)) {
			return false;
		}

		return this.compareTo((OWFile) obj) == 0;
	}

	/**
	 * Computes a hash code for this abstract pathname. Because equality of abstract
	 * pathnames is inherently system-dependent, so is the computation of their hash
	 * codes. On UNIX systems, the hash code of an abstract pathname is equal to the
	 * exclusive <em>or</em> of its pathname string and the decimal value
	 * <code>1234321</code>. On Win32 systems, the hash code is equal to the
	 * exclusive <em>or</em> of its pathname string, converted to lower case, and
	 * the decimal value <code>1234321</code>.
	 *
	 * @return A hash code for this abstract pathname
	 */
	@Override
	public int hashCode() {
		return this.fd.getHashCode();
	}

	/**
	 * Returns the pathname string of this abstract pathname. This is just the
	 * string returned by the <code>{@link #getPath}</code> method.
	 *
	 * @return The string form of this abstract pathname
	 */
	@Override
	public String toString() {
		return this.fd.getPath();
	}

	// --------
	// -------- Custom additions, not normally in File class
	// --------

	/**
	 * Returns the <code>OWFileDescriptor</code> object that represents the
	 * connection to the actual file in the Filesystem being used by this
	 * <code>OWFileInputStream</code>.
	 *
	 * @return the file descriptor object associated with this File.
	 * @exception IOException if an I/O error occurs.
	 * @see com.dalsemi.onewire.application.file.OWFileDescriptor
	 */
	public OWFileDescriptor getFD() throws IOException {
		return this.fd;
	}

	/**
	 * Gets the OneWireContainer that this File resides on. This is where the
	 * 'filesystem' resides. If this Filesystem spans multiple devices then this
	 * method returns the 'MASTER' device.
	 *
	 * @return the OneWireContainer for this Filesystem
	 */
	public OneWireContainer getOneWireContainer() {
		var owd = this.fd.getOneWireContainers();
		return owd[0];
	}

	/**
	 * Gets the OneWireContainer(s) that this File resides on. This is where the
	 * 'filesystem' resides. The first device is the 'MASTER' device and the other
	 * devices are 'SATELLITE' devices.
	 *
	 * @return the OneWireContainer(s) for this Filesystem
	 */
	public OneWireContainer[] getOneWireContainers() {
		return this.fd.getOneWireContainers();
	}

	/**
	 * Format the Filesystem on the 1-Wire device provided in the constructor. This
	 * operation is required before any file IO is possible.
	 * <P>
	 * <b>WARNING</b> this will remove any files/directories.
	 * <P>
	 *
	 * @exception IOException if an I/O error occurs.
	 */
	public void format() throws IOException {
		try {
			this.fd.format();
		} catch (OneWireException e) {
			throw new IOException(e.toString());
		}
	}

	/**
	 * Gets the number of bytes available on this device for file and directory
	 * information.
	 *
	 * @return number of free bytes in the Filesystem
	 *
	 * @exception IOException if an I/O error occurs
	 */
	public int getFreeMemory() throws IOException {
		try {
			return this.fd.getFreeMemory();
		} catch (OneWireException e) {
			throw new IOException(e.toString());
		}
	}

	/**
	 * Closes this file and releases any system resources associated with this
	 * stream. This file may no longer be used after this operation.
	 *
	 * @exception IOException if an I/O error occurs.
	 */
	public void close() throws IOException {
		this.fd.close();

		this.fd = null;
	}

	/**
	 * Gets an array of integers that represents the page list of the file or
	 * directory represented by this OWFile.
	 *
	 * @return node page list file or directory
	 *
	 * @exception IOException if an I/O error occurs.
	 */
	public int[] getPageList() throws IOException {
		if (this.fd == null) {
			return new int[0];
		}
		if (!this.fd.exists()) {
			return new int[0];
		}

		try {
			return this.fd.getPageList();
		} catch (OneWireException e) {
			throw new IOException(e.toString());
		}
	}

	/**
	 * Returns an integer which represents the starting memory page of the file or
	 * directory represented by this OWFile.
	 *
	 * @return The starting page of the file or directory.
	 *
	 * @exception IOException if the file doesn't exist
	 */
	public int getStartPage() throws IOException {
		if (this.fd != null && this.fd.exists()) {
			return this.fd.getStartPage();
		}
		throw new FileNotFoundException();
	}

	/**
	 * Gets the memory bank object for the specified page. This is significant if
	 * the Filesystem spans memory banks on the same or different devices.
	 *
	 * @return PagedMemoryBank for the specified page
	 */
	public PagedMemoryBank getMemoryBankForPage(int page) {
		if (this.fd == null) {
			return null;
		}
		if (!this.fd.exists()) {
			return null;
		}

		return this.fd.getMemoryBankForPage(page);
	}

	/**
	 * Gets the local page number on the memory bank object for the specified page.
	 * This is significant if the Filesystem spans memory banks on the same or
	 * different devices.
	 *
	 * @return local page for the specified Filesystem page (memory bank specific)
	 */
	public int getLocalPage(int page) {
		if (this.fd == null) {
			return 0;
		}
		if (!this.fd.exists()) {
			return 0;
		}

		return this.fd.getLocalPage(page);
	}

	/**
	 * Cleans up the connection to the file, and ensures that the <code>close</code>
	 * method of this file output stream is called when there are no more references
	 * to this stream.
	 *
	 * @exception IOException if an I/O error occurs.
	 * @see java.io.FileInputStream#close()
	 */
	@Override
	protected void finalize() throws IOException {
		if (this.fd != null) {
			this.fd.close();
		}
	}
}
// CHECKSTYLE:ON
