001package bradleyross.library.helpers;
002import java.io.File;
003import java.io.FileFilter;
004import java.io.IOException;
005import java.util.ArrayList;
006import java.util.Iterator;
007import java.util.NoSuchElementException;
008/**
009 * Generates an Iterator that will contain all of the
010 * files belonging to a directory file structure.
011 * @author Bradley Ross
012 *
013 */
014public class DirWalker implements Iterator<File> {
015        /**
016         * Controls amount of diagnostic listing.
017         * 
018         * <p>The default value is zero.  Higher values result in more information.</p>
019         */
020        public int debugLevel = 0;
021        /**
022         * Getter for {@link #debugLevel} property.
023         * @return value of property
024         */
025        public int getDebugLevel() {
026                return debugLevel;
027        }
028        /**
029         * Setter for {@link #debugLevel} property.
030         * @param value value for property
031         */
032        public void setDebugLevel(int value) {
033                debugLevel = value;
034        }
035        /**
036         * Root directory  of directory structure.
037         */
038        protected File root = null;
039        /**
040         * FileFilter to be applied against files before
041         * returning through iterator.
042         */
043        protected FileFilter filter = new DummyFilter();
044        /**
045         * Set to true when no more items to be returned.
046         */
047        protected boolean done = false;
048        /**
049         * Maximum number of files to be returned.
050         */
051        protected int maxCount = 2000;
052        /**
053         * Getter for {@link #maxCount} property.
054         * @return value of property
055         */
056        public int getMaxCount() {
057                return maxCount;
058        }
059        /**
060         * Setter for {@link #maxCount} property.
061         * @param value value for property
062         */
063        public void setMaxCount(int value) {
064                maxCount = value;
065        }
066        /**
067         * Dummy file filter that accepts all files and directories.
068         * @author Bradley Ross
069         */
070        protected class DummyFilter implements FileFilter{
071                public boolean accept(File value) { return true; }
072        }
073        /**
074         * FileFilter that only accepts files.
075         * @author Bradley Ross
076         *
077         */
078        protected class FilesOnly implements FileFilter {
079                public boolean accept(File value) {
080                        if (value.isFile() && filter.accept(value)) {
081                                return true;
082                        } else {
083                                return false;
084                        }
085                }
086        }
087        /**
088         * FileFilter that only accepts directories.
089         * @author Bradley Ross
090         *
091         */
092        protected class DirectoriesOnly implements FileFilter {
093                public boolean accept(File value) {
094                        if (value.isDirectory()) {
095                                return true; 
096                        } else {
097                                return false;
098                        }
099                }
100        }
101        /**
102         * Instance of files only file filter.
103         */
104        protected FileFilter filesOnly = new FilesOnly();
105        /**
106         * Instance of directories only file filter.
107         */
108        protected FileFilter directoriesOnly = new DirectoriesOnly();
109        /**
110         * Instance of iterator for going through directories.
111         */
112        protected DirIterator directories = null;
113        /**
114         * Instance of iterator for going through files in directory.
115         */
116        protected LocalIterator files = null;
117        /**
118         * Indicates whether a directory contains files.
119         * @param value directory
120         * @return true if it contains files
121         */
122        protected boolean hasFiles(File value) {
123                File[] test = value.listFiles(filesOnly);
124                if (test == null) {
125                        return false;
126                } else if (test.length == 0) {
127                        return false;
128                } else {
129                        return true;
130                }
131        }
132        /**
133         * Indicates if a directory contains subdirectories.
134         * @param value directory
135         * @return true if it contains subdirectories
136         */
137        protected boolean hasDirectories(File value) {
138                File[] test = value.listFiles(directoriesOnly);
139                if (test == null) {
140                        return false;
141                } else if (test.length == 0) {
142                        return false;
143                } else {
144                        return true;
145                }
146        }
147        /**
148         * Constructor that results in list of all files that are
149         *      children of root directory.
150         * @param rootValue root directory
151         * @throws IOException
152         */
153        public  DirWalker (File rootValue) throws IOException {
154                if (!rootValue.isDirectory()) {
155                        throw new IOException("Root " + rootValue.getCanonicalPath() + 
156                                        " is not a directory");
157                }
158                root = rootValue;
159                filter = new DummyFilter();
160                if (!hasFiles(root) && !hasDirectories(root)) {
161                        done = true;
162                        return;
163                }
164                directories = new DirIterator(rootValue);
165                if (!directories.hasNext()) {
166                        done = true;
167                        return;
168                }
169                files = new LocalIterator(directories.next());
170        }
171        /**
172         * Constructor that results in a list that contains all files that
173         *        are descendants of the root directory and satisfy the conditions
174         *    contained in the file filter.
175         * @param rootValue root directory
176         * @param filterValue file filter
177         * @throws IOException
178         */
179        public  DirWalker (File rootValue, FileFilter filterValue) throws IOException {
180                if (!rootValue.isDirectory()) {
181                        throw new IOException("Root " + rootValue.getCanonicalPath() + 
182                                        " is not a directory");
183                }
184                root = rootValue;
185                filter = filterValue;
186                if (!hasFiles(root) && !hasDirectories(root)) {
187                        done = true;
188                        return;
189                }
190                directories = new DirIterator(rootValue);
191                if (!directories.hasNext()) {
192                        done = true;
193                        return;
194                }
195                files = new LocalIterator(directories.next());
196        }
197        /**
198         * Iterator returning the files in a directory.
199         * @author Bradley Ross
200         *
201         */
202        protected class LocalIterator implements Iterator<File>  {
203                protected File dir = null;
204                protected File[] files = null;
205                protected int count;
206                protected int position;
207                public LocalIterator(File dirValue) {
208                        dir = dirValue;
209                        files = dir.listFiles(filesOnly);
210                        count = files.length;
211                        position = -1;
212                }
213                public File next() {
214                        position = position + 1;
215                        return files[position];
216                }
217                public boolean hasNext() {
218                        if (position < count - 1) {
219                                return true;
220                        } else {
221                                return false;
222                        }
223                }
224                public void remove() {
225                        throw new UnsupportedOperationException("remove not valid for this class");
226                }
227        }
228        /**
229         * This iterates through the directories.
230         * 
231         * <p>The current location in the tree is given by the structures
232         *    {@link #levels} and {@link #depth}.
233         *    
234         * @author Bradley Ross
235         *
236         */
237        protected class DirIterator implements Iterator<File> {
238                protected File root = null;
239                /**
240                 * Depth of the search tree.
241                 * <p>A value of zero means that there are no levels.  A value
242                 *    of n means that there are n levels and 
243                 *    the highest level is n-1.</p>
244                 */
245                protected int depth = 0;
246                /**
247                 * Array of Level objects representing the levels in the tree
248                 * search.
249                 * 
250                 * <p>The current positions in the levels at the start of executing
251                 *    next represent the next item to be retrieved.</p>
252                 */
253                protected ArrayList<Level> levels = new ArrayList<Level>();
254                /**
255                 * Set to true after the last directory has been retrieved using
256                 * the next method.
257                 */
258                protected boolean done = false;
259                /**
260                 * Set to false after first directory has been retrieved using the
261                 * next method.
262                 */
263                protected boolean initial = true;
264                protected Level currentLevel = null;
265                protected File currentDir = null;
266                protected File nextDir = null;
267                protected int limit = maxCount;
268                /** 
269                 * Number of directories that have been retrieved using next.
270                 */
271                protected int count = 0;
272                /**
273                 * Constructor using depth first search.
274                 * @param value root directory
275                 */
276                public DirIterator(File value) throws IOException {
277                        if (!value.isDirectory()) {
278                                done = true;
279                                throw new IOException("Root " + value.getCanonicalPath() +
280                                                " is not a directory");
281                        }
282                        root = value;
283                }
284                /**
285                 * Returns the next item in the list.
286                 * @return next item
287                 */
288                public File next() throws NoSuchElementException {
289                        if (!initial) {
290                                if (debugLevel > 0) {
291                                        System.out.println("*****");
292                                        System.out.println("Starting next, iteration " + Integer.toString(count) + "  Depth = " + Integer.toString(depth));
293                                        System.out.print("Current positions:  "); 
294                                        for (int i = 0; i < depth; i++) {
295                                                System.out.print(Integer.toString(levels.get(i).getCurrentPosition()) + " ");
296                                        }
297                                        System.out.println();
298                                }
299                        }
300                        if (done) {
301                                throw new NoSuchElementException();
302                        }
303                        count++;
304                        if (limit > 0 && count > limit) {
305                                done = true;
306                                throw new NoSuchElementException();
307                        }
308                        if (initial) {
309                                if (debugLevel > 0) {
310                                        System.out.println("Initial pass");
311                                }
312                                initial = false;
313                                if (!hasDirectories(root)) {
314                                        done = true;
315                                        return root;
316                                } else {
317                                        Level newLevel = new Level(root);
318                                        newLevel.next();
319                                        levels.add(newLevel);
320                                        depth = 1;
321                                        return root;
322                                }
323                        }
324                        currentLevel = levels.get(depth - 1);
325                        if (hasDirectories(currentLevel.current())) {
326                                if (debugLevel > 0) {
327                                        System.out.println("Last entry had subdirectory");
328                                }
329                                Level newLevel = new Level(currentLevel.current());
330                                if (newLevel.hasNext()) {
331                                        File returnItem = newLevel.next();
332                                        levels.add(newLevel);
333                                        depth = depth + 1;
334                                        return returnItem;
335                                } else {
336                                        throw new NoSuchElementException("Shouldn't occur");
337                                }
338
339                        } else if (currentLevel.hasNext()) {
340                                if (debugLevel > 0) {
341                                        System.out.println("Go to next at current level");
342                                }
343                                File returnItem = currentLevel.next();
344                                return returnItem;
345                        } else {
346                                while (true) {
347                                        if (debugLevel > 0) {
348                                                System.out.println("Move up tree");
349                                        }
350                                        levels.remove(depth - 1);
351                                        depth = depth - 1;
352                                        if (depth == 0) {
353                                                done = true;
354                                                throw new NoSuchElementException();
355                                        }
356                                        currentLevel = levels.get(depth - 1);
357                                        if (currentLevel.hasNext()) { break; }
358                                }
359                                File returnItem = currentLevel.next();
360                                return returnItem;
361                        }
362                }
363                /**
364                 * Determines if there are more items in the list.
365                 * @return true if there are more items
366                 */
367                public boolean hasNext() {
368                        if (!root.isDirectory()) { return false; }
369                        if (initial && (hasFiles(root) || hasDirectories(root))) {
370                                return true;
371                        }
372                        if (done) { 
373                                return false;
374                        }
375                        if (hasDirectories(levels.get(depth - 1).current())) {
376                                return true;
377                        }
378                        for (int i = 0; i < depth; i++) {
379                                if (levels.get(i).hasNext()) { return true; }
380                        }
381                        return false;  // temporary
382                }
383                /**
384                 * This method is an optional method that is not implemented for this class.
385                 */
386                public void remove() {
387                        throw new UnsupportedOperationException("remove not valid for this class");
388                }
389        }
390        /**
391         * This represents a level at a single depth in the tree search.
392         * @author Bradley Ross
393         *
394         */
395        public class Level implements Iterator<File> {
396                protected File[] dirs = null;
397                /**
398                 * Index of last element fetched using next().
399                 */
400                protected int position = 0;
401                /**
402                 * Number of directories to be processed.
403                 */
404                protected int length = 0;
405                protected boolean done = false;
406                public Level(File value) { 
407                        dirs = value.listFiles(directoriesOnly);
408                        if (dirs == null) { 
409                                length = 0;
410                                position = -1;
411                                done = true;
412                        }
413                        else if (dirs.length == 0) { 
414                                length = 0;
415                                position = -1;
416                                done = true; }
417                        length = dirs.length;
418                        position = -1;
419                }
420                public boolean hasNext() {
421                        if (done) {
422                                return false;
423                        }
424                        if (length - 1 < position + 1) {
425
426                                return false;
427                        }
428                        return true;
429                }
430                public File current()  {
431                        if (done) {
432                                throw new NoSuchElementException();
433                        }
434                        return dirs[position];
435                }
436                /**
437                 * Returns position of last item retrieved.
438                 * @return position of last item retrieved
439                 */
440                public int getCurrentPosition() {
441                        return position;
442                }
443                public File next() {
444                        if (done) {
445                                throw new NoSuchElementException();
446                        }
447                        if (length - 1 < position + 1) {
448                                done = true;
449                                throw new NoSuchElementException();
450                        }
451                        position = position + 1;
452                        return dirs[position];
453                }
454                public void remove() {
455                        throw new UnsupportedOperationException("remove not valid for this class");
456                }
457        }
458        /**
459         * Indicates whether there are more files in the list.
460         * @return true if there are more items
461         */
462        public boolean hasNext() {
463                if (done) {
464                        return false;
465                } else {
466                        while (!files.hasNext() && directories.hasNext()) {
467                                files = new LocalIterator(directories.next());
468                        }
469                        if (files.hasNext()) { return true; }
470                        if (directories.hasNext()) { return true; }
471                        return false;
472                }
473        }
474        /**
475         * Returns next file in list.
476         * @return next file
477         */
478        public File next() {
479                if (done) {
480                        throw new NoSuchElementException();
481                }
482                while (!files.hasNext() && directories.hasNext()) {
483                        files = new LocalIterator(directories.next());
484                }
485                if (!files.hasNext() && !directories.hasNext()) {
486                        done = true;
487                        throw new NoSuchElementException();
488                }
489                return files.next();
490        }
491        /**
492         * This is an optional method that is not implemented for this class.
493         * 
494         */
495        public void remove() {
496                throw new UnsupportedOperationException("remove not valid for this class");
497        }
498        /**
499         * Test program for Level class.
500         * @param value root directory
501         */
502        public void test1(File value) throws IOException {
503                Level test = new Level(value);
504                while (test.hasNext()) {
505                        File item = test.next();
506                        System.out.println(item.getAbsolutePath() + " " + 
507                                        Integer.toString(test.getCurrentPosition()));
508                }
509        }
510        /**
511         * Test iterator for DirWalker class.
512         * @param value root of directory structure
513         * @return iterator
514         * @throws IOException
515         */
516        public Iterator<File> test2(File value) throws IOException {
517                return new DirIterator(value);
518        }
519        /**
520         * Test driver.
521         * @param args - not used
522         */
523        public static void main(String[] args) {
524                try {
525                        File root = new File("/");
526                        DirWalker instance = new DirWalker(root);
527                        instance.test1(new File("/Users/bradleyross/AmtrakDesktop"));
528                        System.out.println("Starting second test");
529                        Iterator<File> second = instance.test2(new File("/Users/bradleyross/AmtrakDesktop"));
530                        while (second.hasNext()) {
531                                File item = second.next();
532                                System.out.println(item.getAbsolutePath());
533                        }
534                } catch (Exception e)  {
535                        System.out.println(e.getClass().getName() + " " + e.getMessage());
536                        e.printStackTrace();
537                }
538                System.out.println("Program complete");
539        }
540}