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 = 3000;
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 if io problems
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 if io problems
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                 * @throws IOException if io problems
276                 */
277                public DirIterator(File value) throws IOException {
278                        if (!value.isDirectory()) {
279                                done = true;
280                                throw new IOException("Root " + value.getCanonicalPath() +
281                                                " is not a directory");
282                        }
283                        root = value;
284                }
285                /**
286                 * Returns the next item in the list.
287                 * @return next item
288                 */
289                public File next() throws NoSuchElementException {
290                        if (!initial) {
291                                if (debugLevel > 0) {
292                                        System.out.println("*****");
293                                        System.out.println("Starting next, iteration " + Integer.toString(count) + "  Depth = " + Integer.toString(depth));
294                                        System.out.print("Current positions:  "); 
295                                        for (int i = 0; i < depth; i++) {
296                                                System.out.print(Integer.toString(levels.get(i).getCurrentPosition()) + " ");
297                                        }
298                                        System.out.println();
299                                }
300                        }
301                        if (done) {
302                                throw new NoSuchElementException();
303                        }
304                        count++;
305                        if (limit > 0 && count > limit) {
306                                done = true;
307                                throw new NoSuchElementException();
308                        }
309                        if (initial) {
310                                if (debugLevel > 0) {
311                                        System.out.println("Initial pass");
312                                }
313                                initial = false;
314                                if (!hasDirectories(root)) {
315                                        done = true;
316                                        return root;
317                                } else {
318                                        Level newLevel = new Level(root);
319                                        newLevel.next();
320                                        levels.add(newLevel);
321                                        depth = 1;
322                                        return root;
323                                }
324                        }
325                        currentLevel = levels.get(depth - 1);
326                        if (hasDirectories(currentLevel.current())) {
327                                if (debugLevel > 0) {
328                                        System.out.println("Last entry had subdirectory");
329                                }
330                                Level newLevel = new Level(currentLevel.current());
331                                if (newLevel.hasNext()) {
332                                        File returnItem = newLevel.next();
333                                        levels.add(newLevel);
334                                        depth = depth + 1;
335                                        return returnItem;
336                                } else {
337                                        throw new NoSuchElementException("Shouldn't occur");
338                                }
339
340                        } else if (currentLevel.hasNext()) {
341                                if (debugLevel > 0) {
342                                        System.out.println("Go to next at current level");
343                                }
344                                File returnItem = currentLevel.next();
345                                return returnItem;
346                        } else {
347                                while (true) {
348                                        if (debugLevel > 0) {
349                                                System.out.println("Move up tree");
350                                        }
351                                        levels.remove(depth - 1);
352                                        depth = depth - 1;
353                                        if (depth == 0) {
354                                                done = true;
355                                                throw new NoSuchElementException();
356                                        }
357                                        currentLevel = levels.get(depth - 1);
358                                        if (currentLevel.hasNext()) { break; }
359                                }
360                                File returnItem = currentLevel.next();
361                                return returnItem;
362                        }
363                }
364                /**
365                 * Determines if there are more items in the list.
366                 * @return true if there are more items
367                 */
368                public boolean hasNext() {
369                        if (!root.isDirectory()) { return false; }
370                        if (initial && (hasFiles(root) || hasDirectories(root))) {
371                                return true;
372                        }
373                        if (done) { 
374                                return false;
375                        }
376                        if (hasDirectories(levels.get(depth - 1).current())) {
377                                return true;
378                        }
379                        for (int i = 0; i < depth; i++) {
380                                if (levels.get(i).hasNext()) { return true; }
381                        }
382                        return false;  // temporary
383                }
384                /**
385                 * This method is an optional method that is not implemented for this class.
386                 */
387                public void remove() {
388                        throw new UnsupportedOperationException("remove not valid for this class");
389                }
390        }
391        /**
392         * This represents a level at a single depth in the tree search.
393         * @author Bradley Ross
394         *
395         */
396        public class Level implements Iterator<File> {
397                protected File[] dirs = null;
398                /**
399                 * Index of last element fetched using next().
400                 */
401                protected int position = 0;
402                /**
403                 * Number of directories to be processed.
404                 */
405                protected int length = 0;
406                protected boolean done = false;
407                public Level(File value) { 
408                        dirs = value.listFiles(directoriesOnly);
409                        if (dirs == null) { 
410                                length = 0;
411                                position = -1;
412                                done = true;
413                        }
414                        else if (dirs.length == 0) { 
415                                length = 0;
416                                position = -1;
417                                done = true; }
418                        length = dirs.length;
419                        position = -1;
420                }
421                public boolean hasNext() {
422                        if (done) {
423                                return false;
424                        }
425                        if (length - 1 < position + 1) {
426
427                                return false;
428                        }
429                        return true;
430                }
431                public File current()  {
432                        if (done) {
433                                throw new NoSuchElementException();
434                        }
435                        return dirs[position];
436                }
437                /**
438                 * Returns position of last item retrieved.
439                 * @return position of last item retrieved
440                 */
441                public int getCurrentPosition() {
442                        return position;
443                }
444                public File next() {
445                        if (done) {
446                                throw new NoSuchElementException();
447                        }
448                        if (length - 1 < position + 1) {
449                                done = true;
450                                throw new NoSuchElementException();
451                        }
452                        position = position + 1;
453                        return dirs[position];
454                }
455                public void remove() {
456                        throw new UnsupportedOperationException("remove not valid for this class");
457                }
458        }
459        /**
460         * Indicates whether there are more files in the list.
461         * @return true if there are more items
462         */
463        public boolean hasNext() {
464                if (done) {
465                        return false;
466                } else {
467                        while (!files.hasNext() && directories.hasNext()) {
468                                files = new LocalIterator(directories.next());
469                        }
470                        if (files.hasNext()) { return true; }
471                        if (directories.hasNext()) { return true; }
472                        return false;
473                }
474        }
475        /**
476         * Returns next file in list.
477         * @return next file
478         */
479        public File next() {
480                if (done) {
481                        throw new NoSuchElementException();
482                }
483                while (!files.hasNext() && directories.hasNext()) {
484                        files = new LocalIterator(directories.next());
485                }
486                if (!files.hasNext() && !directories.hasNext()) {
487                        done = true;
488                        throw new NoSuchElementException();
489                }
490                return files.next();
491        }
492        /**
493         * This is an optional method that is not implemented for this class.
494         * 
495         */
496        public void remove() {
497                throw new UnsupportedOperationException("remove not valid for this class");
498        }
499        /**
500         * Test program for Level class.
501         * @param value root directory
502         * @throws IOException if io problems
503         */
504        public void test1(File value) throws IOException {
505                Level test = new Level(value);
506                while (test.hasNext()) {
507                        File item = test.next();
508                        System.out.println(item.getAbsolutePath() + " " + 
509                                        Integer.toString(test.getCurrentPosition()));
510                }
511        }
512        /**
513         * Test iterator for DirWalker class.
514         * @param value root of directory structure
515         * @return iterator
516         * @throws IOException if io problems
517         */
518        public Iterator<File> test2(File value) throws IOException {
519                return new DirIterator(value);
520        }
521        /**
522         * Test driver.
523         * @param args - not used
524         */
525        public static void main(String[] args) {
526                try {
527                        File root = new File("/");
528                        DirWalker instance = new DirWalker(root);
529                        instance.test1(new File("/Users/bradleyross/AmtrakDesktop"));
530                        System.out.println("Starting second test");
531                        Iterator<File> second = instance.test2(new File("/Users/bradleyross/AmtrakDesktop"));
532                        while (second.hasNext()) {
533                                File item = second.next();
534                                System.out.println(item.getAbsolutePath());
535                        }
536                } catch (Exception e)  {
537                        System.out.println(e.getClass().getName() + " " + e.getMessage());
538                        e.printStackTrace();
539                }
540                System.out.println("Program complete");
541        }
542}