Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/java/nio/file/FileTreeWalker.java
38918 views
/*1* Copyright (c) 2007, 2013, 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 {@ocde 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*/200private BasicFileAttributes getAttributes(Path file, boolean canUseCached)201throws IOException202{203// if attributes are cached then use them if possible204if (canUseCached &&205(file instanceof BasicFileAttributesHolder) &&206(System.getSecurityManager() == null))207{208BasicFileAttributes cached = ((BasicFileAttributesHolder)file).get();209if (cached != null && (!followLinks || !cached.isSymbolicLink())) {210return cached;211}212}213214// attempt to get attributes of file. If fails and we are following215// links then a link target might not exist so get attributes of link216BasicFileAttributes attrs;217try {218attrs = Files.readAttributes(file, BasicFileAttributes.class, linkOptions);219} catch (IOException ioe) {220if (!followLinks)221throw ioe;222223// attempt to get attrmptes without following links224attrs = Files.readAttributes(file,225BasicFileAttributes.class,226LinkOption.NOFOLLOW_LINKS);227}228return attrs;229}230231/**232* Returns true if walking into the given directory would result in a233* file system loop/cycle.234*/235private boolean wouldLoop(Path dir, Object key) {236// if this directory and ancestor has a file key then we compare237// them; otherwise we use less efficient isSameFile test.238for (DirectoryNode ancestor: stack) {239Object ancestorKey = ancestor.key();240if (key != null && ancestorKey != null) {241if (key.equals(ancestorKey)) {242// cycle detected243return true;244}245} else {246try {247if (Files.isSameFile(dir, ancestor.directory())) {248// cycle detected249return true;250}251} catch (IOException | SecurityException x) {252// ignore253}254}255}256return false;257}258259/**260* Visits the given file, returning the {@code Event} corresponding to that261* visit.262*263* The {@code ignoreSecurityException} parameter determines whether264* any SecurityException should be ignored or not. If a SecurityException265* is thrown, and is ignored, then this method returns {@code null} to266* mean that there is no event corresponding to a visit to the file.267*268* The {@code canUseCached} parameter determines whether cached attributes269* for the file can be used or not.270*/271private Event visit(Path entry, boolean ignoreSecurityException, boolean canUseCached) {272// need the file attributes273BasicFileAttributes attrs;274try {275attrs = getAttributes(entry, canUseCached);276} catch (IOException ioe) {277return new Event(EventType.ENTRY, entry, ioe);278} catch (SecurityException se) {279if (ignoreSecurityException)280return null;281throw se;282}283284// at maximum depth or file is not a directory285int depth = stack.size();286if (depth >= maxDepth || !attrs.isDirectory()) {287return new Event(EventType.ENTRY, entry, attrs);288}289290// check for cycles when following links291if (followLinks && wouldLoop(entry, attrs.fileKey())) {292return new Event(EventType.ENTRY, entry,293new FileSystemLoopException(entry.toString()));294}295296// file is a directory, attempt to open it297DirectoryStream<Path> stream = null;298try {299stream = Files.newDirectoryStream(entry);300} catch (IOException ioe) {301return new Event(EventType.ENTRY, entry, ioe);302} catch (SecurityException se) {303if (ignoreSecurityException)304return null;305throw se;306}307308// push a directory node to the stack and return an event309stack.push(new DirectoryNode(entry, attrs.fileKey(), stream));310return new Event(EventType.START_DIRECTORY, entry, attrs);311}312313314/**315* Start walking from the given file.316*/317Event walk(Path file) {318if (closed)319throw new IllegalStateException("Closed");320321Event ev = visit(file,322false, // ignoreSecurityException323false); // canUseCached324assert ev != null;325return ev;326}327328/**329* Returns the next Event or {@code null} if there are no more events or330* the walker is closed.331*/332Event next() {333DirectoryNode top = stack.peek();334if (top == null)335return null; // stack is empty, we are done336337// continue iteration of the directory at the top of the stack338Event ev;339do {340Path entry = null;341IOException ioe = null;342343// get next entry in the directory344if (!top.skipped()) {345Iterator<Path> iterator = top.iterator();346try {347if (iterator.hasNext()) {348entry = iterator.next();349}350} catch (DirectoryIteratorException x) {351ioe = x.getCause();352}353}354355// no next entry so close and pop directory, creating corresponding event356if (entry == null) {357try {358top.stream().close();359} catch (IOException e) {360if (ioe != null) {361ioe = e;362} else {363ioe.addSuppressed(e);364}365}366stack.pop();367return new Event(EventType.END_DIRECTORY, top.directory(), ioe);368}369370// visit the entry371ev = visit(entry,372true, // ignoreSecurityException373true); // canUseCached374375} while (ev == null);376377return ev;378}379380/**381* Pops the directory node that is the current top of the stack so that382* there are no more events for the directory (including no END_DIRECTORY)383* event. This method is a no-op if the stack is empty or the walker is384* closed.385*/386void pop() {387if (!stack.isEmpty()) {388DirectoryNode node = stack.pop();389try {390node.stream().close();391} catch (IOException ignore) { }392}393}394395/**396* Skips the remaining entries in the directory at the top of the stack.397* This method is a no-op if the stack is empty or the walker is closed.398*/399void skipRemainingSiblings() {400if (!stack.isEmpty()) {401stack.peek().skip();402}403}404405/**406* Returns {@code true} if the walker is open.407*/408boolean isOpen() {409return !closed;410}411412/**413* Closes/pops all directories on the stack.414*/415@Override416public void close() {417if (!closed) {418while (!stack.isEmpty()) {419pop();420}421closed = true;422}423}424}425426427