File Class

package javaxt.io;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.io.*;
import java.util.Locale;

//******************************************************************************
//**  File Class
//******************************************************************************
/**
 *   Used to represent a single file on a file system. In many ways, this class
 *   is an extension of the java.io.File class. However, unlike the java.io.File
 *   class, this object provides functions that are relevant and specific to
 *   files (not directories).
 *
 ******************************************************************************/

public class File implements Comparable {

    private java.io.File file; //<--DO NOT USE DIRECTLY! Use getFile() instead.
    private String name = "";
    private String path = "";
    private FileAttributes attr;
    private long lastAttrUpdate = 0;

    public final String PathSeparator = System.getProperty("file.separator");
    public final String LineSeperator = System.getProperty("line.separator");
    private static final boolean isWindows = Directory.isWindows;
    private int bufferSize = 1024*1024; //1MB


  //**************************************************************************
  //** Constructor
  //**************************************************************************
  /** Creates a new File instance by converting the given pathname string into
   *  an abstract pathname.
   */
    public File(String Path) {
	if (Path == null) {
	    throw new NullPointerException();
	}

        if (Path.startsWith("\"") && Path.endsWith("\"")){
            Path = Path.substring(1,Path.length()-1).trim();
        }
        if (Path.endsWith("\\") || Path.endsWith("/")){
            throw new IllegalArgumentException("Invalid Path: " + Path);
        }

        init(Path);

        //int idx = Path.replace("\\", "/").lastIndexOf("/")+1;
        //path = Path.substring(0, idx);
        //name = Path.substring(idx);
    }


  //**************************************************************************
  //** Constructor
  //**************************************************************************
  /** Instantiates this class using a java.io.File. Please use the Directory
   *  class for directories. Example:
   * <pre>if (file.isDirectory()) new Directory(file);</pre>
   */
    public File(java.io.File File) {
	if (File==null) {
	    throw new NullPointerException();
	}

        if (File.isDirectory()){
            throw new IllegalArgumentException("Invalid Path.");
        }

        String p = File.getAbsolutePath();
        init(p);


        if (p.equals(toString())){
            this.file = File;
        }
    }


  //**************************************************************************
  //** Constructor
  //**************************************************************************
  /** Creates a new File instance from a parent abstract pathname and a child
   *  pathname string.
   */
    public File(java.io.File Parent, String Child){
        this(Parent.getPath(), Child);
    }

    public File(Directory Parent, String Child){
        this(Parent.getPath(), Child);
    }

    public File(String Parent, String Child){
	if (Parent == null || Child == null) {
	    throw new NullPointerException();
	}

      //Normalize the strings
        Parent = Parent.replace("\\", "/");
        Child = Child.replace("\\", "/");

      //Combine the Parent and Child into a single path. This is done in
      //case the Child variable contains additional path information.
        if (!Parent.endsWith("/")) Parent += "/";
        if (Child.startsWith("/")) Child = Child.substring(1);
        if (Child.endsWith("/")) Child = Child.substring(0, Child.length()-1);

        init(Parent + Child);
    }


  /** Used to parse the absolute path to the file. For performance reasons,
   *  we do not rely on the java.io.File to determine the path and name
   *  variables.
   */
    private void init(String Path){
        Path = Path.replace("\\", "/");
        String[] arr = Path.split("/");
        if (arr.length>1){
            name = arr[arr.length-1];
            path = Path.substring(0, Path.lastIndexOf(name));
        }
        else{
            name = arr[0];
            path = "./";
        }

        if (path.isEmpty()) path = PathSeparator;
        else path = path.replace("/", PathSeparator);

        if (!path.endsWith(PathSeparator)) path += PathSeparator;


      //Special case for relative paths
        if (path.startsWith(".")){
            try{
                init(new java.io.File(path + name).getCanonicalPath());
            }
            catch(Exception e){
                throw new IllegalArgumentException("Invalid Path.");
            }
        }
    }


  //**************************************************************************
  //** Get File Name
  //**************************************************************************
  /**  Returns the name of the file, excluding the path. */

    public String getName(){
        return name;
    }


  //**************************************************************************
  //** Get File Name
  //**************************************************************************
  /**  Returns the name of the file, excluding the path.
   *
   *   @param IncludeFileExtension If true, includes the file extension.
   *   Otherwise, will return the file name without the extension.
   */
    public String getName(boolean IncludeFileExtension){
        if (!IncludeFileExtension){
            int idx = name.lastIndexOf(".");
            if (idx > -1) return name.substring(0, idx);
        }
        return name;
    }


  //**************************************************************************
  //** Get File Path
  //**************************************************************************
  /**  Used to retrieve the path to the file, excluding the file name. Appends
   *   a file separator to the end of the string.
   */
    public String getPath(){
        return path;
    }


  //**************************************************************************
  //** Get Directory
  //**************************************************************************
  /**  Returns the file's parent directory. Same as getParentDirectory() */

    public Directory getDirectory(){
        return getParentDirectory();
    }


  //**************************************************************************
  //** Get Parent Directory
  //**************************************************************************
  /** Returns the file's parent directory. Returns null if the parent
   *  directory is unknown.
   */
    public Directory getParentDirectory(){
        if (path.length()>0) return new Directory(path);
        else return null;
    }


  //**************************************************************************
  //** toFile
  //**************************************************************************
  /**  Returns the java.io.File representation of this object. */

    public java.io.File toFile(){
        return getFile();
    }


  //**************************************************************************
  //** getFile
  //**************************************************************************
  /**  Returns the java.io.File representation of this object. */

    private java.io.File getFile(){
        if (file==null) file = new java.io.File(path + name);
        return file;
    }


  //**************************************************************************
  //** Get File Extension
  //**************************************************************************
  /**  Returns the file's extension, excluding the last dot/period
   *   (e.g. "C:\image.jpg" will return "jpg"). Returns a zero-length string
   *   if there is no extension.
   */
    public String getExtension(){
        int idx = name.lastIndexOf(".");
        if (idx > -1) return name.substring(idx+1);
        else return "";
    }


  //**************************************************************************
  //** getSize
  //**************************************************************************
  /** Returns the size of the file, in bytes. Returns 0L if the file is does
   *  not exist or if the object is a directory.
   */
    public long getSize(){
        if (file!=null) return getFileSize();

        try{
            FileAttributes attr = getFileAttributes();
            if (attr!=null) return attr.getSize();
            else{
                getFile();
                return getFileSize();
            }
        }
        catch(java.io.FileNotFoundException e){
            return 0L;
        }
    }

    private long getFileSize(){
        long size = file.length();
        if (size>0L && file.isFile()) return size;
        else return 0L;
    }


  //**************************************************************************
  //** getDate
  //**************************************************************************
  /** Returns a timestamp of when the file was last modified. Returns null if
   *  the file does not exist or if the object is a directory.
   */
    public java.util.Date getDate(){
        if (file!=null) return getFileDate();

        try{
            FileAttributes attr = getFileAttributes();
            if (attr!=null) return attr.getLastWriteTime();
            else{
                getFile();
                return getFileDate();
            }
        }
        catch(java.io.FileNotFoundException e){
            return null;
        }
    }

    private java.util.Date getFileDate(){
        if (file.exists() && file.isFile()) return new java.util.Date(file.lastModified());
        else return null;
    }


  //**************************************************************************
  //** setDate
  //**************************************************************************
  /**  Used to set/update the last modified date.
   */
    public boolean setDate(java.util.Date lastModified){
        if (lastModified!=null){
            java.io.File file = getFile();
            if (file.exists() && file.isFile()){
                long t = lastModified.getTime();
                if (getDate().getTime()!=t){
                    attr = null;
                    return getFile().setLastModified(t);
                }
            }
        }
        return false;
    }


  //**************************************************************************
  //** Exists
  //**************************************************************************
  /**  Used to determine whether a file exists. Returns false if the file
   *   system can't find the file or if the object is a directory.
   */
    public boolean exists(){
        if (file!=null){
            return (file.isFile() && file.exists());
        }

        try{
            FileAttributes attr = getFileAttributes();
            if (attr!=null) return true;
            else{
                getFile();
                return (file.isFile() && file.exists());
            }
        }
        catch(java.io.FileNotFoundException e){
            return false;
        }
    }


  //**************************************************************************
  //** isHidden
  //**************************************************************************
  /** Used to check whether the file is hidden. Returns true if the file
   *  exists and is hidden according to the conventions of the underlying
   *  file system.
   */
    public boolean isHidden(){
        if (file!=null){
            return (file.isFile() && file.isHidden());
        }

        try{
            FileAttributes attr = getFileAttributes();
            if (attr!=null) return attr.isHidden();
            else{
                getFile();
                return (file.isFile() && file.isHidden());
            }
        }
        catch(java.io.FileNotFoundException e){
            return false;
        }
    }


  //**************************************************************************
  //** isReadOnly
  //**************************************************************************
  /** Used to check whether the file has write permissions. Returns true if
   *  the file exists and the application is not allowed to write to the file.
   */
    public boolean isReadOnly(){
        if (file!=null){
            return (file.isFile() && !file.canWrite());
        }

        try{
            FileAttributes attr = getFileAttributes();
            if (attr!=null) return attr.isReadOnly();
            else{
                getFile();
                return (file.isFile() && !file.canWrite());
            }
        }
        catch(java.io.FileNotFoundException e){
            return false;
        }
    }


  //**************************************************************************
  //** isExecutable
  //**************************************************************************
  /** Used to check whether the file has execute permissions. Note that this
   *  method is not supported by JDK 1.5 or lower. Instead, the method will
   *  return false.
   */
    public boolean isExecutable(){
        java.io.File File = getFile();
        if (File!=null){

            //return File.canExecute(); //<--incompatable with JDK 1.5
            int javaVersion = javaxt.utils.Java.getVersion();
            if (javaVersion<6) return false;
            else{
                try{
                    return (Boolean) File.getClass().getMethod("canExecute").invoke(File, null);
                }
                catch(Exception e){
                    return false;
                }
            }
        }
        else return false;
    }


  //**************************************************************************
  //** isLink
  //**************************************************************************
  /**  Used to determine whether the file is actually a link to another file.
   *   Returns true for symbolic links, Windows junctions, and Windows
   *   shortcuts.
   */
    public boolean isLink(){
        return getLink()!=null;
    }


  //**************************************************************************
  //** getLink
  //**************************************************************************
  /**  Returns the target of a symbolic link, Windows junction, or Windows
   *   shortcut.
   */
    public java.io.File getLink(){
        try{
            return getFileAttributes().getLink();
        }
        catch(Exception e){
            return null;
        }
    }


  //**************************************************************************
  //** Delete File
  //**************************************************************************
  /**  Used to delete the file. Warning: this operation is irrecoverable. */

    public boolean delete(){
        java.io.File file = getFile();
        if (file!=null){
            if (file.delete()){
                attr=null;
                return true;
            }
        }
        return false;
    }


  //**************************************************************************
  //** setBufferSize
  //**************************************************************************
  /** Used to set the size of the buffer used to read/write bytes. The default
   *  is 1MB (1,048,576 bytes)
   */
    public void setBufferSize(int numBytes){
        bufferSize = numBytes;
    }


  //**************************************************************************
  //** Move File
  //**************************************************************************
  /** Used to move the file to a different directory. If the operation is
   *  successful, returns a handle to the new file. Otherwise, the original
   *  file is returned.
   */
    public javaxt.io.File moveTo(Directory Destination){
        return moveTo(new javaxt.io.File(Destination, getName()), true);
    }


  //**************************************************************************
  //** Move File
  //**************************************************************************
  /** Used to move the file to a different location. If the operation is
   *  successful, returns a handle to the new file. Otherwise, the original
   *  file is returned.
   */
    public javaxt.io.File moveTo(javaxt.io.File Destination, boolean Overwrite){
        if (Destination.exists()){
            if (Overwrite){
                Destination.delete();
            }
            else{
                return this;
            }
        }

        java.io.File oldFile = getFile();
        java.io.File newFile = Destination.toFile();
        newFile.getParentFile().mkdirs();
        if (oldFile.renameTo(newFile)){
            attr = null;
            file = newFile;
            init(file.getAbsolutePath());
        }
        return this;
    }


  //**************************************************************************
  //** Copy File
  //**************************************************************************
  /** Used to create a copy of this file. Preserves the last modified date
   *  associated with the source file. Returns true if the file was copied
   *  successfully.
   */
    public boolean copyTo(Directory Destination, boolean Overwrite){
        return copyTo(new File(Destination, getName()), Overwrite);
    }


  //**************************************************************************
  //** Copy File
  //**************************************************************************
  /** Used to create a copy of this file. Preserves the last modified date
   *  associated with the source file. Returns true if the file was copied
   *  successfully.
   */
    public boolean copyTo(javaxt.io.File Destination, boolean Overwrite){

      //Validate Input/Output
        java.io.File File = getFile();
        if (File.exists()==false) return false;
        if (Destination.exists() && Overwrite==false) return false;
        if (File.equals(Destination.toFile())) return false;

      //Create New Path
        Destination.getParentDirectory().create();

      //Copy File
        ReadableByteChannel inputChannel = null;
        WritableByteChannel outputChannel = null;
        try{
            FileInputStream input = new FileInputStream(File);
            FileOutputStream output = new FileOutputStream(Destination.toFile());
            inputChannel = Channels.newChannel(input);
            outputChannel = Channels.newChannel(output);
            final java.nio.ByteBuffer buffer = java.nio.ByteBuffer.allocateDirect(bufferSize);

            while (inputChannel.read(buffer) != -1) {
                buffer.flip();
                outputChannel.write(buffer);
                buffer.compact();

            }
            buffer.flip();
            while (buffer.hasRemaining()) {
                outputChannel.write(buffer);
            }

            inputChannel.close();
            outputChannel.close();

            Destination.toFile().setLastModified(File.lastModified());
            return true;
        }
        catch(Exception e){
            try{inputChannel.close();}catch(Exception ex){}
            try{outputChannel.close();}catch(Exception ex){}
            return false;
        }
    }


  //**************************************************************************
  //** Rename File
  //**************************************************************************
  /** Used to rename the file. The existing file name is replaced with a new
   *  name. Only the file name is affected. The file path remains unchanged.
   *  This method is therefore different from the java.io.File "renameTo"
   *  method. If the operation is successful, returns a handle to the new file.
   *  Otherwise, the original file is returned.
   *  @param FileName The new file name (including the file extension).
   */
    public javaxt.io.File rename(String FileName){
        if (FileName!=null){
            FileName = FileName.trim();
            if (FileName.length()>0){
                java.io.File File = getFile();
                if (File!=null) {
                    java.io.File newFile = new java.io.File(getPath() + FileName);
                    if (File.renameTo(newFile)){
                        attr = null;
                        file = newFile;
                        init(file.getAbsolutePath());
                    }
                }
            }
        }
        return this;
    }


  //**************************************************************************
  //** getBufferedWriter
  //**************************************************************************
  /**  Used to instantiate a BufferedWriter for this file.
   */
    public BufferedWriter getBufferedWriter(String charsetName){

        try{
            java.io.File File = getFile();
            attr = null;
            File.getParentFile().mkdirs();
            if (charsetName==null){
                return new BufferedWriter( new FileWriter(File) );
            }
            else{
                return new BufferedWriter(new OutputStreamWriter(new FileOutputStream(File), charsetName));
            }
        }
        catch (Exception e){
            return null;
        }
    }


  //**************************************************************************
  //** getBufferedReader
  //**************************************************************************
  /**  Used to extract the contents of the file into a BufferedReader.
   <pre>
    BufferedReader br = file.getBufferedReader("UTF-8");
    String strLine;
    while ((strLine = br.readLine()) != null){
       System.out.println(strLine);
    }
   </pre>
   *   @return BufferedReader or null
   */
    public BufferedReader getBufferedReader(){
        java.io.File File = getFile();
        if (File.exists()){
            try{
              //NOTE: FileReader always assumes default encoding is OK!
                return new BufferedReader( new FileReader(File) );
            }
            catch (Exception e){
            }
        }
        return null;
    }


  //**************************************************************************
  //** getBufferedReader
  //**************************************************************************
  /**  Used to extract the contents of the file into a BufferedReader.
   <pre>
    BufferedReader br = file.getBufferedReader("UTF-8");
    String strLine;
    while ((strLine = br.readLine()) != null){
       System.out.println (strLine);
    }
   </pre>
   *   WARNING: This method will never throw an error.
   *
   *   @param charsetName Name of the character encoding used to read the file.
   *   Examples include UTF-8 and ISO-8859-1
   */
    public BufferedReader getBufferedReader(String charsetName){
        java.io.File File = getFile();
        if (File.exists()){
            try{
                return new java.io.BufferedReader(new java.io.InputStreamReader(this.getInputStream(),charsetName));
            }
            catch (Exception e){
            }
        }
        return null;
    }


  //**************************************************************************
  //** getBufferedImage
  //**************************************************************************
  /** Returns the file contents as a BufferedImage. Returns a null if the file
   *  contents cannot be converted into a BufferedImage.
   */
    public java.awt.image.BufferedImage getBufferedImage(){
        Image img = getImage();
        if (img!=null) return img.getBufferedImage();
        return null;
    }


  //**************************************************************************
  //** getImage
  //**************************************************************************
  /** Returns the file contents as an Image (javaxt.io.Image). Returns a null
   *  if the file contents cannot be converted into an Image.
   */
    public Image getImage(){
        java.io.File File = getFile();
        if (File.exists()) return new Image(File);
        else return null;
    }


  //**************************************************************************
  //** getText
  //**************************************************************************
  /** Returns the file contents as a String. Returns an empty String if the
   *  file is empty or the contents cannot be converted to a String.
   */
    public String getText(){
        try{
            return getText("UTF-8");
        }
        catch(Exception e){}
        try{
            return getBytes().toString();
        }
        catch(Exception e){}
        return "";
    }


  //**************************************************************************
  //** getText
  //**************************************************************************
  /** Returns the file contents as a String. Returns an empty String if the
   *  file is empty or the contents cannot be converted to a String.
   *  @param charsetName Name of the character encoding used to read the file.
   *  Examples include UTF-8 and ISO-8859-1
   */
    public String getText(String charsetName){
        try{
           return getBytes().toString(charsetName);
        }
        catch(Exception e){}
        return "";
    }


  //**************************************************************************
  //** getXML
  //**************************************************************************
  /** Returns the file contents as a XML DOM Document(org.w3c.dom.Document).
   *  Returns a null if the file contents cannot be converted into a DOM
   *  Document.
   */
    public org.w3c.dom.Document getXML(){
        try (FileInputStream is = getInputStream()) {
            return javaxt.xml.DOM.createDocument(is);
        }
        catch(Exception e){
            return null;
        }
    }


  //**************************************************************************
  //** getJSONObject
  //**************************************************************************
  /** Returns the file contents as a JSON object (javaxt.json.JSONObject).
   *  Returns a null if the file contents cannot be converted into a JSON
   *  object.
   */
    public javaxt.json.JSONObject getJSONObject(){
        try {
            return new javaxt.json.JSONObject(getText());
        }
        catch(Exception e){
            return null;
        }
    }


  //**************************************************************************
  //** getJSONArray
  //**************************************************************************
  /** Returns the file contents as a JSON array (javaxt.json.JSONArray).
   *  Returns a null if the file contents cannot be converted into a JSON
   *  array.
   */
    public javaxt.json.JSONArray getJSONArray(){
        try {
            return new javaxt.json.JSONArray(getText());
        }
        catch(Exception e){
            return null;
        }
    }


  //**************************************************************************
  //** getBytes
  //**************************************************************************
  /** Returns the file contents as a ByteArrayOutputStream. Returns a null if
   *  the file contents cannot be converted to a ByteArrayOutputStream.
   */
    public ByteArrayOutputStream getBytes(){
        return getBytes(getFile(), bufferSize);
    }

    private static ByteArrayOutputStream getBytes(java.io.File File, int bufferSize){
        if (File.exists()){
            try (FileInputStream is = new FileInputStream(File)) {
                ByteArrayOutputStream bas = new ByteArrayOutputStream();
                byte[] b = new byte[bufferSize];
                int x;
                while((x=is.read(b,0,bufferSize))>-1) {
                    bas.write(b,0,x);
                }
                bas.close();
                return bas;
            }
            catch(Exception e){
            }
        }
        return null;
    }


  //**************************************************************************
  //** checksum
  //**************************************************************************
  /** Returns a long value representing a cyclic redundancy check
   * (CRC-32 checksum) of the file, or -1 if not known.
   */
    public long checksum(){
        if (!exists()) return -1;
        try{
            java.util.zip.CRC32 crc = new java.util.zip.CRC32();
            crc.update(getBytes().toByteArray());
            return crc.getValue();
        }
        catch(Exception e){
            return -1;
        }
    }


  //**************************************************************************
  //** getSHA1
  //**************************************************************************
  /** Returns a string representing the SHA-1 hash for the file.
   */
    public String getSHA1() {
        return getHash("SHA-1");
    }


  //**************************************************************************
  //** getMD5
  //**************************************************************************
  /** Returns a string representing the MD5 hash for the file.
   */
    public String getMD5() {
        return getHash("MD5");
    }


  //**************************************************************************
  //** getHash
  //**************************************************************************
    private String getHash(String algorithm){
        if (!exists()) return null;
        try (InputStream input = getInputStream()) {
            java.security.MessageDigest md = java.security.MessageDigest.getInstance(algorithm);
            byte[] buf = new byte[bufferSize];
            int i;
            while((i=input.read(buf))!=-1) {
                md.update(buf, 0, i);
            }
            //return new javax.xml.bind.annotation.adapters.HexBinaryAdapter().marshal(md.digest()).toLowerCase();
            return printHexBinary(md.digest()).toLowerCase();
        }
        catch(Exception e){
            return null;
        }
    }

    private static final char[] hexCode = "0123456789ABCDEF".toCharArray();

    private String printHexBinary(byte[] data) {
        if (data==null) return null;
        StringBuilder r = new StringBuilder(data.length * 2);
        for (byte b : data) {
            r.append(hexCode[(b >> 4) & 0xF]);
            r.append(hexCode[(b & 0xF)]);
        }
        return r.toString();
    }


  //**************************************************************************
  //** create
  //**************************************************************************
  /** Creates a new file if one does not exist.
   */
    public void create(){
        if (exists()) return;
        try {
            java.io.File file = getFile();
            file.getParentFile().mkdirs();
            file.createNewFile();
        }
        catch (Exception e){}
    }


  //**************************************************************************
  //** write
  //**************************************************************************
  /** Creates a new file using the given ByteArrayOutputStream. Note that this
   *  method will convert the ByteArrayOutputStream to a byte array using the
   *  toByteArray() method which may result in memory issues.
   */
    public void write(ByteArrayOutputStream bas){
        write(bas.toByteArray());
    }


  //**************************************************************************
  //** write
  //**************************************************************************
  /** Creates a new file using the given byte array.
   */
    public void write(byte[] bytes){
        java.io.File File = getFile();
        attr = null;
        if (File!=null){
            FileOutputStream output = null;
            try {
                File.getParentFile().mkdirs();
                output = new FileOutputStream(File);
                output.write(bytes);
            }
            catch (Exception e){}
            finally {
                try { if (output != null) output.close(); }
                catch (Exception e){}
            }
        }
    }


  //**************************************************************************
  //** write
  //**************************************************************************
  /** Creates a new file using the given InputStream. Note that the caller is
   *  responsible for closing the input stream after the method is complete.
   */
    public void write(InputStream input){
        java.io.File File = getFile();
        attr = null;
        if (File!=null){
            FileOutputStream output = null;
            try {
                File.getParentFile().mkdirs();
                output = new FileOutputStream(File);
                byte[] buf = new byte[bufferSize];
                int i = 0;
                while((i=input.read(buf))!=-1) {
                  output.write(buf, 0, i);
                }

            }
            catch (Exception e){}
            finally {
                try { if (output != null) output.close(); }
                catch (Exception e){}
            }
        }
    }


  //**************************************************************************
  //** Write Text
  //**************************************************************************
  /** Used to write text to a file. Uses UTF-8 character encoding. Use the
   *  other write method to specify a different character encoding (e.g.
   *  ISO-8859-1).
   */
    public void write(String Text){
        write(Text, "UTF-8");
    }


  //**************************************************************************
  //** Write Text
  //**************************************************************************
  /** Used to write text to a file. Allows users to specify character encoding.
   *
   *  @param charsetName Name of the character encoding used to read the file.
   *  Examples include UTF-8 and ISO-8859-1. If null, the writer will use the
   *  default character encoding defined on the host machine.
   */
    public void write(String Text, String charsetName){
        java.io.File File = getFile();
        attr = null;
        if (File!=null){
            Writer output = null;
            try {
                File.getParentFile().mkdirs();

                if (charsetName==null){
                    output = new BufferedWriter( new FileWriter(File) );
                }
                else{
                    output = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(File), charsetName));
                }

                output.write( Text );
            }
            catch (Exception e){}
            finally {
                try { if (output != null) output.close(); }
                catch (Exception e){}
            }
        }
    }


  //**************************************************************************
  //** Write XML
  //**************************************************************************
  /** Used to write an XML DOM Document to a file.
   */
    public void write(org.w3c.dom.Document xml){
        write(javaxt.xml.DOM.getText(xml), xml.getXmlEncoding());
    }


  //**************************************************************************
  //** write
  //**************************************************************************
  /** Used to write a JSON object to a file. Note that the JSON object is
   *  encoded using UTF-8.
   */
    public void write(javaxt.json.JSONObject json){
        write(json.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8));
    }


  //**************************************************************************
  //** write
  //**************************************************************************
  /** Used to write a JSON array to a file. Note that the JSON array is
   *  encoded using UTF-8.
   */
    public void write(javaxt.json.JSONArray arr){
        write(arr.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8));
    }


  //**************************************************************************
  //** Write Text
  //**************************************************************************
  /** Used to write text to a file using an array of strings. A new line is
   *  created for each entry in the array.
   */
    public void write(String[] Content){
        java.io.File File = getFile();
        attr = null;
        if (File!=null){

            Writer output = null;
            try {
                File.getParentFile().mkdirs();
                output = new BufferedWriter( new FileWriter(File) );
                for (int i=0; i<Content.length-1; i++){
                     output.write( Content[i] + LineSeperator);
                }
                output.write( Content[Content.length-1]);
            }
            catch (Exception e){}
            finally {
                try { if (output != null) output.close(); }
                catch (Exception e){}
            }

        }
    }


  //**************************************************************************
  //** Write Image
  //**************************************************************************
  /** Used to write an image to a file.
   */
    public void write(java.awt.image.BufferedImage Image){
        java.io.File File = getFile();
        attr = null;
        if (File!=null){
            try{

                File.getParentFile().mkdirs();
                java.awt.image.RenderedImage rendImage = Image;
                javax.imageio.ImageIO.write(rendImage,getExtension(),File);
            }
            catch (Exception e){
                //System.out.println(e.toString());
            }
        }
    }




  //**************************************************************************
  //** MapPath
  //**************************************************************************

    public String MapPath(String RelPath){

      //Update currDir
        String currDir = getPath();
        currDir = currDir.replace("\\","/");
        if (!currDir.endsWith("/")){
            currDir+="/";
        }


        RelPath = RelPath.replace("\\","/");


        if (RelPath.startsWith("/")){
            return (currDir + RelPath.substring(1)).replace("/", PathSeparator);
        }


        String[] arrRelPath = RelPath.split("/");
        String[] arrAbsPath = currDir.split("/");



        int x = -1;
        RelPath = "";
        String Dir = "";
        for (int i=0; i<arrRelPath.length; i++) {
            Dir = arrRelPath[i];
            if (Dir.equals("..")){
                x = x + 1;
            }
            else if (Dir.equals(".")){
                //do nothing?
            }
            else{
                RelPath = RelPath + "\\" + arrRelPath[i];
            }

        }


        //x = x + 1 'because currDir has a "\" at the end of it
        Dir = "";
        int ubound = 0;
        for (int i=0; i<arrAbsPath.length-(x+1); i++){ //because currDir has a "\" at the end of it
            Dir = Dir + arrAbsPath[i] + "\\";
        }


        //trim off last "\"
        //Dir = left(Dir, len(Dir) - 1);
        Dir = Dir.substring(0,Dir.length()-1);

        //replace any leftover "/" characters
        Dir = Dir + RelPath.replace("/", "\\");


        Dir = Dir.replace((CharSequence) "\\", (CharSequence) PathSeparator);

        return Dir;
    }


  //**************************************************************************
  //** getInputStream
  //**************************************************************************
  /** Returns a new FileInputStream
   */
    public FileInputStream getInputStream() throws IOException{
        return new FileInputStream(getFile());
    }


  //**************************************************************************
  //** getInputStream
  //**************************************************************************
  /**  Returns a new FileOutputStream
   */
    public FileOutputStream getOutputStream() throws IOException{
        attr = null;
        return new FileOutputStream(getFile());
    }


  //**************************************************************************
  //** toString
  //**************************************************************************
  /** Returns the full file path (including the file name)
   */
    public String toString(){
        return path + name;
    }


    @Override
    public int hashCode(){
        if (isWindows) return toString().toLowerCase(Locale.ENGLISH).hashCode() ^ 1234321; //java.io.Win32FileSystem.java
        else return toString().hashCode() ^ 1234321; //java.io.UnixFileSystem.java
    }


    //@Override
    public int compareTo(Object obj){
        if (obj==null) return -1;
        else return -obj.toString().compareTo(getPath());
    }


  //**************************************************************************
  //** equals
  //**************************************************************************
    public boolean equals(Object obj){
        if (obj instanceof javaxt.io.File || obj instanceof java.io.File){
            return obj.hashCode()==this.hashCode();
        }
        return false;
    }


  //**************************************************************************
  //** clone
  //**************************************************************************
  /** Creates a copy of this object.
   */
    public File clone(){
        return new File(this.toString());
    }


  //**************************************************************************
  //** IsValidPath -- NOT USED!
  //**************************************************************************
  /**  Checks whether PathToFile is a valid
   */
    private boolean isValidPath(String PathToFile){

        if (PathToFile==null) return false;
        if (PathToFile.length()<1) return false;

        //if (File.isDirectory()) return false;

        String FileName = getName();
        if (FileName.length()<1) return false;
        if (FileName.length()>260) return false;

        PathToFile = toString();
        PathToFile = PathToFile.replace((CharSequence) "\\", "/");
        String[] Path = PathToFile.split("/");
        String[] arr = new String[]{ "/", "?", "<", ">", "\\", ":", "*", "|", "\"" };

        for (int i=0; i<Path.length; i++){
             for (int j=0; j<arr.length; j++){
                  if (arr[j].equals(":") && i==0 & Path[i].length()==2){ //&& File.pathSeparator.equals(":")
                    //skip check b/c we've got something like "C:\" in the path
                  }
                  else{
                      if (Path[i].contains((CharSequence) arr[j])) return false;
                  }
             }
        }


        return true;

    }


  //**************************************************************************
  //** getContentType
  //**************************************************************************
  /** Returns the mime type associated with the file extension found in a
   *  given file name. This method only covers the most common/popular mime
   *  types. The returned mime type is NOT authoritative.
   *  @param file File name (e.g. hello.txt)
   *  @return Content type (e.g. text/plain)
   */
    public static String getContentType(String file){

      //Get file extension
        FileExtension ext = null;
        if (file!=null){
            file = file.trim();
            int x = file.lastIndexOf(".");
            if (x!=-1) ext = new FileExtension(file.substring(x+1));
        }
        if (ext==null) return "application/octet-stream";


      //TEXT
        if (ext.equals("css")) return "text/css";
        if (ext.equals("dtd")) return "text/plain";
        if (ext.equals("htm,html")) return "text/html";
        if (ext.equals("java")) return "text/plain";
        if (ext.equals("js")) return "text/javascript";
        if (ext.equals("txt")) return "text/plain";
        if (ext.equals("csv")) return "text/csv";

      //JSON
        if (ext.equals("json")) return "application/json";

      //IMAGE
        if (ext.equals("bmp")) return "image/bmp";
        if (ext.equals("gif")) return "image/gif";
        if (ext.equals("jp2,j2c,j2k,jpx")) return "image/jp2";
        if (ext.equals("jpg,jpe,jpeg,jfif,pjpeg,pjp")) return "image/jpeg";
        if (ext.equals("png")) return "image/png";
        if (ext.equals("psd")) return "image/x-photoshop";
        if (ext.equals("rgb")) return "image/x-rgb";
        if (ext.equals("tif,tiff")) return "image/tiff";
        if (ext.equals("xbm")) return "image/x-xbitmap";
        if (ext.equals("xpm")) return "image/x-xpixmap";
        if (ext.equals("ico")) return "image/vnd.microsoft.icon";
        if (ext.equals("svg")) return "image/svg+xml";

      //MICROSOFT OFFICE APPLICATIONS
        if (ext.equals("doc,dot")) return "application/msword";
        if (ext.equals("xls,xlw,xla,xlc,xlm,xlt,xll")) return "application/vnd.ms-excel";
        if (ext.equals("ppt,pps,pot")) return "application/vnd.ms-powerpoint";
        if (ext.equals("mdb")) return "application/x-msaccess";
        if (ext.equals("mpp")) return "application/vnd.ms-project";
        if (ext.equals("pub")) return "application/x-mspublisher";
        if (ext.equals("wmz")) return "application/x-ms-wmz";
        if (ext.equals("wmd")) return "application/x-ms-wmd";
        if (ext.equals("one,onetoc2,onetmp,onepkg")) return "application/msonenote";
        if (ext.equals("docx")) return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
        if (ext.equals("dotx")) return "application/vnd.openxmlformats-officedocument.wordprocessingml.template";
        if (ext.equals("xlsx")) return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
        if (ext.equals("xltx")) return "application/vnd.openxmlformats-officedocument.spreadsheetml.template";
        if (ext.equals("pptx")) return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
        if (ext.equals("ppsx")) return "application/vnd.openxmlformats-officedocument.presentationml.slideshow";
        if (ext.equals("potx")) return "application/vnd.openxmlformats-officedocument.presentationml.template";
        if (ext.equals("sldx")) return "application/vnd.openxmlformats-officedocument.presentationml.slide";

      //OTHER APPLICATIONS
        if (ext.equals("ai,eps,ps")) return "application/postscript";
        if (ext.equals("gz")) return "application/x-gzip";
        if (ext.equals("pdf")) return "application/pdf";
        if (ext.equals("xml")) return "application/xml"; //return "text/xml";
        if (ext.equals("z")) return "application/x-compress";
        if (ext.equals("zip")) return "application/zip";

      //AUDIO
        if (ext.equals("mid,midi")) return "audio/x-midi";
        if (ext.equals("mp1,mp2,mp3,mpa,mpega")) return "audio/x-mpeg";
        if (ext.equals("ra,ram")) return "audio/x-pn-realaudio";
        if (ext.equals("wav")) return "audio/x-wav";
        if (ext.equals("wma")) return "audio/x-ms-wma";
        if (ext.equals("wax")) return "audio/x-ms-wax";
        if (ext.equals("wmv")) return "audio/x-ms-wmv";

      //VIDEO
        if (ext.equals("asf,asx")) return "video/x-ms-asf";
        if (ext.equals("avi")) return "video/msvideo";
        if (ext.equals("mov")) return "video/quicktime";
        if (ext.equals("mpe,mpeg,mpg")) return "video/mpeg";
        if (ext.equals("mpv2")) return "video/mpeg2";
        if (ext.equals("qt,mov,moov")) return "video/quicktime";
        if (ext.equals("mp4")) return "video/mp4";

        if (ext.equals("wvx")) return "video/x-ms-wvx";
        if (ext.equals("wm")) return "video/x-ms-wm";
        if (ext.equals("wmx")) return "video/x-ms-wmx";

        return "application/octet-stream";
    }

    private static class FileExtension {
        private String ext;
        public FileExtension(String ext){
            this.ext = ext.toLowerCase();
        }

      /**
       *  @param obj Comma Separated List Of File Extensions
       */
        public boolean equals(Object obj){
            if (obj instanceof String){

                String[] arr = ((String) obj).split(",");
                for (int i=0; i<arr.length; i++){
                     String str = arr[i].trim().toLowerCase();
                     if (str.equals(ext)) return true;
                }

            }
            return false;
        }

        public int hashCode(){
            return ext.hashCode();
        }
    }


  //**************************************************************************
  //** getContentType
  //**************************************************************************
  /** Returns the mime type associated with the file extension. This method
   *  only covers the most common/popular mime types. The returned mime type
   *  is NOT authoritative.
   */
    public String getContentType(){
        return getContentType(this.getName());
    }


  //**************************************************************************
  //** setLastModifiedTime
  //**************************************************************************
  /** Used to update the timestamp of when the file was last modified.
   */
    public void setLastModifiedTime(java.util.Date date){
        setDate(date);
    }


  //**************************************************************************
  //** getLastModifiedTime
  //**************************************************************************
  /** Returns a timestamp of when the file was last modified. This is
   *  identical to the getDate() method.
   */
    public java.util.Date getLastModifiedTime(){
        return getDate();
    }


  //**************************************************************************
  //** getCreationTime
  //**************************************************************************
  /** Returns a timestamp of when the file was first created. Returns a null
   *  if the timestamp is not available.
   */
    public java.util.Date getCreationTime(){
        try{
            return getFileAttributes().getCreationTime();
        }
        catch(Exception e){
            return null;
        }
    }


  //**************************************************************************
  //** getLastAccessTime
  //**************************************************************************
  /** Returns a timestamp of when the file was last accessed. Returns a null
   *  if the timestamp is not available.
   */
    public java.util.Date getLastAccessTime(){
        try{
            return getFileAttributes().getLastAccessTime();
        }
        catch(Exception e){
            return null;
        }
    }


  //**************************************************************************
  //** getFlags
  //**************************************************************************
  /** Returns keywords representing file attributes (e.g. "READONLY", "HIDDEN",
   *  etc).
   */
    public java.util.HashSet<String> getFlags(){
        try{
            return getFileAttributes().getFlags();
        }
        catch(Exception e){
            return new java.util.HashSet<String>();
        }
    }


  //**************************************************************************
  //** getFileAttributes
  //**************************************************************************
  /** Returns file attributes such as when the file was first created and when
   *  it was last accessed. File attributes are cached for up to one second.
   *  This provides users the ability to retrieve multiple attributes at once.
   *  Without caching, we would have to ping the file system every time we call
   *  getLastAccessTime(), getLastAccessTime(), getLastWriteTime(), etc. The
   *  cached attributes are automatically updated when the file is updated or
   *  deleted by this class.
   */
    private FileAttributes getFileAttributes() throws java.io.FileNotFoundException {
        if (attr==null) lastAttrUpdate = 0;

        if (attr==null || (new java.util.Date().getTime()-lastAttrUpdate)>1000){
            try{
              //Get file attributes
                String pathToFile = toString();
                attr = new FileAttributes(pathToFile);
                if (attr.isDirectory()) throw new java.io.FileNotFoundException(pathToFile);

              //Set lastUpdate (used to cache file attributes)
                lastAttrUpdate = new java.util.Date().getTime();
            }
            catch(java.io.FileNotFoundException e){
                attr = null;
                throw e;
            }
            catch(Exception e){
                //e.printStackTrace();
                attr = null;
            }
        }
        return attr;
    }


  //**************************************************************************
  //** loadDLL
  //**************************************************************************
  /** Used to load the javaxt-core.dll. Returns a boolean to indicate load
   *  status. Note that the dll is only loaded once per JVM so it should be
   *  safe to call this method multiple times.
   */
    protected static synchronized boolean loadDLL(){

      //Try to load the dll as needed. Update the
        if (isWindows){

            if (dllLoaded==null){ //haven't tried to load the dll yet...
                String jvmPlatform = System.getProperty("os.arch");
                String dllName = null;
                if (jvmPlatform.equalsIgnoreCase("x86")){
                    dllName = "javaxt-core.dll";
                }
                else if(jvmPlatform.equalsIgnoreCase("amd64")){
                    dllName = "javaxt-core64.dll";
                }
                else{
                    dllLoaded = false;
                    return dllLoaded;
                }


              //Find dll entry in the zip/jar file
                Jar jar = new Jar(Jar.class);
                Jar.Entry entry = jar.getEntry(null, dllName);
                long checksum = entry.checksum();


              //Construct list of possible file locations for the dll
                java.util.ArrayList<java.io.File> files = new java.util.ArrayList<java.io.File>();
                files.add(new java.io.File(jar.getFile().getParentFile(), dllName));
                javaxt.io.Directory dir = new javaxt.io.Directory(System.getProperty("user.home"));
                for (String appDir : new String[]{"AppData\\Local", "Application Data"}){
                    javaxt.io.Directory d = new javaxt.io.Directory(dir + appDir);
                    java.io.File f = d.toFile(); //flip to file to prevent infinite recursion
                    if (f.exists() && f.canWrite()){
                        dir = d;
                        break;
                    }
                }
                files.add(new java.io.File(dir + "JavaXT\\" + dllName));


              //Try to load dll
                for (java.io.File dll : files){

                    if (dll.exists()){

                      //Check whether the dll equals the jar entry. Extract as needed.
                        byte[] b = new byte[(int)dll.length()];
                        java.io.DataInputStream is = null;
                        try{
                            is = new java.io.DataInputStream(new FileInputStream(dll));
                            is.readFully(b, 0, b.length);
                            is.close();
                            java.util.zip.CRC32 crc = new java.util.zip.CRC32();
                            crc.update(b);
                            if (checksum!=crc.getValue()){
                                dll.delete();
                                entry.extractFile(dll);
                            }
                        }
                        catch(Exception e){
                            try{is.close();}catch(Exception ex){}
                        }
                    }
                    else{

                      //File does not exist so extract the dll
                        entry.extractFile(dll);
                    }


                  //Load the dll
                    if (dll.exists()){
                        try{
                            System.load(dll.toString());
                            dllLoaded = true;
                            break;
                        }
                        catch(Exception e){
                            e.printStackTrace();
                        }
                    }
                }


              //Don't update the static variable to give users a chance to fix
              //the load error (e.g. manually extract the dll and copy it into
              //one of the directories).
              //dllLoaded = false;
            }

            return dllLoaded;
        }
        else{//not windows...
            return false;
        }
    }


    /** Used to determine whether the JVM is running on Mac OS X. */
    private static final boolean isOSX =
            System.getProperty("os.name").toLowerCase().contains("os x");

    private static final boolean isSolaris =
            System.getProperty("os.name").toLowerCase().contains("sunos");

  /** Used to track load status. Null = no load attempted, True = successfully
   *  loaded the dll, False = failed to load dll (don't try again). Do not try
   *  to modify the value directly. Use the loadDLL() method instead.
   */
    private static Boolean dllLoaded;


    /** JNI entry point to retrieve file attributes. */
    private static native long[] GetFileAttributesEx(String lpPathName) throws Exception;

    /** JNI entry point to retrieve link target. */
    private static native String GetTarget(String lpPathName) throws Exception;

     /** JNI entry point to retrieve a list of shared drives on a server. */
    protected static native String GetSharedDrives(String serverName) throws Exception;

     /** JNI entry point to retrieve a list of network drives mounted to the host. */
    protected static native String GetNetworkDrives() throws Exception;

     /** JNI entry point to retrieve a list of files and directories. */
    protected static native String GetFiles(String lpPathName) throws Exception;



//******************************************************************************
//**  FileAttributes Class
//******************************************************************************
/**
 *  Used to encapsulate extended file attributes. On unix and linux machines,
 *  this class is used to parse the output from ls. On windows, this class
 *  uses a JNI to return WIN32_FILE_ATTRIBUTE_DATA:
 *
 <pre>
    typedef struct _WIN32_FILE_ATTRIBUTE_DATA {
      DWORD dwFileAttributes;
      FILETIME ftCreationTime;
      FILETIME ftLastAccessTime;
      FILETIME ftLastWriteTime;
      DWORD nFileSizeHigh;
      DWORD nFileSizeLow;
    } WIN32_FILE_ATTRIBUTE_DATA;
 </pre>
 *
 ******************************************************************************/

public static class FileAttributes {

    private java.util.Date ftCreationTime;
    private java.util.Date ftLastAccessTime;
    private java.util.Date ftLastWriteTime;
    private long size;
    private java.util.HashSet<String> flags = new java.util.HashSet<String>();
    private java.io.File link;

    public FileAttributes(String path) throws java.io.FileNotFoundException, Exception {

        if (isWindows){

            if (loadDLL()){

              //Get attributes
                long[] attributes = null;
                try{
                    attributes = GetFileAttributesEx(path);
                }
                catch(Exception e){
                    if (!new java.io.File(path).exists()){
                        throw new java.io.FileNotFoundException(path);
                    }
                    else throw e;
                }


              //Parse dates
                java.text.SimpleDateFormat ftFormatter =
                new java.text.SimpleDateFormat("yyyyMMddHHmmssSSS");
                ftCreationTime = ftFormatter.parse(attributes[1]+"");
                ftLastAccessTime = ftFormatter.parse(attributes[2]+"");
                ftLastWriteTime = ftFormatter.parse(attributes[3]+"");


              //Compute file size using the nFileSizeHigh and nFileSizeLow attributes
              //Note that nFileSizeHigh will be zero unless the file size is greater
              //than MAXDWORD (4.2 Gig)
                long MAXDWORD = 4294967296L;
                long nFileSizeHigh = attributes[4];
                long nFileSizeLow = attributes[5];
                size = (nFileSizeHigh * MAXDWORD) + nFileSizeLow;


              //Parse misc file attributes
                long dwFileAttributes = attributes[0];
                if (bitand(dwFileAttributes, FILE_ATTRIBUTE_READONLY) == FILE_ATTRIBUTE_READONLY)
                flags.add("READONLY");

                if (bitand(dwFileAttributes, FILE_ATTRIBUTE_HIDDEN) == FILE_ATTRIBUTE_HIDDEN)
                flags.add("HIDDEN");

                if (bitand(dwFileAttributes, FILE_ATTRIBUTE_SYSTEM) == FILE_ATTRIBUTE_SYSTEM)
                flags.add("SYSTEM");

                if (bitand(dwFileAttributes, FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)
                flags.add("DIRECTORY");

                if (bitand(dwFileAttributes, FILE_ATTRIBUTE_ARCHIVE) == FILE_ATTRIBUTE_ARCHIVE)
                flags.add("ARCHIVE");

                if (bitand(dwFileAttributes, FILE_ATTRIBUTE_DEVICE) == FILE_ATTRIBUTE_DEVICE)
                flags.add("DEVICE");

                if (bitand(dwFileAttributes, FILE_ATTRIBUTE_NORMAL) == FILE_ATTRIBUTE_NORMAL)
                flags.add("NORMAL");

                if (bitand(dwFileAttributes, FILE_ATTRIBUTE_TEMPORARY) == FILE_ATTRIBUTE_TEMPORARY)
                flags.add("TEMPORARY");

                if (bitand(dwFileAttributes, FILE_ATTRIBUTE_SPARSE_FILE) == FILE_ATTRIBUTE_SPARSE_FILE)
                flags.add("SPARSE_FILE");

                if (bitand(dwFileAttributes, FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT)
                flags.add("REPARSE_POINT");

                if (bitand(dwFileAttributes, FILE_ATTRIBUTE_COMPRESSED) == FILE_ATTRIBUTE_COMPRESSED)
                flags.add("COMPRESSED");

                if (bitand(dwFileAttributes, FILE_ATTRIBUTE_OFFLINE) == FILE_ATTRIBUTE_OFFLINE)
                flags.add("OFFLINE");

                if (bitand(dwFileAttributes, FILE_ATTRIBUTE_NOT_CONTENT_INDEXED) == FILE_ATTRIBUTE_NOT_CONTENT_INDEXED)
                flags.add("NOT_CONTENT_INDEXED");

                if (bitand(dwFileAttributes, FILE_ATTRIBUTE_ENCRYPTED) == FILE_ATTRIBUTE_ENCRYPTED)
                flags.add("ENCRYPTED");

                if (bitand(dwFileAttributes, FILE_ATTRIBUTE_VIRTUAL) == FILE_ATTRIBUTE_VIRTUAL)
                flags.add("VIRTUAL");


              //Parse symlink
                if (flags.contains("REPARSE_POINT")){
                    link = new java.io.File(GetTarget(path));
                }

            }
            else{

              //Failed to load the javaxt-core.dll. Fall back to the java.io.File object.
                java.io.File f = new java.io.File(path);
                if (!f.exists()) throw new java.io.FileNotFoundException(path);
                size = f.length();
                ftLastWriteTime = new java.util.Date(f.lastModified());
                if (!f.canWrite()) flags.add("READONLY");
                if (f.isHidden()) flags.add("HIDDEN");
                if (f.isDirectory()) flags.add("DIRECTORY");

            }
        }
        else{//UNIX or LINIX Operating System


            java.io.File f = new java.io.File(path);
            if (!f.exists()) throw new java.io.FileNotFoundException(path);


          //Execute ls command to get last access time and creation time
            if (isOSX){
                String[] params = new String[]{"ls", "-lauT", path};
                javaxt.io.Shell cmd = new javaxt.io.Shell(params);
                cmd.run();
                java.util.Iterator<String> it = cmd.getOutput().iterator();
                ftLastAccessTime = parseOSXDate(it);

                params = new String[]{"ls", "-laUT", path};
                cmd = new javaxt.io.Shell(params);
                cmd.run();
                it = cmd.getOutput().iterator();
                ftCreationTime = parseOSXDate(it);
            }
            else if (isSolaris){
                String[] params = new String[]{"ls", "-lauE", path};
                javaxt.io.Shell cmd = new javaxt.io.Shell(params);
                cmd.run();
                java.util.Iterator<String> it = cmd.getOutput().iterator();
                ftLastAccessTime = parseFullDate(it);
            }
            else{//Linux (e.g. Ubuntu)
                String[] params = new String[]{"ls", "-lau", "--full-time", path};
                javaxt.io.Shell cmd = new javaxt.io.Shell(params);
                cmd.run();
                java.util.Iterator<String> it = cmd.getOutput().iterator();
                ftLastAccessTime = parseFullDate(it);

                params = new String[]{"ls", "-laU", "--full-time", path};
                cmd = new javaxt.io.Shell(params);
                cmd.run();
                it = cmd.getOutput().iterator();
                ftCreationTime = parseFullDate(it);
            }


          //Set other attributes including last modified date
            size = f.length();
            ftLastWriteTime = new java.util.Date(f.lastModified());
            if (!f.canWrite()) flags.add("READONLY");
            if (f.isHidden()) flags.add("HIDDEN");
            if (f.isDirectory()) flags.add("DIRECTORY");
            try{
                if (!f.getCanonicalFile().equals(f.getAbsoluteFile())){
                    flags.add("REPARSE_POINT");
                    link = f.getCanonicalFile();
                }
            }
            catch(Exception e){
            }
        }


      //Check whether the file is a Windows shortcut (.lnk file) and parse as needed
        if (!isDirectory() && link==null){
            if (path.toLowerCase().endsWith(".lnk")){
                try{
                    link = new LnkParser(path).getFile();
                }
                catch(Exception e){
                }
            }
        }

    }

    /** Used to extract a date from a ls output using the "--full-time" option. */
    private java.util.Date parseFullDate(java.util.Iterator<String> it){
        while (it.hasNext()){
            String line = it.next();
            if (line!=null){
                while (line.contains("  ")) line = line.replace("  ", " ");
                String[] arr = line.split(" ");
                if (arr.length>7){
                    try{
                        if (arr[6].length()>8) arr[6] = arr[6].substring(0,8);
                        String date = arr[5] + " " + arr[6] + " " + arr[7];
                        return new javaxt.utils.Date(date, "yyyy-MM-dd HH:mm:ss z").getDate();
                    }
                    catch(Exception e){
                    }
                }
            }
        }
        return null;
    }

    /** Used to extract a date from a ls output using the "T" option on OSX. */
    private java.util.Date parseOSXDate(java.util.Iterator<String> it){
        while (it.hasNext()){
            String line = it.next();
            if (line!=null){
                while (line.contains("  ")) line = line.replace("  ", " ");
                String[] arr = line.split(" ");
                if (arr.length>10){
                    try{
                        String date = arr[5] + " " + arr[6] + " " + arr[7]+ " " + arr[8];
                        return new javaxt.utils.Date(date, "MMM dd hh:mm:ss yyyy").getDate();
                    }
                    catch(Exception e){
                    }
                }
            }
        }
        return null;
    }

    public long getSize(){
        return size;
    }
    public java.util.Date getCreationTime(){
        return ftCreationTime;
    }
    public java.util.Date getLastAccessTime(){
        return ftLastAccessTime;
    }
    public java.util.Date getLastWriteTime(){
        return ftLastWriteTime;
    }
    public boolean isDirectory(){
        return flags.contains("DIRECTORY");
    }
    public boolean isHidden(){
        return flags.contains("HIDDEN");
    }
    public boolean isReadOnly(){
        return flags.contains("READONLY");
    }
    public java.util.HashSet<String> getFlags(){
        return flags;
    }
    public java.io.File getLink(){
        return link;
    }

  /** A file that is read-only. Applications can read the file, but cannot
   *  write to it or delete it. This attribute is not honored on directories.
   *  For more information, see You cannot view or change the Read-only or the
   *  System attributes of folders in Windows Server 2003, in Windows XP, in
   *  Windows Vista or in Windows 7.
   */
    private static final int FILE_ATTRIBUTE_READONLY  = 1;


  /** The file or directory is hidden. It is not included in an ordinary
   *  directory listing.
   */
    private static final int FILE_ATTRIBUTE_HIDDEN    = 2;

  /** A file or directory that the operating system uses a part of, or uses
   * exclusively.
   */
    private static final int FILE_ATTRIBUTE_SYSTEM    = 4;

  /** The handle that identifies a directory. */
    private static final int FILE_ATTRIBUTE_DIRECTORY = 16;

  /** A file or directory that is an archive file or directory. Applications
   *  typically use this attribute to mark files for backup or removal.
   */
    private static final int FILE_ATTRIBUTE_ARCHIVE   = 32;

  /** This value is reserved for system use. */
    private static final int FILE_ATTRIBUTE_DEVICE    = 64;

  /** A file that does not have other attributes set. This attribute is valid
   *  only when used alone.
   */
    private static final int FILE_ATTRIBUTE_NORMAL    = 128;

  /** A file that is being used for temporary storage. File systems avoid
   *  writing data back to mass storage if sufficient cache memory is available,
   *  because typically, an application deletes a temporary file after the
   *  handle is closed. In that scenario, the system can entirely avoid writing
   *  the data. Otherwise, the data is written after the handle is closed.
   */
    private static final int FILE_ATTRIBUTE_TEMPORARY = 256;

  /** A file that is a sparse file. */
    private static final int FILE_ATTRIBUTE_SPARSE_FILE  = 512;

  /** A file or directory that has an associated reparse point, or a file that
   *  is a symbolic link.
   */
    private static final int FILE_ATTRIBUTE_REPARSE_POINT = 1024;

  /** A file or directory that is compressed. For a file, all of the data in
   *  the file is compressed. For a directory, compression is the default for
   *  newly created files and subdirectories.
   */
    private static final int FILE_ATTRIBUTE_COMPRESSED = 2048;

  /** The data of a file is not available immediately. This attribute indicates
   *  that the file data is physically moved to offline storage. This attribute
   *  is used by Remote Storage, which is the hierarchical storage management
   *  software. Applications should not arbitrarily change this attribute.
   */
    private static final int FILE_ATTRIBUTE_OFFLINE  = 4096;

  /** The file or directory is not to be indexed by the content indexing
   *  service.
   */
    private static final int FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 8192;

   /** A file or directory that is encrypted. For a file, all data streams in
    *  the file are encrypted. For a directory, encryption is the default for
    *  newly created files and subdirectories.
    */
    private static final int FILE_ATTRIBUTE_ENCRYPTED = 16384;

    private static final int FILE_ATTRIBUTE_VIRTUAL = 65536;  //This value is reserved for system use.



    private long bitand(long Number1, long Number2){
        try {
            return Number1 & Number2;
        }
        catch (Exception e) {
            return -1;
        }
    }


}// End FileAttributes Class


//******************************************************************************
//**  LnkParser Class
//******************************************************************************
/**
 *   Class used to parse a windows lnk files (aka shortcuts). Credit:
 *   http://stackoverflow.com/a/672775/
 *
 ******************************************************************************/

private static class LnkParser {

    private boolean isDirectory;
    private boolean isLocal;
    private String real_file;

    public LnkParser(String lnk) {
        this(new java.io.File(lnk));
    }

  //**************************************************************************
  //** Constructor
  //**************************************************************************
  /** Instantiates the class by parsing the Windows shortcut (lnk) file.
   *  @param lnk File representing the full path to the shortcut file.
   */
    public LnkParser(java.io.File lnk) {
        byte[] link = getBytes(lnk, 1024).toByteArray();

        // get the flags byte
        byte flags = link[0x14];

        // get the file attributes byte
        final int file_atts_offset = 0x18;
        byte file_atts = link[file_atts_offset];
        byte is_dir_mask = (byte)0x10;
        if ((file_atts & is_dir_mask) > 0) {
            isDirectory = true;
        } else {
            isDirectory = false;
        }

        // if the shell settings are present, skip them
        final int shell_offset = 0x4c;
        final byte has_shell_mask = (byte)0x01;
        int shell_len = 0;
        if ((flags & has_shell_mask) > 0) {
            // the plus 2 accounts for the length marker itself
            shell_len = bytes2short(link, shell_offset) + 2;
        }

        // get to the file settings
        int file_start = 0x4c + shell_len;

        final int file_location_info_flag_offset_offset = 0x08;
        int file_location_info_flag = link[file_start + file_location_info_flag_offset_offset];
        isLocal = (file_location_info_flag & 2) == 0;
        // get the local volume and local system values
        //final int localVolumeTable_offset_offset = 0x0C;
        final int basename_offset_offset = 0x10;
        final int networkVolumeTable_offset_offset = 0x14;
        final int finalname_offset_offset = 0x18;
        int finalname_offset = link[file_start + finalname_offset_offset] + file_start;
        String finalname = getNullDelimitedString(link, finalname_offset);
        if (isLocal) {
            int basename_offset = link[file_start + basename_offset_offset] + file_start;
            String basename = getNullDelimitedString(link, basename_offset);
            real_file = basename + finalname;
        } else {
            int networkVolumeTable_offset = link[file_start + networkVolumeTable_offset_offset] + file_start;
            int shareName_offset_offset = 0x08;
            int shareName_offset = link[networkVolumeTable_offset + shareName_offset_offset]
                    + networkVolumeTable_offset;
            String shareName = getNullDelimitedString(link, shareName_offset);
            real_file = shareName + "\\" + finalname;
        }
    }

    private static String getNullDelimitedString(byte[] bytes, int off) {
        int len = 0;
        // count bytes until the null character (0)
        while (true) {
            if (bytes[off + len] == 0) {
                break;
            }
            len++;
        }
        return new String(bytes, off, len);
    }

    /*
     * convert two bytes into a short note, this is little endian because it's
     * for an Intel only OS.
     */
    private static int bytes2short(byte[] bytes, int off) {
        return ((bytes[off + 1] & 0xff) << 8) | (bytes[off] & 0xff);
    }

    public java.io.File getFile(){
        return new java.io.File(real_file);
    }

}//End LnkParser Inner Class
}//End File Class