helma/modules/helma/File.js
2012-03-27 11:46:35 +02:00

760 lines
22 KiB
JavaScript

/*
* 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;