1 /* 2 * Copyright (c) 2007, 2013, 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.Collection; 33 import java.util.Iterator; 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 DirectoryNode(Path dir, Object key, DirectoryStream<Path> stream)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 directory()81 Path directory() { 82 return dir; 83 } 84 key()85 Object key() { 86 return key; 87 } 88 stream()89 DirectoryStream<Path> stream() { 90 return stream; 91 } 92 iterator()93 Iterator<Path> iterator() { 94 return iterator; 95 } 96 skip()97 void skip() { 98 skipped = true; 99 } 100 skipped()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 Event(EventType type, Path file, BasicFileAttributes attrs, IOException ioe)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 Event(EventType type, Path file, BasicFileAttributes attrs)140 Event(EventType type, Path file, BasicFileAttributes attrs) { 141 this(type, file, attrs, null); 142 } 143 Event(EventType type, Path file, IOException ioe)144 Event(EventType type, Path file, IOException ioe) { 145 this(type, file, null, ioe); 146 } 147 type()148 EventType type() { 149 return type; 150 } 151 file()152 Path file() { 153 return file; 154 } 155 attributes()156 BasicFileAttributes attributes() { 157 return attrs; 158 } 159 ioeException()160 IOException ioeException() { 161 return ioe; 162 } 163 } 164 165 /** 166 * Creates a {@code FileTreeWalker}. 167 * 168 * @throws IllegalArgumentException 169 * if {@code maxDepth} is negative 170 * @throws ClassCastException 171 * if (@code options} contains an element that is not a 172 * {@code FileVisitOption} 173 * @throws NullPointerException 174 * if {@code options} is {@ocde null} or the options 175 * array contains a {@code null} element 176 */ FileTreeWalker(Collection<FileVisitOption> options, int maxDepth)177 FileTreeWalker(Collection<FileVisitOption> options, int maxDepth) { 178 boolean fl = false; 179 for (FileVisitOption option: options) { 180 // will throw NPE if options contains null 181 switch (option) { 182 case FOLLOW_LINKS : fl = true; break; 183 default: 184 throw new AssertionError("Should not get here"); 185 } 186 } 187 if (maxDepth < 0) 188 throw new IllegalArgumentException("'maxDepth' is negative"); 189 190 this.followLinks = fl; 191 this.linkOptions = (fl) ? new LinkOption[0] : 192 new LinkOption[] { LinkOption.NOFOLLOW_LINKS }; 193 this.maxDepth = maxDepth; 194 } 195 196 /** 197 * Returns the attributes of the given file, taking into account whether 198 * the walk is following sym links is not. The {@code canUseCached} 199 * argument determines whether this method can use cached attributes. 200 */ getAttributes(Path file, boolean canUseCached)201 private BasicFileAttributes getAttributes(Path file, boolean canUseCached) 202 throws IOException 203 { 204 // if attributes are cached then use them if possible 205 if (canUseCached && 206 (file instanceof BasicFileAttributesHolder) && 207 (System.getSecurityManager() == null)) 208 { 209 BasicFileAttributes cached = ((BasicFileAttributesHolder)file).get(); 210 if (cached != null && (!followLinks || !cached.isSymbolicLink())) { 211 return cached; 212 } 213 } 214 215 // attempt to get attributes of file. If fails and we are following 216 // links then a link target might not exist so get attributes of link 217 BasicFileAttributes attrs; 218 try { 219 attrs = Files.readAttributes(file, BasicFileAttributes.class, linkOptions); 220 } catch (IOException ioe) { 221 if (!followLinks) 222 throw ioe; 223 224 // attempt to get attrmptes without following links 225 attrs = Files.readAttributes(file, 226 BasicFileAttributes.class, 227 LinkOption.NOFOLLOW_LINKS); 228 } 229 return attrs; 230 } 231 232 /** 233 * Returns true if walking into the given directory would result in a 234 * file system loop/cycle. 235 */ wouldLoop(Path dir, Object key)236 private boolean wouldLoop(Path dir, Object key) { 237 // if this directory and ancestor has a file key then we compare 238 // them; otherwise we use less efficient isSameFile test. 239 for (DirectoryNode ancestor: stack) { 240 Object ancestorKey = ancestor.key(); 241 if (key != null && ancestorKey != null) { 242 if (key.equals(ancestorKey)) { 243 // cycle detected 244 return true; 245 } 246 } else { 247 try { 248 if (Files.isSameFile(dir, ancestor.directory())) { 249 // cycle detected 250 return true; 251 } 252 } catch (IOException | SecurityException x) { 253 // ignore 254 } 255 } 256 } 257 return false; 258 } 259 260 /** 261 * Visits the given file, returning the {@code Event} corresponding to that 262 * visit. 263 * 264 * The {@code ignoreSecurityException} parameter determines whether 265 * any SecurityException should be ignored or not. If a SecurityException 266 * is thrown, and is ignored, then this method returns {@code null} to 267 * mean that there is no event corresponding to a visit to the file. 268 * 269 * The {@code canUseCached} parameter determines whether cached attributes 270 * for the file can be used or not. 271 */ visit(Path entry, boolean ignoreSecurityException, boolean canUseCached)272 private Event visit(Path entry, boolean ignoreSecurityException, boolean canUseCached) { 273 // need the file attributes 274 BasicFileAttributes attrs; 275 try { 276 attrs = getAttributes(entry, canUseCached); 277 } catch (IOException ioe) { 278 return new Event(EventType.ENTRY, entry, ioe); 279 } catch (SecurityException se) { 280 if (ignoreSecurityException) 281 return null; 282 throw se; 283 } 284 285 // at maximum depth or file is not a directory 286 int depth = stack.size(); 287 if (depth >= maxDepth || !attrs.isDirectory()) { 288 return new Event(EventType.ENTRY, entry, attrs); 289 } 290 291 // check for cycles when following links 292 if (followLinks && wouldLoop(entry, attrs.fileKey())) { 293 return new Event(EventType.ENTRY, entry, 294 new FileSystemLoopException(entry.toString())); 295 } 296 297 // file is a directory, attempt to open it 298 DirectoryStream<Path> stream = null; 299 try { 300 stream = Files.newDirectoryStream(entry); 301 } catch (IOException ioe) { 302 return new Event(EventType.ENTRY, entry, ioe); 303 } catch (SecurityException se) { 304 if (ignoreSecurityException) 305 return null; 306 throw se; 307 } 308 309 // push a directory node to the stack and return an event 310 stack.push(new DirectoryNode(entry, attrs.fileKey(), stream)); 311 return new Event(EventType.START_DIRECTORY, entry, attrs); 312 } 313 314 315 /** 316 * Start walking from the given file. 317 */ walk(Path file)318 Event walk(Path file) { 319 if (closed) 320 throw new IllegalStateException("Closed"); 321 322 Event ev = visit(file, 323 false, // ignoreSecurityException 324 false); // canUseCached 325 assert ev != null; 326 return ev; 327 } 328 329 /** 330 * Returns the next Event or {@code null} if there are no more events or 331 * the walker is closed. 332 */ next()333 Event next() { 334 DirectoryNode top = stack.peek(); 335 if (top == null) 336 return null; // stack is empty, we are done 337 338 // continue iteration of the directory at the top of the stack 339 Event ev; 340 do { 341 Path entry = null; 342 IOException ioe = null; 343 344 // get next entry in the directory 345 if (!top.skipped()) { 346 Iterator<Path> iterator = top.iterator(); 347 try { 348 if (iterator.hasNext()) { 349 entry = iterator.next(); 350 } 351 } catch (DirectoryIteratorException x) { 352 ioe = x.getCause(); 353 } 354 } 355 356 // no next entry so close and pop directory, creating corresponding event 357 if (entry == null) { 358 try { 359 top.stream().close(); 360 } catch (IOException e) { 361 if (ioe != null) { 362 ioe = e; 363 } else { 364 ioe.addSuppressed(e); 365 } 366 } 367 stack.pop(); 368 return new Event(EventType.END_DIRECTORY, top.directory(), ioe); 369 } 370 371 // visit the entry 372 ev = visit(entry, 373 true, // ignoreSecurityException 374 true); // canUseCached 375 376 } while (ev == null); 377 378 return ev; 379 } 380 381 /** 382 * Pops the directory node that is the current top of the stack so that 383 * there are no more events for the directory (including no END_DIRECTORY) 384 * event. This method is a no-op if the stack is empty or the walker is 385 * closed. 386 */ pop()387 void pop() { 388 if (!stack.isEmpty()) { 389 DirectoryNode node = stack.pop(); 390 try { 391 node.stream().close(); 392 } catch (IOException ignore) { } 393 } 394 } 395 396 /** 397 * Skips the remaining entries in the directory at the top of the stack. 398 * This method is a no-op if the stack is empty or the walker is closed. 399 */ skipRemainingSiblings()400 void skipRemainingSiblings() { 401 if (!stack.isEmpty()) { 402 stack.peek().skip(); 403 } 404 } 405 406 /** 407 * Returns {@code true} if the walker is open. 408 */ isOpen()409 boolean isOpen() { 410 return !closed; 411 } 412 413 /** 414 * Closes/pops all directories on the stack. 415 */ 416 @Override close()417 public void close() { 418 if (!closed) { 419 while (!stack.isEmpty()) { 420 pop(); 421 } 422 closed = true; 423 } 424 } 425 } 426