001 // Copyright 2006-2008 Regents of the University of California. May be used
002 // under the terms of the revised BSD license. See LICENSING for details.
003 package org.joe_e.file;
004
005 import java.io.File;
006 import java.io.FileInputStream;
007 import java.io.FileNotFoundException;
008 import java.io.FileOutputStream;
009 import java.io.IOException;
010 import java.io.InputStream;
011 import java.io.OutputStream;
012 import java.util.Arrays;
013 import java.util.Comparator;
014
015 import org.joe_e.array.ConstArray;
016
017 /**
018 * {@link File} construction. This provides a capability-safe API for access to
019 * files. A File object grants read and write access to a file or directory.
020 * Due to limitations in Java, all file references are to textual file names, not
021 * file descriptors. Multiple operations on a File may thus apply to different
022 * incarnations of the file.
023 */
024 public final class Filesystem {
025
026 private Filesystem() {}
027
028 /*
029 * The safety of this API relies on all File constructors being tamed away.
030 */
031
032 /**
033 * Produce a File capability for a file contained in a folder. The returned
034 * object is just a handle; the underlying file may not yet exist.
035 * @param folder containing folder
036 * @param child a single filename component, not a relative path
037 * @return a capability for the requested file
038 * @throws IllegalArgumentException if <code>folder</code> is null or
039 * the empty path
040 */
041 static public File file(final File folder, final String child)
042 throws InvalidFilenameException {
043 // With Java's File(File, String) constructor, either of these can give
044 // access to all files on system; null is ambiently available.
045 if (folder == null || folder.getPath().equals("")) {
046 throw new IllegalArgumentException();
047 }
048 checkName(child);
049 return new File(folder, child);
050 }
051
052 /**
053 * Vets a filename. Checks that the argument would be interpreted as a
054 * file name rather than as a path or relative directory specifier
055 * @param name a single filename component, not a relative path
056 * @throws InvalidFilenameException <code>name</code> is rejected\
057 */
058 static public void checkName(final String name)
059 throws InvalidFilenameException {
060 // Check for path operation names.
061 if (name.equals("") || name.equals(".") || name.equals("..")) {
062 throw new InvalidFilenameException();
063 }
064
065 // Check for path separator char.
066 if (name.indexOf(File.separatorChar) != -1) {
067 throw new InvalidFilenameException();
068 }
069 // '/' works as a separator on Windows, even though it isn't the
070 // platform separator character
071 if ('/' != File.separatorChar && name.indexOf('/') != -1) {
072 throw new InvalidFilenameException();
073 }
074 }
075
076 /**
077 * List the contents of a directory.
078 * @param dir directory to list
079 * @return directory entries, sorted alphabetically
080 * @throws IOException <code>dir</code> is not a directory, or an I/O error
081 */
082 static public ConstArray<File> list(final File dir) throws IOException {
083 final File[] contents = dir.listFiles();
084 if (contents == null) {
085 throw new IOException();
086 }
087 Arrays.sort(contents, new Comparator<File>() {
088 public int compare(final File a, final File b) {
089 return a.getName().compareTo(b.getName());
090 }
091 });
092 return ConstArray.array(contents);
093 }
094
095 /**
096 * Gets the length of a file
097 * @param file file to stat
098 * @return the length of the file in bytes
099 * @throws FileNotFoundException <code>file</code> not found
100 */
101 static public long length(final File file)
102 throws FileNotFoundException {
103 if (!file.isFile()) {
104 throw new FileNotFoundException();
105 }
106 return file.length();
107 }
108
109 /**
110 * Opens an existing file for reading.
111 * @param file file to open
112 * @return opened input stream
113 * @throws FileNotFoundException <code>file</code> not found
114 */
115 static public InputStream read(final File file)
116 throws FileNotFoundException {
117 if (!file.isFile()) {
118 throw new FileNotFoundException();
119 }
120 return new FileInputStream(file);
121 }
122
123 /**
124 * Creates a file for writing.
125 * @param file file to create
126 * @return opened output stream
127 * @throws IOException <code>file</code> could not be created
128 */
129 static public OutputStream writeNew(final File file) throws IOException {
130 if (!file.createNewFile()) {
131 throw new IOException();
132 }
133 return new FileOutputStream(file);
134 }
135 }