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}