1 /* 2 * Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.nio.file; 27 28 import java.nio.file.attribute.BasicFileAttributes; 29 import java.io.Closeable; 30 import java.io.IOException; 31 import java.util.ArrayDeque; 32 import java.util.Iterator; 33 import java.util.Set; 34 import sun.nio.fs.BasicFileAttributesHolder; 35 36 /** 37 * Walks a file tree, generating a sequence of events corresponding to the files 38 * in the tree. 39 * 40 * <pre>{@code 41 * Path top = ... 42 * Set<FileVisitOption> options = ... 43 * int maxDepth = ... 44 * 45 * try (FileTreeWalker walker = new FileTreeWalker(options, maxDepth)) { 46 * FileTreeWalker.Event ev = walker.walk(top); 47 * do { 48 * process(ev); 49 * ev = walker.next(); 50 * } while (ev != null); 51 * } 52 * }</pre> 53 * 54 * @see Files#walkFileTree 55 */ 56 57 class FileTreeWalker implements Closeable { 58 private final boolean followLinks; 59 private final LinkOption[] linkOptions; 60 private final int maxDepth; 61 private final ArrayDeque<DirectoryNode> stack = new ArrayDeque<>(); 62 private boolean closed; 63 64 /** 65 * The element on the walking stack corresponding to a directory node. 66 */ 67 private static class DirectoryNode { 68 private final Path dir; 69 private final Object key; 70 private final DirectoryStream<Path> stream; 71 private final Iterator<Path> iterator; 72 private boolean skipped; 73 74 DirectoryNode(Path dir, Object key, DirectoryStream<Path> stream) { 75 this.dir = dir; 76 this.key = key; 77 this.stream = stream; 78 this.iterator = stream.iterator(); 79 } 80 81 Path directory() { 82 return dir; 83 } 84 85 Object key() { 86 return key; 87 } 88 89 DirectoryStream<Path> stream() { 90 return stream; 91 } 92 93 Iterator<Path> iterator() { 94 return iterator; 95 } 96 97 void skip() { 98 skipped = true; 99 } 100 101 boolean skipped() { 102 return skipped; 103 } 104 } 105 106 /** 107 * The event types. 108 */ 109 static enum EventType { 110 /** 111 * Start of a directory 112 */ 113 START_DIRECTORY, 114 /** 115 * End of a directory 116 */ 117 END_DIRECTORY, 118 /** 119 * An entry in a directory 120 */ 121 ENTRY; 122 } 123 124 /** 125 * Events returned by the {@link #walk} and {@link #next} methods. 126 */ 127 static class Event { 128 private final EventType type; 129 private final Path file; 130 private final BasicFileAttributes attrs; 131 private final IOException ioe; 132 133 private Event(EventType type, Path file, BasicFileAttributes attrs, IOException ioe) { 134 this.type = type; 135 this.file = file; 136 this.attrs = attrs; 137 this.ioe = ioe; 138 } 139 140 Event(EventType type, Path file, BasicFileAttributes attrs) { 141 this(type, file, attrs, null); 142 } 143 144 Event(EventType type, Path file, IOException ioe) { 145 this(type, file, null, ioe); 146 } 147 148 EventType type() { 149 return type; 150 } 151 152 Path file() { 153 return file; 154 } 155 156 BasicFileAttributes attributes() { 157 return attrs; 158 } 159 160 IOException ioeException() { 161 return ioe; 162 } 163 } 164 165 /** 166 * Creates a {@code FileTreeWalker}. 167 */ 168 FileTreeWalker(Set<FileVisitOption> options, int maxDepth) { 169 boolean fl = false; 170 for (FileVisitOption option: options) { 171 // will throw NPE if options contains null 172 switch (option) { 173 case FOLLOW_LINKS : fl = true; break; 174 default: 175 throw new AssertionError("Should not get here"); 176 } 177 } 178 this.followLinks = fl; 179 this.linkOptions = (fl) ? new LinkOption[0] : 180 new LinkOption[] { LinkOption.NOFOLLOW_LINKS }; 181 this.maxDepth = maxDepth; 182 } 183 184 /** 185 * Returns the attributes of the given file, taking into account whether 186 * the walk is following sym links is not. The {@code canUseCached} 187 * argument determines whether this method can use cached attributes. 188 */ 189 private BasicFileAttributes getAttributes(Path file, boolean canUseCached) 190 throws IOException 191 { 192 // if attributes are cached then use them if possible 193 if (canUseCached && 194 (file instanceof BasicFileAttributesHolder) && 195 (System.getSecurityManager() == null)) 196 { 197 BasicFileAttributes cached = ((BasicFileAttributesHolder)file).get(); 198 if (cached != null && (!followLinks || !cached.isSymbolicLink())) { 199 return cached; 200 } 201 } 202 203 // attempt to get attributes of file. If fails and we are following 204 // links then a link target might not exist so get attributes of link 205 BasicFileAttributes attrs; 206 try { 207 attrs = Files.readAttributes(file, BasicFileAttributes.class, linkOptions); 208 } catch (IOException ioe) { 209 if (!followLinks) 210 throw ioe; 211 212 // attempt to get attrmptes without following links 213 attrs = Files.readAttributes(file, 214 BasicFileAttributes.class, 215 LinkOption.NOFOLLOW_LINKS); 216 } 217 return attrs; 218 } 219 220 /** 221 * Returns true if walking into the given directory would result in a 222 * file system loop/cycle. 223 */ 224 private boolean wouldLoop(Path dir, Object key) { 225 // if this directory and ancestor has a file key then we compare 226 // them; otherwise we use less efficient isSameFile test. 227 for (DirectoryNode ancestor: stack) { 228 Object ancestorKey = ancestor.key(); 229 if (key != null && ancestorKey != null) { 230 if (key.equals(ancestorKey)) { 231 // cycle detected 232 return true; 233 } 234 } else { 235 try { 236 if (Files.isSameFile(dir, ancestor.directory())) { 237 // cycle detected 238 return true; 239 } 240 } catch (IOException | SecurityException x) { 241 // ignore 242 } 243 } 244 } 245 return false; 246 } 247 248 /** 249 * Visits the given file, returning the {@code Event} corresponding to that 250 * visit. 251 * 252 * The {@code ignoreSecurityException} parameter determines whether 253 * any SecurityException should be ignored or not. If a SecurityException 254 * is thrown, and is ignored, then this method returns {@code null} to 255 * mean that there is no event corresponding to a visit to the file. 256 * 257 * The {@code canUseCached} parameter determines whether cached attributes 258 * for the file can be used or not. 259 */ 260 private Event visit(Path entry, boolean ignoreSecurityException, boolean canUseCached) { 261 // need the file attributes 262 BasicFileAttributes attrs; 263 try { 264 attrs = getAttributes(entry, canUseCached); 265 } catch (IOException ioe) { 266 return new Event(EventType.ENTRY, entry, ioe); 267 } catch (SecurityException se) { 268 if (ignoreSecurityException) 269 return null; 270 throw se; 271 } 272 273 // at maximum depth or file is not a directory 274 int depth = stack.size(); 275 if (depth >= maxDepth || !attrs.isDirectory()) { 276 return new Event(EventType.ENTRY, entry, attrs); 277 } 278 279 // check for cycles when following links 280 if (followLinks && wouldLoop(entry, attrs.fileKey())) { 281 return new Event(EventType.ENTRY, entry, 282 new FileSystemLoopException(entry.toString())); 283 } 284 285 // file is a directory, attempt to open it 286 DirectoryStream<Path> stream = null; 287 try { 288 stream = Files.newDirectoryStream(entry); 289 } catch (IOException ioe) { 290 return new Event(EventType.ENTRY, entry, ioe); 291 } catch (SecurityException se) { 292 if (ignoreSecurityException) 293 return null; 294 throw se; 295 } 296 297 // push a directory node to the stack and return an event 298 stack.push(new DirectoryNode(entry, attrs.fileKey(), stream)); 299 return new Event(EventType.START_DIRECTORY, entry, attrs); 300 } 301 302 303 /** 304 * Start walking from the given file. 305 */ 306 Event walk(Path file) { 307 if (closed) 308 throw new IllegalStateException("Closed"); 309 310 Event ev = visit(file, 311 false, // ignoreSecurityException 312 false); // canUseCached 313 assert ev != null; 314 return ev; 315 } 316 317 /** 318 * Returns the next Event or {@code null} if there are no more events or 319 * the walker is closed. 320 */ 321 Event next() { 322 DirectoryNode top = stack.peek(); 323 if (top == null) 324 return null; // stack is empty, we are done 325 326 // continue iteration of the directory at the top of the stack 327 Event ev; 328 do { 329 Path entry = null; 330 IOException ioe = null; 331 332 // get next entry in the directory 333 if (!top.skipped()) { 334 Iterator<Path> iterator = top.iterator(); 335 try { 336 if (iterator.hasNext()) { 337 entry = iterator.next(); 338 } 339 } catch (DirectoryIteratorException x) { 340 ioe = x.getCause(); 341 } 342 } 343 344 // no next entry so close and pop directory, creating corresponding event 345 if (entry == null) { 346 try { 347 top.stream().close(); 348 } catch (IOException e) { 349 if (ioe != null) { 350 ioe = e; 351 } else { 352 ioe.addSuppressed(e); 353 } 354 } 355 stack.pop(); 356 return new Event(EventType.END_DIRECTORY, top.directory(), ioe); 357 } 358 359 // visit the entry 360 ev = visit(entry, 361 true, // ignoreSecurityException 362 true); // canUseCached 363 364 } while (ev == null); 365 366 return ev; 367 } 368 369 /** 370 * Pops the directory node that is the current top of the stack so that 371 * there are no more events for the directory (including no END_DIRECTORY) 372 * event. This method is a no-op if the stack is empty or the walker is 373 * closed. 374 */ 375 void pop() { 376 if (!stack.isEmpty()) { 377 DirectoryNode node = stack.pop(); 378 try { 379 node.stream().close(); 380 } catch (IOException ignore) { } 381 } 382 } 383 384 /** 385 * Skips the remaining entries in the directory at the top of the stack. 386 * This method is a no-op if the stack is empty or the walker is closed. 387 */ 388 void skipRemainingSiblings() { 389 if (!stack.isEmpty()) { 390 stack.peek().skip(); 391 } 392 } 393 394 /** 395 * Returns {@code true} if the walker is open. 396 */ 397 boolean isOpen() { 398 return !closed; 399 } 400 401 /** 402 * Closes/pops all directories on the stack. 403 */ 404 @Override 405 public void close() { 406 if (!closed) { 407 while (!stack.isEmpty()) { 408 pop(); 409 } 410 closed = true; 411 } 412 } 413 }