/* Copyright (c) 2006, Sriram Srinivasan
*
* You may distribute this software under the terms of the license
* specified in the file "License"
*/
package kilim.analysis;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Stack;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* Utility class to present a uniform iterator interface for file containers; presently
* includes directories and jar files.
*/
public class FileLister implements Iterable<FileLister.Entry> {
public static abstract class Entry {
public abstract String getFileName();
public abstract long getSize();
public abstract InputStream getInputStream() throws IOException;
};
/**
* weak ref to a container to avoid hanging on to an open jar file.
*/
volatile WeakReference<FileContainer> containerRef;
String name;
public FileLister(String dirOrJarName) throws IOException {
name= dirOrJarName;
}
/**
* @param relativeFileName
* @return if the relativeFileName exists in the directory or jar represented by FileLister object
* open it. If not return null.
* @throws IOException
*/
public Entry open(String relativeFileName) throws IOException {
return getContainer().open(relativeFileName);
}
// Lazily initialize the container.
private FileContainer getContainer() throws IOException {
FileContainer container = null;
if (containerRef != null) {
container = containerRef.get();
if (container != null) return container;
}
if (name.endsWith(".jar")) {
container = openJar(this.name);
} else {
File f = new File(this.name);
if (f.exists() && f.isDirectory()) {
container = new DirIterator(f);
} else {
throw new IOException("Expected jar file or directory name");
}
}
containerRef = new WeakReference<FileContainer>(container);
return container;
}
private FileContainer openJar(String jarFile) throws IOException {
return new JarIterator(new JarFile(jarFile));
}
public Iterator<FileLister.Entry> iterator() {
try {
return getContainer();
} catch (IOException ignore) {}
return null;
}
}
abstract class FileContainer implements Iterator<FileLister.Entry> {
abstract FileLister.Entry open(String relativeFileName) throws IOException;
}
/**
* Preorder traversal of a directory. Returns everything including directory
* names.
*/
class DirIterator extends FileContainer {
final File root;
private static class DirEntry extends FileLister.Entry {
final File file;
DirEntry(File f) {file = f;}
@Override
public long getSize() {
return file.length();
}
@Override
public String getFileName() {
try {
return file.getCanonicalPath();
} catch (IOException ignore) {}
return null;
}
@Override
public InputStream getInputStream() throws IOException {
return new BufferedInputStream(new FileInputStream(file));
}
}
Stack<File> stack = new Stack<File>();
DirIterator(File f) {
root = f;
stack.push(f);
}
public boolean hasNext() {
return !stack.isEmpty();
}
public FileLister.Entry next() {
File ret = stack.pop();
if (ret.isDirectory()) {
// prepare for next round
File[] files = ret.listFiles();
// first add all directories to stack, then the files, so that
// all files in a directory are processed continuously
for (int i = files.length - 1; i >= 0; i--) {
File ff = files[i];
if (ff.isDirectory()) {
stack.push(ff);
}
}
for (int i = files.length - 1; i >= 0; i--) {
File ff = files[i];
if (!ff.isDirectory()) {
stack.push(ff);
}
}
}
return new DirEntry(ret);
}
public void remove() {
throw new RuntimeException("FileLister does not remove files");
}
@Override
FileLister.Entry open(String fileName) throws IOException {
File ret = new File(root.getAbsolutePath() + File.separatorChar + fileName);
if (ret.exists() && ret.isFile()) {
return new DirEntry(ret);
}
return null;
}
}
class JarIterator extends FileContainer {
Enumeration<JarEntry> jarEnum;
JarFile jarFile;
String nextName;
private class JEntry extends FileLister.Entry {
private final JarEntry jarEntry;
JEntry(JarEntry j) {jarEntry = j;}
@Override
public String getFileName() {
return jarEntry.getName();
}
@Override
public InputStream getInputStream() throws IOException {
return jarFile.getInputStream(jarEntry);
}
@Override
public long getSize() {
return jarEntry.getSize();
}
}
JarIterator(JarFile f) {
jarFile = f;
jarEnum = f.entries();
}
public boolean hasNext() {
return jarEnum.hasMoreElements();
}
public FileLister.Entry next() {
return new JEntry(jarEnum.nextElement());
}
public void remove() {
throw new RuntimeException("FileLister does not remove files");
}
@Override
FileLister.Entry open(String relativeFileName) throws IOException {
JarEntry e = jarFile.getJarEntry(relativeFileName);
return e == null ? null : new JEntry(e);
}
}