# Source: helma/File.js

    /*
     * Helma License Notice
     *
     * The contents of this file are subject to the Helma License
     * Version 2.0 (the "License"). You may not use this file except in
     * compliance with the License. A copy of the License is available at
     * http://adele.helma.org/download/helma/license.txt
     *
     * Copyright 1998-2007 Helma Software. All Rights Reserved.
     *
     * $RCSfile: File.js,v $
     * $Author$
     * $Revision$
     * $Date$
     */

    /**
     * @fileoverview Default properties and methods of the File prototype.
     * <br /><br />
     * To use this optional module, its repository needs to be added to the
     * application, for example by calling app.addRepository('modules/helma/File.js')
     */

    /**
     * Define the global namespace if not existing
     */
    if (!global.helma) {
        global.helma = {};
    }

    /**
     * Constructor for File objects, providing read and
     * write access to the file system.
     * @class This class represents a local file or directory
     * @param {String} path as String, can be either absolute or relative to the helma home directory
     * @constructor
     */
    helma.File = function(path) {
       var BufferedReader            = java.io.BufferedReader;
       var File                      = java.io.File;
       var Writer                    = java.io.Writer;
       var FileReader                = java.io.FileReader;
       var PrintWriter               = java.io.PrintWriter;
       var FileOutputStream          = java.io.FileOutputStream;
       var OutputStreamWriter        = java.io.OutputStreamWriter;
       var FileInputStream           = java.io.FileInputStream;
       var InputStreamReader         = java.io.InputStreamReader;
       var EOFException              = java.io.EOFException;
       var IllegalStateException     = java.lang.IllegalStateException;
       var IllegalArgumentException  = java.lang.IllegalArgumentException

       var self = this;

       var file;
       try {
          // immediately convert to absolute path - java.io.File is
          // incredibly stupid when dealing with relative file names
          if (arguments.length > 1)
             file = new File(path, arguments[1]).getAbsoluteFile();
          else
             file = new File(path).getAbsoluteFile();
       } catch (e) {
          throw(e);
       }

       var readerWriter;
       var atEOF = false;
       var lastLine = null;

       var setError = function(e) {
          self.lastError = e;
       };

       this.lastError = null;

       /** @ignore */
       this.toString = function() {
          return file.toString();
       };

       /**
        * Returns the name of the file or directory represented by this File object.
        * <br /><br />
        * 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.
        *
        * @returns String containing the name of the file or directory
        * @type String
        */
       this.getName = function() {
          var name = file.getName();
          return (name == null ? "" : name);
       };

       /**
        * Returns true if the file represented by this File object
        * is currently open.
        *
        * @returns Boolean
        * @type Boolean
        */
       this.isOpened = function() {
          return (readerWriter != null);
       };

       /**
        * Opens the file represented by this File object. If the file exists,
        * it is used for reading, otherwise it is opened for writing.
        * If the encoding argument is specified, it is used to read or write
        * the file. Otherwise, the platform's default encoding is used.
        *
        * @param {Object} options an optional argument holder object.
        *  The following options are supported:
        *  <ul><li>charset name of encoding to use for reading or writing</li>
        *  <li>append whether to append to the file if it exists</li></ul>
        * @returns Boolean true if the operation succeeded
        * @type Boolean
        */
       this.open = function(options) {
          if (self.isOpened()) {
             setError(new IllegalStateException("File already open"));
             return false;
          }
          // We assume that the BufferedReader and PrintWriter creation
          // cannot fail except if the FileReader/FileWriter fails.
          // Otherwise we have an open file until the reader/writer
          // get garbage collected.
          var charset = options && options.charset;
          var append = options && options.append;
          try {
              if (file.exists() && !append) {
                 if (charset) {
                     readerWriter = new BufferedReader(
                             new InputStreamReader(new FileInputStream(file), charset));
                 } else {
                     readerWriter = new BufferedReader(new FileReader(file));
                 }
              } else {
                 if (append && charset)  {
                     readerWriter = new PrintWriter(
                             new OutputStreamWriter(new FileOutputStream(file, true), charset));
                 } else if (append) {
                     readerWriter = new PrintWriter(
                             new OutputStreamWriter(new FileOutputStream(file, true)));
                 } else if (charset) {
                     readerWriter = new PrintWriter(file, charset);
                 } else {
                     readerWriter = new PrintWriter(file);
                 }
              }
             return true;
          } catch (e) {
             setError(e);
             return false;
          }
          return;
       };

       /**
        * Tests whether the file or directory represented by this File object exists.
        *
        * @returns Boolean true if the file or directory exists; false otherwise
        * @type Boolean
        */
       this.exists = function() {
          return file.exists();
       };

       /**
        * Returns the pathname string of this File object's parent directory.
        *
        * @returns String containing the pathname of the parent directory
        * @type String
        */
       this.getParent = function() {
          if (!file.getParent())
             return null;
          return new helma.File(file.getParent());
       };

       /**
        * This methods reads characters until an end of line/file is encountered
        * then returns the string for these characters (without any end of line
        * character).
        *
        * @returns String of the next unread line in the file
        * @type String
        */
       this.readln = function() {
          if (!self.isOpened()) {
             setError(new IllegalStateException("File not opened"));
             return null;
          }
          if (!(readerWriter instanceof BufferedReader)) {
             setError(new IllegalStateException("File not opened for reading"));
             return null;
          }
          if (atEOF) {
             setError(new EOFException());
             return null;
          }
          if (lastLine != null) {
             var line = lastLine;
             lastLine = null;
             return line;
          }
          var reader = readerWriter;
          // Here lastLine is null, return a new line
          try {
             var line = readerWriter.readLine();
             if (line == null) {
                atEOF = true;
                setError(new EOFException());
             }
             return line;
          } catch (e) {
             setError(e);
             return null;
          }
          return;
       };

       /**
        * Appends a string to the file represented by this File object.
        *
        * @param {String} what as String, to be written to the file
        * @returns Boolean
        * @type Boolean
        * @see #writeln
        */
       this.write = function(what) {
          if (!self.isOpened()) {
             setError(new IllegalStateException("File not opened"));
             return false;
          }
          if (!(readerWriter instanceof PrintWriter)) {
             setError(new IllegalStateException("File not opened for writing"));
             return false;
          }
          if (what != null) {
             readerWriter.print(what.toString());
          }
          return true;
       };

       /**
        * Appends a string with a platform specific end of
        * line to the file represented by this File object.
        *
        * @param {String} what as String, to be written to the file
        * @returns Boolean
        * @type Boolean
        * @see #write
        */
       this.writeln = function(what) {
          if (self.write(what)) {
             readerWriter.println();
             return true;
          }
          return false;
       };

       /**
        * Tests whether this File object's pathname is absolute.
        * <br /><br />
        * The definition of absolute pathname is system dependent.
        * On UNIX systems, a pathname is absolute if its prefix is "/".
        * On Microsoft Windows systems, a pathname is absolute if its prefix
        * is a drive specifier followed by "\\", or if its prefix is "\\".
        *
        * @returns Boolean if this abstract pathname is absolute, false otherwise
        * @type Boolean
        */
       this.isAbsolute = function() {
          return file.isAbsolute();
       };

       /**
        * Deletes the file or directory represented by this File object.
        *
        * @returns Boolean
        * @type Boolean
        */
       this.remove = function() {
          if (self.isOpened()) {
             setError(new IllegalStateException("An openened file cannot be removed"));
             return false;
          }
          return file["delete"]();
       };

       /**
        * List of all files within the directory represented by this File object.
        * <br /><br />
        * You may pass a RegExp Pattern to return just files matching this pattern.
        * <br /><br />
        * Example: var xmlFiles = dir.list(/.*\.xml/);
        *
        * @param {RegExp} pattern as RegExp, optional pattern to test each file name against
        * @returns Array the list of file names
        * @type Array
        */
       this.list = function(pattern) {
          if (self.isOpened())
             return null;
          if (!file.isDirectory())
             return null;
          if (pattern) {
             var fileList = file.list();
             var result = [];
             for (var i in fileList) {
                if (pattern.test(fileList[i]))
                   result.push(fileList[i]);
             }
             return result;
          }
          return file.list();
       };

       /**
        * Purges the content of the file represented by this File object.
        *
        * @returns Boolean
        * @type Boolean
        */
       this.flush = function() {
          if (!self.isOpened()) {
             setError(new IllegalStateException("File not opened"));
             return false;
          }
          if (readerWriter instanceof Writer) {
             try {
                readerWriter.flush();
             } catch (e) {
               setError(e);
               return false;
             }
          } else {
             setError(new IllegalStateException("File not opened for write"));
             return false; // not supported by reader
          }
          return true;
       };

       /**
        * Closes the file represented by this File object.
        *
        * @returns Boolean
        * @type Boolean
        */
       this.close = function() {
          if (!self.isOpened())
             return false;
          try {
             atEOF = false;
             lastLine = null;
             readerWriter.close();
             readerWriter = null;
             return true;
          } catch (e) {
             setError(e);
             readerWriter = null;
             return false;
          }
       };

       /**
        * Returns the pathname string of this File object.
        * <br /><br />
        * The resulting string uses the default name-separator character
        * to separate the names in the name sequence.
        *
        * @returns String of this file's pathname
        * @type String
        */
       this.getPath = function() {
          var path = file.getPath();
          return (path == null ? "" : path);
       };

       /**
        * Contains the last error that occured, if any.
        * @returns String
        * @type String
        * @see #clearError
        */
       this.error = function() {
          if (this.lastError == null) {
             return "";
          } else {
             var exceptionName = this.lastError.getClass().getName();
             var l = exceptionName.lastIndexOf(".");
             if (l > 0)
                exceptionName = exceptionName.substring(l + 1);
             return exceptionName + ": " + this.lastError.getMessage();
          }
       };

       /**
        * Clears any error message that may otherwise be returned by the error method.
        *
        * @see #error
        */
       this.clearError = function() {
          this.lastError = null;
          return;
       };

       /**
        * Tests whether the application can read the file
        * represented by this File object.
        *
        * @returns Boolean true if the file exists and can be read; false otherwise
        * @type Boolean
        */
       this.canRead = function() {
          return file.canRead();
       };

       /**
        * Tests whether the file represented by this File object is writable.
        *
        * @returns Boolean true if the file exists and can be modified; false otherwise.
        * @type Boolean
        */
       this.canWrite = function() {
          return file.canWrite();
       };

       /**
        * Returns the absolute pathname string of this file.
        * <br /><br />
        * If this File object's pathname is already absolute, then the pathname
        * string is simply returned as if by the getPath() 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 user.dir, 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 Microsoft Windows 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.
        *
        * @returns String The absolute pathname string
        * @type String
        */
       this.getAbsolutePath = function() {
          var absolutPath = file.getAbsolutePath();
          return (absolutPath == null ? "" : absolutPath);
       };

       /**
        * Returns the length of the file represented by this File object.
        * <br /><br />
        * The return value is unspecified if this pathname denotes a directory.
        *
        * @returns Number The length, in bytes, of the file, or 0L if the file does not exist
        * @type Number
        */
       this.getLength = function() {
          return file.length();
       };

       /**
        * Tests whether the file represented by this File object is a directory.
        *
        * @returns Boolean true if this File object is a directory and exists; false otherwise
        * @type Boolean
        */
       this.isDirectory = function() {
          return file.isDirectory();
       };

       /**
        * Tests whether the file represented by this File object is a normal file.
        * <br /><br />
        * A file is normal 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.
        *
        * @returns Boolean true if this File object is a normal file and exists; false otherwise
        * @type Boolean
        */
       this.isFile = function() {
          return file.isFile();
       };

       /**
        * Returns the time when the file represented by this File object was last modified.
        * <br /><br />
        * A number representing the time the file was last modified,
        * measured in milliseconds since the epoch (00:00:00 GMT, January 1, 1970),
        * or 0L if the file does not exist or if an I/O error occurs.
        *
        * @returns Number in milliseconds since 00:00:00 GMT, January 1, 1970
        * @type Number
        */
       this.lastModified = function() {
          return file.lastModified();
       };

       /**
        * Creates the directory represented by this File object.
        *
        * @returns Boolean true if the directory was created; false otherwise
        * @type Boolean
        */
       this.makeDirectory = function() {
          if (self.isOpened())
             return false;
          // don't do anything if file exists or use multi directory version
          return (file.exists() || file.mkdirs());
       };

       /**
        * Renames the file represented by this File object.
        * <br /><br />
        * Whether or not this method can move a file from one
        * filesystem to another is platform-dependent. The return
        * value should always be checked to make sure that the
        * rename operation was successful.
        *
        * @param {FileObject} toFile as FileObject of the new path
        * @returns true if the renaming succeeded; false otherwise
        * @type Boolean
        */
       this.renameTo = function(toFile) {
          if (toFile == null) {
             setError(new IllegalArgumentException("Uninitialized target File object"));
             return false;
          }
          if (self.isOpened()) {
             setError(new IllegalStateException("An openened file cannot be renamed"));
             return false;
          }
          if (toFile.isOpened()) {
             setError(new IllegalStateException("You cannot rename to an openened file"));
             return false;
          }
          return file.renameTo(new java.io.File(toFile.getAbsolutePath()));
       };

       /**
        * Returns true if the file represented by this File object
        * has been read entirely and the end of file has been reached.
        *
        * @returns Boolean
        * @type Boolean
        */
       this.eof = function() {
          if (!self.isOpened()) {
             setError(new IllegalStateException("File not opened"));
             return true;
          }
          if (!(readerWriter instanceof BufferedReader)) {
             setError(new IllegalStateException("File not opened for read"));
             return true;
          }
          if (atEOF)
             return true;
          if (lastLine != null)
             return false;
          try {
             lastLine = readerWriter.readLine();
             if (lastLine == null)
                atEOF = true;
             return atEOF;
          } catch (e) {
             setError(e);
             return true;
          }
       };

       /**
        * This methods reads all the lines contained in the
        * file and returns them.
        *
        * @return String of all the lines in the file
        * @type String
        */
       this.readAll = function() {
          // Open the file for readAll
          if (self.isOpened()) {
             setError(new IllegalStateException("File already open"));
             return null;
          }
          try {
             if (file.exists()) {
                readerWriter = new BufferedReader(new FileReader(file));
             } else {
                setError(new IllegalStateException("File does not exist"));
                return null;
             }
             if (!file.isFile()) {
                setError(new IllegalStateException("File is not a regular file"));
                return null;
             }

             // read content line by line to setup proper eol
             var buffer = new java.lang.StringBuffer(file.length() * 1.10);
             while (true) {
                var line = readerWriter.readLine();
                if (line == null)
                   break;
                if (buffer.length() > 0)
                   buffer.append("\n");  // EcmaScript EOL
                buffer.append(line);
             }

             // Close the file
             readerWriter.close();
             readerWriter = null;
             return buffer.toString();
          } catch (e) {
             readerWriter = null;
             setError(e);
             return null;
          }
       };

       /**
        * This method removes a directory recursively .
        * <br /><br />
        * DANGER! DANGER! HIGH VOLTAGE!
        * The directory is deleted recursively without
        * any warning or precautious measures.
        */
       this.removeDirectory = function() {
          if (!file.isDirectory())
             return false;
          var arr = file.list();
          for (var i=0; i<arr.length; i++) {
             var f = new helma.File(file, arr[i]);
             if (f.isDirectory())
                f.removeDirectory();
             else
                f.remove();
          }
          file["delete"]();
          return true;
       };

       /**
        * Recursivly lists all files below a given directory
        * you may pass a RegExp Pattern to return just
        * files matching this pattern.
        *
        * @param {RegExp} pattern as RegExp, to test each file name against
        * @returns Array the list of absolute file paths
        */
       this.listRecursive = function(pattern) {
          if (!file.isDirectory())
             return false;
          if (!pattern || pattern.test(file.getName()))
             var result = [file.getAbsolutePath()];
          else
             var result = [];
          var arr = file.list();
          for (var i=0; i<arr.length; i++) {
             var f = new helma.File(file, arr[i]);
             if (f.isDirectory())
                result = result.concat(f.listRecursive(pattern));
             else if (!pattern || pattern.test(arr[i]))
                result.push(f.getAbsolutePath());
          }
          return result;
       }

       /**
        * Makes a copy of a file over partitions.
        *
        * @param {String|helma.File} dest as a File object or the String of full path of the new file
        */
       this.hardCopy = function(dest) {
          var inStream = new java.io.BufferedInputStream(
             new java.io.FileInputStream(file)
          );
          var outStream = new java.io.BufferedOutputStream(
             new java.io.FileOutputStream(dest)
          );
          var buffer = java.lang.reflect.Array.newInstance(
             java.lang.Byte.TYPE, 4096
          );
          var bytesRead = 0;
          while ((bytesRead = inStream.read(buffer, 0, buffer.length)) != -1) {
             outStream.write(buffer, 0, bytesRead);
          }
          outStream.flush();
          inStream.close();
          outStream.close();
          return true;
       }

       /**
        * Moves a file to a new destination directory.
        *
        * @param {String} dest as String, the full path of the new file
        * @returns Boolean true in case file could be moved, false otherwise
        */
       this.move = function(dest) {
          // instead of using the standard File method renameTo()
          // do a hardCopy and then remove the source file. This way
          // file locking shouldn't be an issue
          self.hardCopy(dest);
          // remove the source file
          file["delete"]();
          return true;
       }

       /**
        * Returns file as ByteArray.
        * <br /><br />
        * Useful for passing it to a function instead of an request object.
        */
       this.toByteArray = function() {
          if (!this.exists())
             return null;
          var body = new java.io.ByteArrayOutputStream();
          var stream = new java.io.BufferedInputStream(
             new java.io.FileInputStream(this.getAbsolutePath())
          );
          var buf = java.lang.reflect.Array.newInstance(
             java.lang.Byte.TYPE, 1024
          );
          var read;
          while ((read = stream.read(buf)) > -1)
             body.write(buf, 0, read);
          stream.close();
          return body.toByteArray();
       };

       for (var i in this)
          this.dontEnum(i);

       return this;
    }

    /** @ignore */
    helma.File.toString = function() {
       return "[helma.File]";
    };

    helma.File.separator = java.io.File.separator;

    helma.lib = "File";
    helma.dontEnum(helma.lib);
    for (var i in helma[helma.lib])
       helma[helma.lib].dontEnum(i);
    for (var i in helma[helma.lib].prototype)
       helma[helma.lib].prototype.dontEnum(i);
    delete helma.lib;