/*
* aitools utilities
* Copyright (C) 2006 Noel Bush
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.aitools.util.resource;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipOutputStream;
import org.aitools.util.runtime.DeveloperError;
import org.aitools.util.runtime.UserError;
import org.aitools.util.xml.Characters;
import org.apache.log4j.Logger;
/**
* FileManager provides a standard interface for getting File objects and paths.
*/
public class Filesystem {
/** The root path for running Program D. */
private static URL root;
/** The logger. */
private static Logger LOGGER = Logger.getLogger("programd");
/** The current working directory. */
private static Stack<URL> workingDirectory = new Stack<URL>();
/** The string "{@value} ". */
public static final String FILE = "file";
/** Put the working directory onto the stack right away. */
static {
try {
// We do it directly, instead of using the push method, so we don't get complaints about a logger not being
// ready.
workingDirectory.add(URLTools.createValidURL(System.getProperty("user.dir")));
}
catch (FileNotFoundException e) {
throw new DeveloperError("Current working directory (according to system properties) does not exist!", e);
}
}
private static final String SLASH = "/";
/**
* Adds the given path to the given ZipOutputStream, omitting the given prefix from the ZipEntry created.
*
* @param out
* @param path
* @param omitPrefix
*/
public static void addToZip(ZipOutputStream out, URL path, String omitPrefix) {
addToZip(out, path, omitPrefix, null);
}
/**
* Adds the given path to the given ZipOutputStream, omitting the given prefix from the ZipEntry created.
*
* @param out
* @param path
* @param omitPrefix
* @param logger a logger to use in reporting errors like duplicate entries -- may be null
*/
public static void addToZip(ZipOutputStream out, URL path, String omitPrefix, Logger logger) {
byte[] buffer = new byte[1024];
try {
InputStream in = path.openStream();
out.putNextEntry(new ZipEntry(URLTools.unescape(path.getPath().replace(omitPrefix, ""))));
for (int length = 0; (length = in.read(buffer)) > 0;) {
out.write(buffer, 0, length);
}
out.closeEntry();
in.close();
}
catch (IOException e) {
if (logger != null && e instanceof ZipException) {
logger.warn(e);
return;
}
throw new RuntimeException("Error adding file to zip file.", e);
}
}
/**
* Checks whether a file given by a path exists, and if not, creates it, along with any necessary subdirectories.
*
* @param path denoting the file to create
* @param description describes what the file is for, for trace messages. Should fit into a sentence like,
* "created new <i>description </i>". May be null (which will result in less informative messages).
* @return the file that is created (or retrieved)
*/
public static File checkOrCreate(String path, String description) {
File file = getBestFile(path);
if (file.exists()) {
return file;
}
String _description = description;
if (_description == null) {
_description = "file";
}
try {
file.createNewFile();
}
catch (IOException e) {
// This may mean that some necessary directories don't exist.
File directory = file.getParentFile();
if (directory != null) {
if (directory.mkdirs()) {
try {
file.createNewFile();
}
catch (IOException ee) {
throw new UserError(String.format("Could not create %s \"%s\".", _description, path),
new CouldNotCreateFileException(file.getAbsolutePath()));
}
}
else {
throw new UserError(String.format("Could not create %s directory \"%s\".", _description,
directory.getAbsolutePath()), new CouldNotCreateFileException(directory.getAbsolutePath()));
}
}
else {
throw new UserError(String.format("Could not create %s directory for \"%s\".", _description,
file.getAbsolutePath()), new CouldNotCreateFileException(file.getAbsolutePath()));
}
}
assert file.exists();
LOGGER.info(String.format("Created new %s \"%s\".", _description, file.getAbsolutePath()));
return file;
}
/**
* Checks whether a directory given by a path exists, and if not, creates it, along with any necessary subdirectories.
*
* @param path denoting the directory to create
* @param description describes what the directory is for, for trace messages. Should fit into a sentence like,
* "created new <i>description </i>". May be null (which will result in less informative messages).
* @return the directory that is created (or retrieved)
*/
public static File checkOrCreateDirectory(String path, String description) {
File file = getBestFile(path);
if (file.exists()) {
if (!file.isDirectory()) {
throw new UserError(new FileAlreadyExistsAsFileException(file));
}
// otherwise
return file;
}
String _description = description;
if (_description == null) {
_description = "file";
}
if (!file.mkdirs()) {
throw new UserError(String.format("Could not create %s directory at \"%s\".", _description, path),
new CouldNotCreateFileException(file.getAbsolutePath()));
}
LOGGER.debug(String.format("Created new %s \"%s\".", _description, path));
return file;
}
/**
* Checks whether a file given by a path exists, and if not, creates it, along with any necessary subdirectories, then
* returns a PrintWriter configured with it.
*
* @param path denoting the file to create
* @param description describes what the file is for, for trace messages. Should fit into a sentence like,
* "created new <i>description </i>". May be null (which will result in less informative messages).
* @return the file that is created (or retrieved)
*/
public static PrintWriter checkOrCreatePrintWriter(String path, String description) {
PrintWriter out = null;
try {
out = new PrintWriter(checkOrCreate(path, description));
}
catch (FileNotFoundException e) {
throw new UserError(String.format("Could not find just-created %s \"%s\".", description, path), e);
}
return out;
}
/**
* Deletes the contents of the given directory (but not the directory itself).
*
* @param directory
*/
public static void deleteDirectoryContents(File directory) {
File[] contents = directory.listFiles();
for (File file : contents) {
if (file.isDirectory()) {
deleteDirectoryContents(file);
file.delete();
}
else {
file.delete();
}
}
}
/**
* Returns the absolute path. First checks whether the path as-is is absolute, then (otherwise) looks in the defined
* root directory.
*
* @param path the path for the file (may be absolute or relative to root directory)
* @return the absolute path
* @throws FileNotFoundException if a file with the given path cannot be located
*/
public static String getAbsolutePath(String path) throws FileNotFoundException {
File file = new File(path);
if (file.isAbsolute()) {
return file.getAbsolutePath();
}
file = new File(root.getPath() + path);
if (!file.exists()) {
throw new FileNotFoundException(String.format("Could not find \"%s\".", path));
}
return file.getAbsolutePath();
}
/**
* Gets a file from a given path. Tries to return it as a canonical file; failing that, returns the absolute file,
* (which may not be valid, but we do not check that here).
*
* @param path the path for the file
* @return the file (may not exist!)
*/
public static File getBestFile(String path) {
File file = new File(URLTools.unescape(path));
try {
return file.getCanonicalFile();
}
catch (IOException e) {
return file.getAbsoluteFile();
}
}
/**
* Sames as {@link #getExistingFile} except that it also checks that the given path is a directory.
*
* @param path the path for the directory (may be absolute or relative to root directory)
* @return the directory
*/
public static File getExistingDirectory(String path) {
File file = getBestFile(path);
if (!file.exists()) {
file = getBestFile(workingDirectory.peek().getPath() + path);
if (!file.exists()) {
throw new DeveloperError(String.format("Couldn't find \"%s\".", path), new FileNotFoundException(path));
}
}
try {
if (!file.isDirectory()) {
throw new DeveloperError(String.format("Could not find directory \"%s\".", path),
new FileAlreadyExistsAsFileException(file));
}
// otherwise...
return file.getCanonicalFile();
}
catch (IOException e) {
throw new DeveloperError(String.format("I/O Error creating the canonical form of file \"%s\".", path), e);
}
}
/**
* Gets a file from a given path. First tries to use the path as-is if it's absolute, then looks in the current
* working directory. The file <i>must</i> already exist, or an exception will be thrown.
*
* @param path the path for the file (may be absolute or relative to root directory)
* @return the file
* @throws FileNotFoundException
*/
public static File getExistingFile(String path) throws FileNotFoundException {
File file = getBestFile(path);
if (!file.exists()) {
file = getBestFile(workingDirectory.peek().getPath() + path);
if (!file.exists()) {
throw new FileNotFoundException(String.format("Couldn't find \"%s\".", path));
}
}
try {
return file.getCanonicalFile();
}
catch (IOException e) {
throw new DeveloperError(String.format("I/O Error creating the canonical form of file \"%s\".", path), e);
}
}
/**
* Returns the entire contents of a file as a String.
*
* @param path the path to the file (local file or URL)
* @return the entire contents of a file as a String
*/
public static String getFileContents(String path) {
BufferedReader buffReader = null;
InputStream stream = null;
// Guess if this is a URL.
if (path.indexOf("://") != -1) {
// Try to create this as a URL.
URL url = null;
try {
url = new URL(path);
}
catch (MalformedURLException e) {
LOGGER.warn(String.format("Malformed URL: \"%s\"", path));
}
if (url == null) {
throw new DeveloperError(String.format("Cannot create URL from path: \"%s\"", path));
}
try {
String encoding = Characters.getDeclaredXMLEncoding(url);
stream = url.openStream();
buffReader = new BufferedReader(new InputStreamReader(stream, encoding));
}
catch (IOException e) {
LOGGER.warn(String.format("I/O error trying to read \"%s\"", path));
}
}
// Handle paths which are apparently files.
else {
File toRead = null;
try {
toRead = getExistingFile(path);
}
catch (FileNotFoundException e) {
throw new UserError(new FileNotFoundException(path));
}
if (toRead.isAbsolute()) {
String parent = toRead.getParent();
try {
workingDirectory.push(URLTools.createValidURL(parent));
}
catch (FileNotFoundException e) {
throw new DeveloperError(String.format("Created an invalid parent file: \"%s\".", parent), e);
}
}
if (toRead.exists() && !toRead.isDirectory()) {
// The path may have been modified.
String _path = toRead.getAbsolutePath();
try {
String encoding = Characters.getDeclaredXMLEncoding(URLTools.createValidURL(_path));
stream = new FileInputStream(_path);
buffReader = new BufferedReader(new InputStreamReader(stream, encoding));
}
catch (IOException e) {
LOGGER.warn(String.format("I/O error trying to read \"%s\"", _path));
return null;
}
}
else {
assert toRead.exists() : "getExistingFile() returned a non-existent file";
if (toRead.isDirectory()) {
throw new UserError(new FileAlreadyExistsAsDirectoryException(toRead));
}
}
}
StringBuilder result = new StringBuilder();
String line;
if (buffReader != null && stream != null) {
try {
while ((line = buffReader.readLine()) != null) {
result.append(line);
}
buffReader.close();
stream.close();
}
catch (IOException e) {
LOGGER.warn(String.format("I/O error trying to read \"%s\"", path));
return null;
}
}
return result.toString();
}
/**
* Opens and returns a FileInputStream for a given path. If the specified file doesn't exist, an exception will be
* thrown.
*
* @param path
* @return a stream pointing to the given path
* @throws FileNotFoundException if the file does not exist
*/
public static FileInputStream getFileInputStream(String path) throws FileNotFoundException {
File file = getExistingFile(path);
return new FileInputStream(file);
}
/**
* Opens and returns a FileOutputStream for a given path. If the specified file doesn't exist, an exception will be
* thrown.
*
* @param path the path to which to return a stream
* @return a stream pointing to the given path
* @throws FileNotFoundException if the file does not exist
*/
public static FileOutputStream getFileOutputStream(String path) throws FileNotFoundException {
File file = getExistingFile(path);
return new FileOutputStream(file);
}
/**
* Gets a FileWriter from a given path. First tries to use the path as-is if it's absolute, then (otherwise) looks in
* the defined root directory.
*
* @param path the path for the file (may be absolute or relative to root directory)
* @param append if <tt>true</tt>, then bytes will be written to the end of the file rather than the beginning
* @return the FileWriter
* @throws IOException if the specified file is not found or if some other I/O error occurs
*/
public static FileWriter getFileWriter(String path, boolean append) throws IOException {
File file = getBestFile(path);
return new FileWriter(file, append);
}
/**
* Returns the root path.
*
* @return the root path
*/
public static URL getRootPath() {
return root;
}
/**
* Returns the working directory.
*
* @return the working directory
*/
public static URL getWorkingDirectory() {
return workingDirectory.peek();
}
/**
* Expands a localized file name that may contain wildcards to an array of file names without wildcards. All file
* separators in the file name must preceed any wildcard. The current directory is assumed to be the working
* directory.
*
* @param path
* @return array of file names without wildcards
* @throws FileNotFoundException if wild card is misused
*/
public static List<File> glob(String path) throws FileNotFoundException {
return glob(path, workingDirectory.peek().getPath());
}
/**
* <p>
* Expands a localized file name that may contain wildcards to an array of file names without wildcards. All file
* separators in the file name must preceed any wildcard.
* </p>
* <p>
* Adapted, with gratitude, from the <a href="http://sourceforge.net/projects/jmk/">JMK </a> project. (Under the GNU
* LGPL)
* </p>
*
* @param path the path string to glob
* @param workingDirectoryToUse the path to which relative paths should be considered relative
* @return array of file names without wildcards
* @see <a href="http://sourceforge.net/projects/jmk/">JMK</a>
* @throws FileNotFoundException if wild card is misused
*/
public static List<File> glob(String path, String workingDirectoryToUse) throws FileNotFoundException {
int wildCardIndex = path.indexOf('*');
if (wildCardIndex < 0) {
List<File> list = new ArrayList<File>(1);
list.add(new File(path));
return list;
}
// (otherwise...)
int separatorIndex = path.lastIndexOf(File.separatorChar);
// In case someone used a file separator char that doesn't belong to
// this system....
if (separatorIndex < 0) {
separatorIndex = path.lastIndexOf('\\');
if (separatorIndex < 0) {
separatorIndex = path.lastIndexOf('/');
if (separatorIndex < 0) {
// This is really going the extra mile....
separatorIndex = path.lastIndexOf(':');
}
}
}
if (separatorIndex > wildCardIndex) {
throw new FileNotFoundException(String.format("Cannot expand %s", path));
}
String pattern;
String dirName;
File dir = null;
if (separatorIndex >= 0) {
pattern = path.substring(separatorIndex + 1);
dirName = URLTools.unescape(path.substring(0, separatorIndex + 1));
if (!dirName.startsWith(File.separator) && !dirName.startsWith(SLASH)) {
dir = new File(workingDirectory.peek().getPath() + dirName);
}
else {
dir = new File(dirName);
}
}
else {
pattern = path;
dirName = URLTools.unescape(workingDirectoryToUse);
dir = new File(dirName);
try {
dir = dir.getCanonicalFile();
}
catch (IOException e) {
throw new DeveloperError(String.format("Could not get canonical file for \"%s\".", dir.getAbsolutePath()), e);
}
}
if (!dir.isDirectory()) {
throw new FileNotFoundException(String.format("\"%s\" is not a valid directory path!", dirName));
}
String[] files = dir.list(new WildCardFilter(pattern, '*'));
if (files == null) {
return new ArrayList<File>();
}
List<File> list = new ArrayList<File>(files.length);
for (int i = files.length; --i >= 0;) {
list.add(new File(dirName + files[i]));
}
return list;
}
/**
* Loads a file into a String.
*
* @param file the file
* @return the loaded template
*/
public static String loadFileAsString(File file) {
String templateLine;
StringBuilder result = new StringBuilder(1000);
BufferedReader reader;
try {
String encoding = Characters.getDeclaredXMLEncoding(file.toURI().toURL());
reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), encoding));
}
catch (IOException e) {
return null;
}
try {
while ((templateLine = reader.readLine()) != null) {
result.append(templateLine);
}
reader.close();
}
catch (IOException e) {
throw new UserError(String.format("I/O error reading \"%s\".", file.getAbsolutePath()), e);
}
return result.toString();
}
/**
* Loads a file into a String.
*
* @param path the path to the file
* @return the loaded template
* @throws FileNotFoundException
*/
public static String loadFileAsString(String path) throws FileNotFoundException {
return loadFileAsString(getExistingFile(path));
}
/**
* If a file exists at the given source, moves to the given destination.
*
* @param source
* @param destination
* @return whether the move succeeded
*/
public static boolean move(URL source, URL destination) {
File src = new File(source.getPath());
if (!src.exists() || !src.canWrite()) {
return false;
}
File dest = new File(destination.getPath());
if (dest.exists()) {
return false;
}
return src.renameTo(dest);
}
/**
* Pops a working directory off the stack.
*/
@SuppressWarnings("boxing")
public static void popWorkingDirectory() {
URL popped;
if (workingDirectory.size() > 1) {
popped = workingDirectory.pop();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String.format("Popped working directory \"%s\". Stack size now: %,d", popped,
workingDirectory.size()));
}
}
else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("No more working directories to pop.");
}
}
}
/**
* Pushes a new working directory onto the stack.
*
* @param path the directory path
*/
@SuppressWarnings("boxing")
public static void pushWorkingDirectory(URL path) {
workingDirectory.push(path);
if (LOGGER.isDebugEnabled()) {
LOGGER
.debug(String.format("Pushed working directory \"%s\". Stack size now: %,d", path, workingDirectory.size()));
}
}
/**
* Sets the root path.
*
* @param url the root path
*/
public static void setRootPath(URL url) {
root = url;
workingDirectory.push(url);
}
}