/* XXL: The eXtensible and fleXible Library for data processing Copyright (C) 2000-2011 Prof. Dr. Bernhard Seeger Head of the Database Research Group Department of Mathematics and Computer Science University of Marburg Germany 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 3 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, see <http://www.gnu.org/licenses/>. http://code.google.com/p/xxl/ */ package xxl.core.io.fat; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import xxl.core.io.fat.util.ExtendedFileFilter; import xxl.core.io.fat.util.ExtendedFilenameFilter; import xxl.core.io.fat.util.StringOperations; /** * An abstract representation of file and directory pathnames. * * This class presents an abstract, this raw-system-dependent view of hierarchical * pathnames. An abstract pathname has two components: * An optional prefix string, "\\", and * a sequence of zero or more string names. * * Each name in an abstract pathname except for the last denotes a directory; * the last name may denote either a directory or a file. The empty * abstract pathname has no prefix and an empty name sequence. * * A pathname, whether abstract or in string form, is always absolute. * An absolute pathname is complete in that no other information is * required in order to locate the file that it denotes. */ public class ExtendedFile implements Comparable { /** * The FileSystem object representing the platform's local file system. */ protected FATDevice device; /** * The complete filename without the device name. */ protected String completeName; /** * The mode of the file it is either "r" which means read access only or * it is "rw" which means read and write access. */ protected String mode = FATDevice.FILE_MODE_READ; /** * The system-dependent default name-separator character. This field is * initialized to contain the first character of the value of the system * property file.separator. On UNIX systems the value of this * field is '/'; on Win32 systems it is '\'. */ public static final char separatorChar = System.getProperty("file.separator").charAt(0); /** * Creates a new ExtendedFile instance. The given pathname is used to * navigate through the directory structure. If the given string is * the empty string, then the result is the empty pathname. * @param device instance of FATDevice. * @param pathname a pathname string. * @throws NullPointerException if the pathname argument is null. */ protected ExtendedFile(FATDevice device, String pathname)// throws Exception { if (pathname == null) throw new NullPointerException(); this.device = device; completeName = StringOperations.removeDeviceName(pathname); if (completeName.startsWith(System.getProperty("file.separator"))) completeName = completeName.substring(1); } //end constructor /** * Creates a new ExtendedFile instance from a parent pathname string * and a child pathname string. * * If parent is null then the new ExtendedFile instance is created * as if by invoking the single-argument ExtendedFile constructor on * the given child pathname string. * * Otherwise the parent pathname string is taken to denote * a directory, and the child pathname string is taken to * denote either a directory or a file. If parent is the empty string then * it is handled as if it is the root directory. Otherwise the two pathname * strings are merged to get one pathname. * @param device instance of FATDevice. * @param parent the parent pathname string. * @param child the child pathname string. * @throws NullPointerException if child is null. */ protected ExtendedFile(FATDevice device, String parent, String child) { if (child == null) throw new NullPointerException(); this.device = device; if (parent != null) { if (parent.equals("")) { completeName = StringOperations.extractFileName(child); if (completeName.startsWith(System.getProperty("file.separator"))) completeName = completeName.substring(1); } else { completeName = StringOperations.removeDeviceName(parent); if (completeName.startsWith(System.getProperty("file.separator"))) completeName = completeName.substring(1); String fileName = StringOperations.extractFileName(child); if (!fileName.startsWith(System.getProperty("file.separator")) && !completeName.endsWith(System.getProperty("file.separator"))) fileName = System.getProperty("file.separator") + fileName; completeName += fileName; } } else { completeName = StringOperations.removeDeviceName(child); if (completeName.startsWith(System.getProperty("file.separator"))) completeName = completeName.substring(1); } } /** * Returns the name of the file or directory denoted by this pathname. * This is just the last name in the pathname's name * sequence. If the pathname's name sequence is empty, then the empty * string is returned. * @return the name of the file or directory denoted by this pathname, * or the empty string if this pathname's name sequence is empty. */ public String getName() { return StringOperations.extractFileName(completeName); } //end getName() /** * Returns the pathname string of this abstract pathname's parent, or * null if this pathname does not name a parent directory. * The parent of an pathname consists of the * pathname's prefix, if any, and each name in the pathname's name * sequence except for the last. At last the root directory is the * parent if the pathname string is empty or is a filename only. * @return the pathname string of the parent directory named by this * pathname, or null if this pathname does not name a parent. */ public String getParent() { String fileName = StringOperations.extractFileName(completeName); if (fileName.equals("")) return null; String path = StringOperations.extractPath(completeName); if (!path.startsWith(System.getProperty("file.separator"))) path = System.getProperty("file.separator") + path; if (FileSystem.isUnixDeviceName(device.getDeviceName())) return device.getDeviceName() + path; else return device.getDeviceName() + ":" + path; } //end getParent() /** * Returns the pathname of this pathname's parent, or null if this * pathname does not name a parent directory. * The parent of an pathname consists of the pathname's prefix, if * any, and each name in the pathname's name sequence except for * the last. If the name sequence is empty then the pathname does * not name a parent directory. * @return the pathname of the parent directory named by this * pathname, or null if this pathname does not name a parent. */ public ExtendedFile getParentFile() { String p = this.getParent(); if (p == null) return null; return new ExtendedFile(device, p); } //end getParentFile() /** * Converts this pathname into a pathname string. * @return the string representation of this pathname. */ public String getPath() { if (FileSystem.isUnixDeviceName(device.getDeviceName())) return device.getDeviceName() + System.getProperty("file.separator") + completeName; else return device.getDeviceName() + ":" + System.getProperty("file.separator") + completeName; } //end getPath() /** * Returns the absolute pathname string of this pathname inclusive * the name of the device. * If this pathname is the empty pathname then only the name of the device * is returned. * @return the absolute pathname string inclusive the name of the device. */ public String getAbsolutePath() { if (FileSystem.isUnixDeviceName(device.getDeviceName())) return device.getDeviceName() + System.getProperty("file.separator") + completeName; else return device.getDeviceName() + ":" + System.getProperty("file.separator") + completeName; } //end getAbsolutePath() /** * Returns the absolute form of this pathname. * @return the absolute pathname denoting the same file or * directory as this pathname. */ public ExtendedFile getAbsoluteFile() { return new ExtendedFile(device, getAbsolutePath()); } //end getAbsoluteFile() /** * Converts this abstract pathname into a file: URL. The * exact form of the URL is system-dependent. If it can be determined that * the file denoted by this abstract pathname is a directory, then the * resulting URL will end with a slash. * @return a URL object representing the equivalent file URL. * @throws MalformedURLException if the path cannot be parsed as a URL. * @see java.net.URL */ public URL toURL() throws MalformedURLException { String path = getAbsolutePath(); if (ExtendedFile.separatorChar != '/') path = path.replace(ExtendedFile.separatorChar, '/'); if (!path.startsWith("/")) path = "/" + path; if (!path.endsWith("/") && isDirectory()) path = path + "/"; return new URL("file", "", path); } //end toURL() /** * Tests whether the file denoted by this pathname exists. * @return true if and only if the file denoted by this * pathname exists; false otherwise. */ public boolean exists() { return device.fileExists(completeName); } //end exists() /** * Tests whether the file denoted by this pathname is a directory. * @return true if and only if the file denoted by this * pathname exists and is a directory; false otherwise. */ public boolean isDirectory() { return device.isDirectory(completeName); } //end isDirectory() /** * Tests whether the file denoted by this pathname is a normal file. * A file is normal if it is not a directory and, in addition, * satisfies other criteria. Any non-directory file created by * this class is guaranteed to be a normal file. * @return true if and only if the file denoted by this pathname * exists and is a normal file; false otherwise. */ public boolean isFile() { return device.isFile(completeName); } //end isFile() /** * Tests whether the file named by this pathname is a hidden file. * @return true if and only if the file denoted by this pathname * is hidden. */ public boolean isHidden() { return device.isHidden(completeName); } //end isHidden() /** * Returns the time that the file denoted by this pathname was * last modified. * @return a long value 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. */ public long lastModified() { return device.getWriteTime(completeName); } //end lastModified() /** * Returns the time that the file denoted by this pathname was created. * @return a long value representing the time the file was created, * 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. */ public long created() { return device.getCreationTime(completeName); } //end created() /** * Returns the length of the file denoted by this pathname. * @return the length, in bytes, of the file denoted by this * pathname, or 0L if the file does not exist. */ public long length() { try { return device.length(completeName); } catch (Exception e) { return 0L; } } //end length() /** * Creates a new, empty file named by this pathname if and only if a * file with this name does not yet exist and there is enough free space. * @return true if the named file does not exist and was successfully * created; false otherwise. */ public boolean createNewFile() { try { device.createFile(completeName); } catch (xxl.core.io.fat.errors.DirectoryException e) { return false; } return true; } //end createNewFile() /** * Deletes the file or directory denoted by this pathname. If * this pathname denotes a directory, then the directory must be empty in * order to be deleted. * @return true if and only if the file or directory is * successfully deleted; false otherwise. */ public boolean delete() { return device.delete(completeName); } //end delete() /** * Returns an array of strings naming the files and directories in the * directory denoted by this pathname. * If this pathname does not denote a directory, then this method returns * null. Otherwise an array of strings is returned, one for each file or * directory in the directory. Names denoting the directory itself and * the directory's parent directory are not included in the result. * Each string is a file name rather than a complete path. * There is no guarantee that the name strings in the resulting array * will appear in any specific order; they are not, in particular, * guaranteed to appear in alphabetical order. * @return an array of strings naming the files and directories in the * directory denoted by this pathname. The array will be empty if the * directory is empty. Returns null if this pathname does not denote a * directory, or if an I/O error occurs. */ public String[] list() { return device.list(completeName); } //end list() /** * Returns an array of strings naming the files and directories in the * directory denoted by this pathname that satisfy the specified * filter. The behavior of this method is the same as that of the * {@link #list()} method, except that the strings in the * returned array must satisfy the filter. If the given * filter is null then all names are accepted. * Otherwise, a name satisfies the filter if and only if the value * true results when the {@link ExtendedFilenameFilter} method * of the filter is invoked on this pathname and the name of a file * or directory in the directory that it denotes. * @param filter a filename filter. * @return an array of strings naming the files and directories, in the * directory denoted by this pathname, that were accepted by the given * filter. The array will be empty if the directory is empty or if no * names were accepted by the filter. Returns null if this pathname * does not denote a directory, or if an I/O error occurs. */ public String[] list(ExtendedFilenameFilter filter) { String names[] = list(); if ((names == null) || (filter == null)) return names; ArrayList v = new ArrayList(); for (int i = 0 ; i < names.length ; i++) if (filter.accept(this, names[i])) v.add(names[i]); return (String[])(v.toArray(new String[0])); } //end list(ExtendedFilenameFilter filter) /** * Returns an array of pathnames denoting the files in the directory * denoted by this pathname. * If this pathname does not denote a directory, then this method * returns null. Otherwise an array of ExtendedFile objects is * returned, one for each file or directory in the directory. * Pathnames denoting the directory itself and the directory's parent * directory are not included in the result. * There is no guarantee that the name strings in the resulting array * will appear in any specific order; they are not, in particular, * guaranteed to appear in alphabetical order. * @return an array of pathnames denoting the files and directories * in the directory denoted by this pathname. The array will be empty * if the directory is empty. Returns null if this pathname does not * denote a directory, or if an I/O error occurs. */ public ExtendedFile[] listFiles() { String[] ss = list(); if (ss == null) return null; int n = ss.length; ExtendedFile[] fs = new ExtendedFile[n]; for (int i = 0; i < n; i++) { fs[i] = new ExtendedFile(device, getPath(), ss[i]); } return fs; } //end listFiles() /** * Returns an array of pathnames denoting the files and * directories in the directory denoted by this pathname that * satisfy the specified filter. The behavior of this method is the * same as that of the {@link #listFiles()} method, except * that the pathnames in the returned array must satisfy the filter. * If the given filter is null then all * pathnames are accepted. Otherwise, a pathname satisfies the filter * if and only if the value true results when the * {@link ExtendedFilenameFilter} method of the filter is * invoked on this abstract pathname and the name of a file or * directory in the directory that it denotes. * @param filter a filename filter. * @return an array of pathnames denoting the files and directories * in the directory denoted by this pathname. The array will be empty * if the directory is empty. Returns null if this pathname does not * denote a directory, or if an I/O error occurs. */ public ExtendedFile[] listFiles(ExtendedFilenameFilter filter) { String ss[] = list(); if (ss == null) return null; ArrayList v = new ArrayList(); for (int i = 0 ; i < ss.length ; i++) { if ((filter == null) || filter.accept(this, ss[i])) { v.add(new ExtendedFile(device, StringOperations.extractPath(completeName), ss[i])); } } return (ExtendedFile[])(v.toArray(new ExtendedFile[0])); } //end listFiles(ExtendedFilenameFilter filter) /** * Returns an array of pathnames denoting the files and * directories, in the directory denoted by this pathname, that * satisfy the specified filter. The behavior of this method is the * same as that of the {@link #listFiles()} method, except * that the pathnames in the returned array must satisfy the filter. * If the given filter is null then all pathnames are accepted. * Otherwise, a pathname satisfies the filter if and only if the * value true results when the {@link ExtendedFilenameFilter} * method of the filter is invoked on the pathname. * @param filter a filename filter. * @return an array of pathnames denoting the files and directories * in the directory denoted by this pathname and satisfy the * specified filter. The array will be empty if * the directory is empty. Returns null if this pathname * does not denote a directory, or if an I/O error occurs. */ public ExtendedFile[] listFiles(ExtendedFileFilter filter) { String ss[] = list(); if (ss == null) return null; ArrayList v = new ArrayList(); for (int i = 0 ; i < ss.length ; i++) { ExtendedFile f = new ExtendedFile(device, StringOperations.extractPath(completeName), ss[i]); if ((filter == null) || filter.accept(f)) { v.add(f); } } return (ExtendedFile[])(v.toArray(new ExtendedFile[0])); } //end listFiles(ExtendedFileFilter filter) /** * Creates the directory named by this pathname. * @return true if and only if the directory was * created; false otherwise. */ public boolean mkdir() { return device.makeDirectory(completeName); } //end mkdir() /** * Creates the directory named by this pathname, including any * necessary but nonexistent parent directories. Note that if this * operation fails it may have succeeded in creating some of the necessary * parent directories. * @return true if and only if the directory was created, along with all * necessary parent directories; false otherwise. */ public boolean mkdirs() { if (exists()) return false; if (mkdir()) return true; String parent = getParent(); return (parent != null) && (new ExtendedFile(device, parent).mkdirs() && mkdir()); } //end mkdirs() /** * Renames the file denoted by this pathname. If the entry named by this pathname * is an existing file, it will be deleted and a new file/directory given by dest * will be created (with all subdirectories if they don't exist). Since ExtendedFile * objects always belong to one device the device will and can not be changed. In case * the dest file/directory already exists, the old file will not be deleted and the * renaming operation will not processed. In case the * entry named by this pathname is a directory the directory is only deleted if it is * empty. * @param dest the new pathname for the named file. * @return true if and only if the renaming succeeded; false * otherwise. * @throws NullPointerException if parameter dest is null. */ public boolean renameTo(ExtendedFile dest) { if (dest == null) throw new NullPointerException(); boolean result = device.renameTo( StringOperations.removeDeviceName(getAbsolutePath()), StringOperations.removeDeviceName(dest.getAbsolutePath())); if (result) { completeName = StringOperations.removeDeviceName(dest.getAbsolutePath()); if (completeName.startsWith(System.getProperty("file.separator"))) completeName = completeName.substring(1); } return result; } //end renameTo(ExtendedFile dest) /** * Sets the last-modified time of the file or directory named by this * pathname. The modified time will be written to disk immediately. * @param time the new last-modified time, measured in milliseconds since * the epoch (00:00:00 GMT, January 1, 1970). * @return true if and only if the operation succeeded; false otherwise. * @throws IllegalArgumentException if the argument is negative. */ public boolean setLastModifiedTime(long time) { if (time < 0) throw new IllegalArgumentException("Negative time"); return device.setLastWriteTime(completeName, time, true); } //end setLastModifiedTime(long time) /** * Compares two pathnames without the device names lexicographically. * @param pathname the pathname to be compared to this pathname. * @return zero if the argument is equal to this pathname, a * value less than zero if this pathname is lexicographically less * than the argument, or a value greater than zero if this pathname * is lexicographically greater than the argument. */ public int compareTo(ExtendedFile pathname) { String thisAbsPath = StringOperations.removeDeviceName(getAbsolutePath()); return thisAbsPath.compareTo(StringOperations.removeDeviceName(pathname.getAbsolutePath())); } //end compareTo(ExtendedFile pathname) /** * Compares this pathname to another object. If the other object * is an pathname, then this function behaves like {@link * #compareTo(ExtendedFile)}. Otherwise, it throws a ClassCastException, * since pathnames can only be compared to pathnames. * @param o the Object to be compared to this pathname. * @return if the argument is an pathname, returns zero if the * argument is equal to this pathname, a value less than zero if * this pathname is lexicographically less than the argument, or * a value greater than zero if this pathname is * lexicographically greater than the argument. * @throws ClassCastException if the argument is not an pathname. */ public int compareTo(Object o) { return compareTo((ExtendedFile)o); } //end compareTo(Object o) /** * Tests this pathname for equality with the given object. * Returns true if and only if the argument is not null and is an * pathname that denotes the same file or directory as this * pathname. * @param obj the object to be compared with this pathname. * @return true if and only if the objects are the same; * false otherwise. */ public boolean equals(Object obj) { if ((obj != null) && (obj instanceof ExtendedFile)) { return compareTo((ExtendedFile)obj) == 0; } return false; } //end equals(Object obj) /** * Returns the pathname string of this pathname. This is just the * string returned by the getPath method. * @return the string form of this pathname. */ public String toString() { return getPath(); } //end toString() /** * Return the attribute of this pathname file or directory. * @return attribute stored at the directory entry. */ public byte getAttribute() { return device.getAttribute(completeName); } //end getAttribute() /** * Return the date the file was last modified. * @return DirectoryDate object which contains the date information. */ public DirectoryDate getLastModifiedDate() { return new DirectoryDate(lastModified()); } //end getLastModifiedDate() /** * Return the time the file was last modified. * @return DirectoryTime object which contains the time information. */ public DirectoryTime getLastModifiedTime() { return new DirectoryTime(lastModified()); } //end getLastModifiedTime() /** * Return the time the file was created. * @return DirectoryTime object which contains the time information. */ public DirectoryTime getCreationTime() { return new DirectoryTime(created()); } //end getCreationTime() /** * Return the date the file was created. * @return DirectoryDate object which contains the date information. */ public DirectoryDate getCreationDate() { return new DirectoryDate(created()); } //end getCreationDate() } //end class ExtendedFile