JavaFileSystem.java
/***************************************************************************
Copyright 2008 Emily Estes
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
***************************************************************************/
package net.metanotion.io;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
/** Implementation of the "normal" file system available through the Java I/O package.
*/
public final class JavaFileSystem implements TraverseableFileSystem<JavaFileSystem.JavaFile> {
private static final String randomAccessReadWriteMode = "rw";
private static final String destinationAccessError = "Destination file must be from the same FileSystem object.";
private static enum PROPERTIES {
CanRead {
public Object getAttribute(JavaFileSystem jfs, JavaFile f) { return null; }
public void setAttribute(JavaFileSystem jfs, JavaFile f, Object val) { throw new UnsupportedOperationException(); }
},
CanWrite {
public Object getAttribute(JavaFileSystem jfs, JavaFile f) { return null; }
public void setAttribute(JavaFileSystem jfs, JavaFile f, Object val) { throw new UnsupportedOperationException(); }
},
Hidden {
public Object getAttribute(JavaFileSystem jfs, JavaFile f) { return null; }
public void setAttribute(JavaFileSystem jfs, JavaFile f, Object val) { throw new UnsupportedOperationException(); }
},
LastModifiedDate {
public Object getAttribute(JavaFileSystem jfs, JavaFile f) { return new Date(f.f.lastModified()); }
public void setAttribute(JavaFileSystem jfs, JavaFile f, Object val) { throw new UnsupportedOperationException(); }
},
ReadOnly {
public Object getAttribute(JavaFileSystem jfs, JavaFile f) { return null; }
public void setAttribute(JavaFileSystem jfs, JavaFile f, Object val) { throw new UnsupportedOperationException(); }
},
MIMEType {
public Object getAttribute(JavaFileSystem jfs, JavaFile f) { return jfs.mimeUtil.getMIMEType(f); }
public void setAttribute(JavaFileSystem jfs, JavaFile f, Object val) { throw new UnsupportedOperationException(); }
};
abstract Object getAttribute(JavaFileSystem jfs, JavaFile f);
abstract void setAttribute(JavaFileSystem jfs, JavaFile f, Object val);
}
private final List<String> root;
private final MIMEUtil mimeUtil;
private final String rootCanon;
private static boolean copy(java.io.File src, java.io.File dst) {
FileInputStream in = null;
FileOutputStream out = null;
try {
try {
in = new FileInputStream(src);
out = new FileOutputStream(dst);
} catch (final java.io.FileNotFoundException fnfe) {
throw new FileNotFoundException();
} catch (final IOException ioe) {
return false;
}
new StreamPump(in, out).pumpAll();
in.close();
out.close();
} catch (final IOException ioe) {
return false;
} finally {
if(in != null) {
try {
in.close();
} catch (final IOException ioe) { }
}
if(out != null) {
try {
out.close();
} catch (final IOException ioe) { }
}
}
return true;
}
/** This class is the file implementation used by the JavaFileSystem implementation. */
public static final class JavaFile implements WriteableFile<JavaFile>, TraverseableFile<JavaFile> {
private final JavaFileSystem owner;
private final String[] name;
private final String url;
private final java.io.File f;
private JavaFile(JavaFileSystem jfs, String[] name, String url, java.io.File f) {
this.owner = jfs;
this.name = name;
this.url = url;
this.f = f;
}
@Override public int compareTo(final JavaFile file) {
if (file.owner != owner) {
throw new BadFileException("Only files from the same FileSystem object can be compared.");
}
return this.url.compareTo(file.url);
}
@Override public boolean equals(final Object o) {
if(o==this) { return true; }
if(o==null) { return false; }
if(!(o instanceof JavaFile)) { return false; }
final JavaFile file = (JavaFile) o;
if(file.owner != this.owner) { return false; }
return this.url.equals(file.url);
}
@Override public int hashCode() { return owner.hashCode() + url.hashCode(); }
@Override public boolean renameTo(final JavaFile destination) {
if(destination.owner != owner) { throw new BadFileException(destinationAccessError); }
return f.renameTo(destination.f);
}
@Override public boolean copyTo(final File destination) {
final JavaFile d = (JavaFile) destination;
if(d.owner != owner) { throw new BadFileException(destinationAccessError); }
return JavaFileSystem.copy(this.f, d.f);
}
@Override public boolean copyFrom(final FileSource fsrc) {
fsrc.writeToFile(f);
return true;
}
@Override public boolean createNewFile() {
try {
return f.createNewFile();
} catch (final IOException ioe) { throw new RuntimeException(ioe); }
}
@Override public boolean delete() { return f.delete(); }
@Override public boolean exists() { return f.exists(); }
@Override public JavaFile getParent() {
final StringBuilder b = new StringBuilder("");
if((this.name.length - 1) < owner.root.size()) {
throw new RuntimeException("Cannot get the parent of the root file.");
}
for(int i=0;i<(this.name.length - 1);i++) {
if(i < owner.root.size()) { continue; }
b.append(FileSystem.SEPARATOR);
b.append(this.name[i]);
}
return owner.get(b.toString());
}
@Override public boolean isFile() { return f.isFile(); }
@Override public boolean isDirectory() { return f.isDirectory(); }
@Override public String getShortName() { return this.name[this.name.length - 1]; }
@Override public JavaFile getChild(String n) {
if(this.name.length == 0) { return owner.get(FileSystem.SEPARATOR + n); }
final StringBuilder b = new StringBuilder("");
for(int i=0;i<this.name.length;i++) {
b.append(FileSystem.SEPARATOR);
b.append(this.name[i]);
}
b.append(FileSystem.SEPARATOR);
b.append(n);
return owner.get(b.toString());
}
@Override public void setAttribute(final String attrib, final Object val) {
JavaFileSystem.PROPERTIES.valueOf(attrib).setAttribute(owner, this, val);
}
@Override public Object getAttribute(final String attrib) {
return JavaFileSystem.PROPERTIES.valueOf(attrib).getAttribute(owner, this);
}
@Override public Iterator<String> listAttributes() {
final ArrayList<String> props = new ArrayList<>();
for(final JavaFileSystem.PROPERTIES p: JavaFileSystem.PROPERTIES.values()) { props.add(p.toString()); }
return props.iterator();
}
@Override public Iterator<JavaFile> iterator() {
try {
final String[] fileNames = f.list();
if(fileNames == null) {
final List<JavaFile> empty = Collections.emptyList();
return empty.iterator();
}
final ArrayList<JavaFile> files = new ArrayList<>(fileNames.length);
for(final String theFile: fileNames) {
final String[] fName = new String[this.name.length + 1];
System.arraycopy(this.name, 0, fName, 0, this.name.length);
final java.io.File f2 = new java.io.File(f, theFile).getCanonicalFile();
fName[this.name.length] = f2.getName();
files.add(new JavaFile(owner, fName, f2.toString(), f2));
}
return files.iterator();
} catch (final IOException ioe) { throw new RuntimeException("Error listing files in directory", ioe); }
}
@Override public int countChildren() {
final String[] flist = f.list();
return ((flist != null) ? flist.length : 0); }
@Override public boolean mkdir() { return f.mkdir(); }
@Override public boolean mkdirs() { return f.mkdirs(); }
@Override public long length() { return f.length(); }
@Override public String toString() {
if(this.name.length == 0) { return FileSystem.SEPARATOR; }
final StringBuilder b = new StringBuilder("");
for(int i=0;i<this.name.length;i++) {
if(i < owner.root.size()) { continue; }
b.append(FileSystem.SEPARATOR);
b.append(this.name[i]);
}
return b.toString();
}
@Override public boolean setLength(long length) {
java.io.RandomAccessFile raf = null;
try {
raf = new java.io.RandomAccessFile(f, randomAccessReadWriteMode);
raf.setLength(length);
} catch (final IOException ioe) {
} finally {
if (raf != null) {
try {
raf.close();
} catch (final IOException ioe) {
return false;
}
}
}
return true;
}
@Override public java.io.InputStream openInput() {
try {
return new java.io.FileInputStream(this.f);
} catch (final java.io.FileNotFoundException fnfe) {
throw new FileNotFoundException();
} catch (final IOException ioe) {
throw new RuntimeException(ioe);
}
}
@Override public java.io.OutputStream openOutput() {
try {
return new java.io.FileOutputStream(this.f);
} catch (final java.io.FileNotFoundException fnfe) {
throw new FileNotFoundException();
} catch (final IOException ioe) {
throw new RuntimeException(ioe);
}
}
@Override public java.io.RandomAccessFile openRandomAccess() {
try {
return new java.io.RandomAccessFile(this.f, randomAccessReadWriteMode);
} catch (final java.io.FileNotFoundException fnfe) {
throw new FileNotFoundException();
} catch (final IOException ioe) {
throw new RuntimeException(ioe);
}
}
}
/** Create a new Java File System instance with a default {@link net.metanotion.io.MIMEUtil} instance
whose root file is FileSystem.SEPARATOR.
@throws IOException if the root of the filesystem cannot be constructed.
*/
public JavaFileSystem() throws IOException { this(FileSystem.SEPARATOR, new MIMEUtil()); }
/** Create a new Java File System instance with a default {@link net.metanotion.io.MIMEUtil} instance
with a specific root folder.
@param root The root file folder for the file system. The ability to traverse files outside of this root
is considered a security violation.
@throws IOException if the root of the filesystem cannot be constructed.
*/
public JavaFileSystem(final String root) throws IOException { this(root, new MIMEUtil()); }
/** Create a new Java File System instance with a custom {@link net.metanotion.io.MIMEUtil} instance
with a specific root folder.
@param base The root file folder for the file system. The ability to traverse files outside of this root
is considered a security violation.
@param mimeUtil The mime util instance for sniffing file mime types.
@throws IOException if the root of the filesystem cannot be constructed.
*/
public JavaFileSystem(final String base, final MIMEUtil mimeUtil) throws IOException {
System.out.println(base);
this.mimeUtil = mimeUtil;
this.root = FileSystemUtils.getRoot(base, java.io.File.separator);
this.rootCanon = new java.io.File(FileSystemUtils.join(root)).getCanonicalFile().toString();
System.out.println(rootCanon);
}
@Override public JavaFile get(final String url) {
final List<String> file = FileSystemUtils.getFile(url, root);
final String fName = FileSystemUtils.join(file, java.io.File.separator);
final java.io.File jFile = new java.io.File(fName);
try {
final String fCanon = jFile.getCanonicalFile().toString();
if(!fCanon.startsWith(rootCanon)) { throw new RuntimeException("File is outside of root path of FileSystem"); }
return new JavaFile(this, file.toArray(new String[file.size()]), fCanon, jFile);
} catch (final IOException ioe) { throw new RuntimeException(ioe); }
}
@Override public JavaFile getRoot() { return this.get(FileSystem.SEPARATOR); }
}