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}