Path: blob/master/src/java.base/share/classes/java/nio/file/FileTreeWalker.java
41159 views
/*1* Copyright (c) 2007, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package java.nio.file;2627import java.nio.file.attribute.BasicFileAttributes;28import java.io.Closeable;29import java.io.IOException;30import java.util.ArrayDeque;31import java.util.Collection;32import java.util.Iterator;33import sun.nio.fs.BasicFileAttributesHolder;3435/**36* Walks a file tree, generating a sequence of events corresponding to the files37* in the tree.38*39* <pre>{@code40* Path top = ...41* Set<FileVisitOption> options = ...42* int maxDepth = ...43*44* try (FileTreeWalker walker = new FileTreeWalker(options, maxDepth)) {45* FileTreeWalker.Event ev = walker.walk(top);46* do {47* process(ev);48* ev = walker.next();49* } while (ev != null);50* }51* }</pre>52*53* @see Files#walkFileTree54*/5556class FileTreeWalker implements Closeable {57private final boolean followLinks;58private final LinkOption[] linkOptions;59private final int maxDepth;60private final ArrayDeque<DirectoryNode> stack = new ArrayDeque<>();61private boolean closed;6263/**64* The element on the walking stack corresponding to a directory node.65*/66private static class DirectoryNode {67private final Path dir;68private final Object key;69private final DirectoryStream<Path> stream;70private final Iterator<Path> iterator;71private boolean skipped;7273DirectoryNode(Path dir, Object key, DirectoryStream<Path> stream) {74this.dir = dir;75this.key = key;76this.stream = stream;77this.iterator = stream.iterator();78}7980Path directory() {81return dir;82}8384Object key() {85return key;86}8788DirectoryStream<Path> stream() {89return stream;90}9192Iterator<Path> iterator() {93return iterator;94}9596void skip() {97skipped = true;98}99100boolean skipped() {101return skipped;102}103}104105/**106* The event types.107*/108static enum EventType {109/**110* Start of a directory111*/112START_DIRECTORY,113/**114* End of a directory115*/116END_DIRECTORY,117/**118* An entry in a directory119*/120ENTRY;121}122123/**124* Events returned by the {@link #walk} and {@link #next} methods.125*/126static class Event {127private final EventType type;128private final Path file;129private final BasicFileAttributes attrs;130private final IOException ioe;131132private Event(EventType type, Path file, BasicFileAttributes attrs, IOException ioe) {133this.type = type;134this.file = file;135this.attrs = attrs;136this.ioe = ioe;137}138139Event(EventType type, Path file, BasicFileAttributes attrs) {140this(type, file, attrs, null);141}142143Event(EventType type, Path file, IOException ioe) {144this(type, file, null, ioe);145}146147EventType type() {148return type;149}150151Path file() {152return file;153}154155BasicFileAttributes attributes() {156return attrs;157}158159IOException ioeException() {160return ioe;161}162}163164/**165* Creates a {@code FileTreeWalker}.166*167* @throws IllegalArgumentException168* if {@code maxDepth} is negative169* @throws ClassCastException170* if {@code options} contains an element that is not a171* {@code FileVisitOption}172* @throws NullPointerException173* if {@code options} is {@code null} or the options174* array contains a {@code null} element175*/176FileTreeWalker(Collection<FileVisitOption> options, int maxDepth) {177boolean fl = false;178for (FileVisitOption option: options) {179// will throw NPE if options contains null180switch (option) {181case FOLLOW_LINKS : fl = true; break;182default:183throw new AssertionError("Should not get here");184}185}186if (maxDepth < 0)187throw new IllegalArgumentException("'maxDepth' is negative");188189this.followLinks = fl;190this.linkOptions = (fl) ? new LinkOption[0] :191new LinkOption[] { LinkOption.NOFOLLOW_LINKS };192this.maxDepth = maxDepth;193}194195/**196* Returns the attributes of the given file, taking into account whether197* the walk is following sym links is not. The {@code canUseCached}198* argument determines whether this method can use cached attributes.199*/200@SuppressWarnings("removal")201private BasicFileAttributes getAttributes(Path file, boolean canUseCached)202throws IOException203{204// if attributes are cached then use them if possible205if (canUseCached &&206(file instanceof BasicFileAttributesHolder) &&207(System.getSecurityManager() == null))208{209BasicFileAttributes cached = ((BasicFileAttributesHolder)file).get();210if (cached != null && (!followLinks || !cached.isSymbolicLink())) {211return cached;212}213}214215// attempt to get attributes of file. If fails and we are following216// links then a link target might not exist so get attributes of link217BasicFileAttributes attrs;218try {219attrs = Files.readAttributes(file, BasicFileAttributes.class, linkOptions);220} catch (IOException ioe) {221if (!followLinks)222throw ioe;223224// attempt to get attrmptes without following links225attrs = Files.readAttributes(file,226BasicFileAttributes.class,227LinkOption.NOFOLLOW_LINKS);228}229return attrs;230}231232/**233* Returns true if walking into the given directory would result in a234* file system loop/cycle.235*/236private boolean wouldLoop(Path dir, Object key) {237// if this directory and ancestor has a file key then we compare238// them; otherwise we use less efficient isSameFile test.239for (DirectoryNode ancestor: stack) {240Object ancestorKey = ancestor.key();241if (key != null && ancestorKey != null) {242if (key.equals(ancestorKey)) {243// cycle detected244return true;245}246} else {247try {248if (Files.isSameFile(dir, ancestor.directory())) {249// cycle detected250return true;251}252} catch (IOException | SecurityException x) {253// ignore254}255}256}257return false;258}259260/**261* Visits the given file, returning the {@code Event} corresponding to that262* visit.263*264* The {@code ignoreSecurityException} parameter determines whether265* any SecurityException should be ignored or not. If a SecurityException266* is thrown, and is ignored, then this method returns {@code null} to267* mean that there is no event corresponding to a visit to the file.268*269* The {@code canUseCached} parameter determines whether cached attributes270* for the file can be used or not.271*/272private Event visit(Path entry, boolean ignoreSecurityException, boolean canUseCached) {273// need the file attributes274BasicFileAttributes attrs;275try {276attrs = getAttributes(entry, canUseCached);277} catch (IOException ioe) {278return new Event(EventType.ENTRY, entry, ioe);279} catch (SecurityException se) {280if (ignoreSecurityException)281return null;282throw se;283}284285// at maximum depth or file is not a directory286int depth = stack.size();287if (depth >= maxDepth || !attrs.isDirectory()) {288return new Event(EventType.ENTRY, entry, attrs);289}290291// check for cycles when following links292if (followLinks && wouldLoop(entry, attrs.fileKey())) {293return new Event(EventType.ENTRY, entry,294new FileSystemLoopException(entry.toString()));295}296297// file is a directory, attempt to open it298DirectoryStream<Path> stream = null;299try {300stream = Files.newDirectoryStream(entry);301} catch (IOException ioe) {302return new Event(EventType.ENTRY, entry, ioe);303} catch (SecurityException se) {304if (ignoreSecurityException)305return null;306throw se;307}308309// push a directory node to the stack and return an event310stack.push(new DirectoryNode(entry, attrs.fileKey(), stream));311return new Event(EventType.START_DIRECTORY, entry, attrs);312}313314315/**316* Start walking from the given file.317*/318Event walk(Path file) {319if (closed)320throw new IllegalStateException("Closed");321322Event ev = visit(file,323false, // ignoreSecurityException324false); // canUseCached325assert ev != null;326return ev;327}328329/**330* Returns the next Event or {@code null} if there are no more events or331* the walker is closed.332*/333Event next() {334DirectoryNode top = stack.peek();335if (top == null)336return null; // stack is empty, we are done337338// continue iteration of the directory at the top of the stack339Event ev;340do {341Path entry = null;342IOException ioe = null;343344// get next entry in the directory345if (!top.skipped()) {346Iterator<Path> iterator = top.iterator();347try {348if (iterator.hasNext()) {349entry = iterator.next();350}351} catch (DirectoryIteratorException x) {352ioe = x.getCause();353}354}355356// no next entry so close and pop directory,357// creating corresponding event358if (entry == null) {359try {360top.stream().close();361} catch (IOException e) {362if (ioe == null) {363ioe = e;364} else {365ioe.addSuppressed(e);366}367}368stack.pop();369return new Event(EventType.END_DIRECTORY, top.directory(), ioe);370}371372// visit the entry373ev = visit(entry,374true, // ignoreSecurityException375true); // canUseCached376377} while (ev == null);378379return ev;380}381382/**383* Pops the directory node that is the current top of the stack so that384* there are no more events for the directory (including no END_DIRECTORY)385* event. This method is a no-op if the stack is empty or the walker is386* closed.387*/388void pop() {389if (!stack.isEmpty()) {390DirectoryNode node = stack.pop();391try {392node.stream().close();393} catch (IOException ignore) { }394}395}396397/**398* Skips the remaining entries in the directory at the top of the stack.399* This method is a no-op if the stack is empty or the walker is closed.400*/401void skipRemainingSiblings() {402if (!stack.isEmpty()) {403stack.peek().skip();404}405}406407/**408* Returns {@code true} if the walker is open.409*/410boolean isOpen() {411return !closed;412}413414/**415* Closes/pops all directories on the stack.416*/417@Override418public void close() {419if (!closed) {420while (!stack.isEmpty()) {421pop();422}423closed = true;424}425}426}427428429