2009-02-15 12:25:54 +00:00
|
|
|
/*
|
2010-05-25 15:58:33 -07:00
|
|
|
* Copyright (c) 2007, 2009, Oracle and/or its affiliates. All rights reserved.
|
2009-02-15 12:25:54 +00:00
|
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
|
|
*
|
|
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
|
|
* under the terms of the GNU General Public License version 2 only, as
|
2010-05-25 15:58:33 -07:00
|
|
|
* published by the Free Software Foundation. Oracle designates this
|
2009-02-15 12:25:54 +00:00
|
|
|
* particular file as subject to the "Classpath" exception as provided
|
2010-05-25 15:58:33 -07:00
|
|
|
* by Oracle in the LICENSE file that accompanied this code.
|
2009-02-15 12:25:54 +00:00
|
|
|
*
|
|
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
|
|
* accompanied this code).
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License version
|
|
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
*
|
2010-05-25 15:58:33 -07:00
|
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
|
|
* questions.
|
2009-02-15 12:25:54 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
package java.nio.file;
|
|
|
|
|
|
|
|
import java.nio.file.attribute.*;
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.util.*;
|
2009-02-24 09:11:42 +00:00
|
|
|
import sun.nio.fs.BasicFileAttributesHolder;
|
2009-02-15 12:25:54 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Simple file tree walker that works in a similar manner to nftw(3C).
|
|
|
|
*
|
|
|
|
* @see Files#walkFileTree
|
|
|
|
*/
|
|
|
|
|
|
|
|
class FileTreeWalker {
|
|
|
|
private final boolean followLinks;
|
|
|
|
private final boolean detectCycles;
|
|
|
|
private final LinkOption[] linkOptions;
|
|
|
|
private final FileVisitor<? super Path> visitor;
|
2009-09-14 17:47:26 +01:00
|
|
|
private final int maxDepth;
|
2009-02-15 12:25:54 +00:00
|
|
|
|
2009-09-14 17:47:26 +01:00
|
|
|
FileTreeWalker(Set<FileVisitOption> options,
|
|
|
|
FileVisitor<? super Path> visitor,
|
|
|
|
int maxDepth)
|
|
|
|
{
|
2009-02-15 12:25:54 +00:00
|
|
|
boolean fl = false;
|
|
|
|
boolean dc = false;
|
|
|
|
for (FileVisitOption option: options) {
|
|
|
|
switch (option) {
|
|
|
|
case FOLLOW_LINKS : fl = true; break;
|
|
|
|
case DETECT_CYCLES : dc = true; break;
|
|
|
|
default:
|
|
|
|
throw new AssertionError("Should not get here");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.followLinks = fl;
|
|
|
|
this.detectCycles = fl | dc;
|
|
|
|
this.linkOptions = (fl) ? new LinkOption[0] :
|
|
|
|
new LinkOption[] { LinkOption.NOFOLLOW_LINKS };
|
|
|
|
this.visitor = visitor;
|
2009-09-14 17:47:26 +01:00
|
|
|
this.maxDepth = maxDepth;
|
2009-02-15 12:25:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Walk file tree starting at the given file
|
|
|
|
*/
|
2009-09-14 17:47:26 +01:00
|
|
|
void walk(Path start) {
|
2009-02-15 12:25:54 +00:00
|
|
|
FileVisitResult result = walk(start,
|
2009-09-14 17:47:26 +01:00
|
|
|
0,
|
2009-02-15 12:25:54 +00:00
|
|
|
new ArrayList<AncestorDirectory>());
|
|
|
|
if (result == null) {
|
|
|
|
throw new NullPointerException("Visitor returned 'null'");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param file
|
2009-02-24 09:11:42 +00:00
|
|
|
* the directory to visit
|
2009-02-15 12:25:54 +00:00
|
|
|
* @param depth
|
2009-02-24 09:11:42 +00:00
|
|
|
* depth remaining
|
2009-02-15 12:25:54 +00:00
|
|
|
* @param ancestors
|
|
|
|
* use when cycle detection is enabled
|
|
|
|
*/
|
|
|
|
private FileVisitResult walk(Path file,
|
|
|
|
int depth,
|
|
|
|
List<AncestorDirectory> ancestors)
|
|
|
|
{
|
|
|
|
// depth check
|
2009-09-14 17:47:26 +01:00
|
|
|
if (depth > maxDepth)
|
2009-02-15 12:25:54 +00:00
|
|
|
return FileVisitResult.CONTINUE;
|
|
|
|
|
2009-02-24 09:11:42 +00:00
|
|
|
// if attributes are cached then use them if possible
|
2009-02-15 12:25:54 +00:00
|
|
|
BasicFileAttributes attrs = null;
|
2009-09-14 17:47:26 +01:00
|
|
|
if ((depth > 0) &&
|
|
|
|
(file instanceof BasicFileAttributesHolder) &&
|
|
|
|
(System.getSecurityManager() == null))
|
|
|
|
{
|
2009-02-24 09:11:42 +00:00
|
|
|
BasicFileAttributes cached = ((BasicFileAttributesHolder)file).get();
|
|
|
|
if (!followLinks || !cached.isSymbolicLink())
|
|
|
|
attrs = cached;
|
|
|
|
}
|
2009-02-15 12:25:54 +00:00
|
|
|
IOException exc = null;
|
|
|
|
|
|
|
|
// attempt to get attributes of file. If fails and we are following
|
|
|
|
// links then a link target might not exist so get attributes of link
|
2009-02-24 09:11:42 +00:00
|
|
|
if (attrs == null) {
|
2009-02-15 12:25:54 +00:00
|
|
|
try {
|
2009-02-24 09:11:42 +00:00
|
|
|
try {
|
|
|
|
attrs = Attributes.readBasicFileAttributes(file, linkOptions);
|
|
|
|
} catch (IOException x1) {
|
|
|
|
if (followLinks) {
|
|
|
|
try {
|
|
|
|
attrs = Attributes
|
|
|
|
.readBasicFileAttributes(file, LinkOption.NOFOLLOW_LINKS);
|
|
|
|
} catch (IOException x2) {
|
|
|
|
exc = x2;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
exc = x1;
|
2009-02-15 12:25:54 +00:00
|
|
|
}
|
|
|
|
}
|
2009-02-24 09:11:42 +00:00
|
|
|
} catch (SecurityException x) {
|
2009-09-14 17:47:26 +01:00
|
|
|
// If access to starting file is denied then SecurityException
|
|
|
|
// is thrown, otherwise the file is ignored.
|
|
|
|
if (depth == 0)
|
|
|
|
throw x;
|
2009-02-24 09:11:42 +00:00
|
|
|
return FileVisitResult.CONTINUE;
|
2009-02-15 12:25:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// unable to get attributes of file
|
|
|
|
if (exc != null) {
|
|
|
|
return visitor.visitFileFailed(file, exc);
|
|
|
|
}
|
|
|
|
|
|
|
|
// file is not a directory so invoke visitFile method
|
|
|
|
if (!attrs.isDirectory()) {
|
|
|
|
return visitor.visitFile(file, attrs);
|
|
|
|
}
|
|
|
|
|
|
|
|
// check for cycles
|
|
|
|
if (detectCycles) {
|
|
|
|
Object key = attrs.fileKey();
|
|
|
|
|
|
|
|
// if this directory and ancestor has a file key then we compare
|
|
|
|
// them; otherwise we use less efficient isSameFile test.
|
|
|
|
for (AncestorDirectory ancestor: ancestors) {
|
|
|
|
Object ancestorKey = ancestor.fileKey();
|
|
|
|
if (key != null && ancestorKey != null) {
|
|
|
|
if (key.equals(ancestorKey)) {
|
|
|
|
// cycle detected
|
|
|
|
return visitor.visitFile(file, attrs);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
try {
|
|
|
|
if (file.isSameFile(ancestor.file())) {
|
|
|
|
// cycle detected
|
|
|
|
return visitor.visitFile(file, attrs);
|
|
|
|
}
|
|
|
|
} catch (IOException x) {
|
|
|
|
// ignore
|
|
|
|
} catch (SecurityException x) {
|
|
|
|
// ignore
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ancestors.add(new AncestorDirectory(file, key));
|
|
|
|
}
|
|
|
|
|
|
|
|
// visit directory
|
|
|
|
try {
|
|
|
|
DirectoryStream<Path> stream = null;
|
|
|
|
FileVisitResult result;
|
|
|
|
|
|
|
|
// open the directory
|
|
|
|
try {
|
|
|
|
stream = file.newDirectoryStream();
|
|
|
|
} catch (IOException x) {
|
|
|
|
return visitor.preVisitDirectoryFailed(file, x);
|
|
|
|
} catch (SecurityException x) {
|
|
|
|
// ignore, as per spec
|
|
|
|
return FileVisitResult.CONTINUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
// the exception notified to the postVisitDirectory method
|
|
|
|
IOException ioe = null;
|
|
|
|
|
|
|
|
// invoke preVisitDirectory and then visit each entry
|
|
|
|
try {
|
|
|
|
result = visitor.preVisitDirectory(file);
|
|
|
|
if (result != FileVisitResult.CONTINUE) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if an I/O occurs during iteration then a CME is thrown. We
|
|
|
|
// need to distinguish this from a CME thrown by the visitor.
|
|
|
|
boolean inAction = false;
|
|
|
|
|
|
|
|
try {
|
|
|
|
for (Path entry: stream) {
|
|
|
|
inAction = true;
|
2009-09-14 17:47:26 +01:00
|
|
|
result = walk(entry, depth+1, ancestors);
|
2009-02-15 12:25:54 +00:00
|
|
|
inAction = false;
|
|
|
|
|
|
|
|
// returning null will cause NPE to be thrown
|
|
|
|
if (result == null || result == FileVisitResult.TERMINATE)
|
|
|
|
return result;
|
|
|
|
|
|
|
|
// skip remaining siblings in this directory
|
|
|
|
if (result == FileVisitResult.SKIP_SIBLINGS)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} catch (ConcurrentModificationException x) {
|
|
|
|
// if CME thrown because the iteration failed then remember
|
|
|
|
// the IOException so that it is notified to postVisitDirectory
|
|
|
|
if (!inAction) {
|
|
|
|
// iteration failed
|
|
|
|
Throwable t = x.getCause();
|
|
|
|
if (t instanceof IOException)
|
|
|
|
ioe = (IOException)t;
|
|
|
|
}
|
|
|
|
if (ioe == null)
|
|
|
|
throw x;
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
try {
|
|
|
|
stream.close();
|
|
|
|
} catch (IOException x) { }
|
|
|
|
}
|
|
|
|
|
|
|
|
// invoke postVisitDirectory last
|
|
|
|
return visitor.postVisitDirectory(file, ioe);
|
|
|
|
|
|
|
|
} finally {
|
|
|
|
// remove key from trail if doing cycle detection
|
|
|
|
if (detectCycles) {
|
|
|
|
ancestors.remove(ancestors.size()-1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static class AncestorDirectory {
|
2009-06-27 21:46:53 +01:00
|
|
|
private final Path dir;
|
2009-02-15 12:25:54 +00:00
|
|
|
private final Object key;
|
2009-06-27 21:46:53 +01:00
|
|
|
AncestorDirectory(Path dir, Object key) {
|
2009-02-15 12:25:54 +00:00
|
|
|
this.dir = dir;
|
|
|
|
this.key = key;
|
|
|
|
}
|
2009-06-27 21:46:53 +01:00
|
|
|
Path file() {
|
2009-02-15 12:25:54 +00:00
|
|
|
return dir;
|
|
|
|
}
|
|
|
|
Object fileKey() {
|
|
|
|
return key;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|