FileFind.java

/*
 * FileFind.java
 *
 * Created on November 12, 2001, 6:29 AM
 */

package emissary.util.io;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Iterator;
import java.util.NoSuchElementException;
import javax.annotation.Nullable;

/**
 * Implements the unix 'find' command. This class lists files within a directory no matter how many levels of
 * subdirectories are used. The basic functionality of find is supported. If you would like the options then go ahead
 * and implement them.
 *
 * @author ce
 * @version 1.0
 */

public class FileFind {

    public static final int NO_OPTIONS = 0x00;
    public static final int FILES_FLAG = 0x01;
    public static final int DIRECTORIES_FLAG = 0x02;

    @SuppressWarnings("unused")
    private boolean wantFiles = true;
    private boolean wantDirectories = false;

    /** Creates new FileFind */
    public FileFind() {}

    /**
     * Creates new FileFind with options
     */
    public FileFind(int options) {
        // default is true
        if ((options & FILES_FLAG) == 0) {
            wantFiles = false;
        }

        // default is false
        if ((options & DIRECTORIES_FLAG) > 0) {
            wantDirectories = true;
        }
    }

    public Iterator<?> find(String filename) throws IOException {
        return new FileIterator(filename);
    }

    public Iterator<?> find(String filename, FileFilter filter) throws IOException {
        return new FileIterator(filename, filter);
    }

    @SuppressWarnings("SystemOut")
    public static void main(String[] args) {

        FileFind ff = new FileFind(FILES_FLAG | DIRECTORIES_FLAG);

        String start = "c:\\Temp";
        if (args.length > 0) {
            start = args[0];
        }


        try {
            Iterator<?> i = ff.find(start);
            while (i.hasNext()) {
                File f = (File) i.next();
                if (f.isDirectory()) {
                    System.out.println("d: " + f.getPath());
                } else {
                    System.out.println("f: " + f.getPath());
                }
            }
        } catch (Exception e) {
            System.out.println("Exception:" + e);
        }
    }


    /** Iterator that iterates through a directory tree. */
    @SuppressWarnings("rawtypes")
    class FileIterator implements Iterator {
        /**
         * Stack of Files and directory lists keeping track of where in the tree we are.
         */
        private final Deque<Object> currentPath = new ArrayDeque<>();
        @Nullable
        private FileFilter filter = null;

        public FileIterator(String filename) throws IOException {
            this(filename, null);
        }

        public FileIterator(String filename, FileFilter filter) throws IOException {
            File f = new File(filename);
            if (!f.exists() || !f.canRead()) {
                throw new IOException("File not Found:" + filename);
            }
            currentPath.push(f);
            this.filter = filter;
            findNextFile();
        }

        /**
         * The stack always has a 'file' on the top if it has one, so return true if the stack is non empty.
         */
        @Override
        public boolean hasNext() {
            return !currentPath.isEmpty();
        }

        /** {@inheritDoc} */
        @Override
        public Object next() {
            if (!currentPath.isEmpty()) {
                Object tmp = currentPath.pop();
                findNextFile();
                return tmp;
            }
            throw new NoSuchElementException();
        }

        /** Not supported, but we could delete the file in this case, or should we? */
        @Override
        public void remove() {
            throw new UnsupportedOperationException("Not implemented");
        }

        /** Finally here is all the meat of this file. */
        private void findNextFile() {
            // Start by assumming that there are no files left.
            boolean found = false;
            // While the stack is not empty, and we have not found the type of file that we are looking for.
            while (!currentPath.isEmpty() && !found) {
                Object tmp = currentPath.peek();
                if (tmp instanceof File && !((File) tmp).isDirectory()) {
                    // We found one. Leave it on the stack for 'next()'
                    found = true;
                } else if (tmp instanceof File && ((File) tmp).isDirectory()) {

                    // Pop the entry for the directory name if not desired
                    Object theDir = currentPath.pop();

                    // Add the directory contents to the stack
                    File[] tmpContents = ((File) tmp).listFiles(filter);
                    currentPath.push(new DirectoryList(tmpContents));

                    // Put back the directory if we are supposed to return thm
                    if (wantDirectories) {
                        currentPath.push(theDir);
                        found = true;
                    }

                } else if (tmp instanceof DirectoryList) {
                    // This is a directories contents. Check for the next entry.
                    if (((DirectoryList) tmp).hasNext()) {
                        currentPath.push(((DirectoryList) tmp).next());
                    } else {
                        currentPath.pop();
                    }
                }
            }
        }

    }
    /**
     * Simple class to capture the contents of a directory AND a position within it. We use Iterator like methods, but avoid
     * the added complexity of the 'Iterator' class.
     */
    static class DirectoryList {
        private final File[] contents;
        private int position;

        public DirectoryList(File[] contents) {
            this.contents = contents;
            position = 0;
        }

        public boolean hasNext() {
            return (position < contents.length);
        }

        public File next() {
            return contents[position++];
        }
    }
}