Java tutorial
/* * Copyright 2013 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.jimfs; import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.ImmutableSortedSet; import java.io.IOException; import java.nio.file.LinkOption; import java.nio.file.NoSuchFileException; import java.util.Iterator; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; /** * The tree of directories and files for the file system. Contains the file system root directories * and provides the ability to look up files by path. One piece of the file store implementation. * * @author Colin Decker */ final class FileTree { /** * Doesn't much matter, but this number comes from MIN_ELOOP_THRESHOLD * <a href="https://sourceware.org/git/gitweb.cgi?p=glibc.git;a=blob_plain;f=sysdeps/generic/eloop-threshold.h;hb=HEAD"> * here</a> */ private static final int MAX_SYMBOLIC_LINK_DEPTH = 40; private static final ImmutableList<Name> EMPTY_PATH_NAMES = ImmutableList.of(Name.SELF); /** * Map of root names to root directories. */ private final ImmutableSortedMap<Name, File> roots; /** * Creates a new file tree with the given root directories. */ FileTree(Map<Name, File> roots) { this.roots = ImmutableSortedMap.copyOf(roots, Name.canonicalOrdering()); } /** * Returns the names of the root directories in this tree. */ public ImmutableSortedSet<Name> getRootDirectoryNames() { return roots.keySet(); } /** * Gets the directory entry for the root with the given name or {@code null} if no such root * exists. */ @Nullable public DirectoryEntry getRoot(Name name) { File file = roots.get(name); return file == null ? null : ((Directory) file).entryInParent(); } /** * Returns the result of the file lookup for the given path. */ public DirectoryEntry lookUp(File workingDirectory, JimfsPath path, Set<? super LinkOption> options) throws IOException { checkNotNull(path); checkNotNull(options); DirectoryEntry result = lookUp(workingDirectory, path, options, 0); if (result == null) { // an intermediate file in the path did not exist or was not a directory throw new NoSuchFileException(path.toString()); } return result; } @Nullable private DirectoryEntry lookUp(File dir, JimfsPath path, Set<? super LinkOption> options, int linkDepth) throws IOException { ImmutableList<Name> names = path.names(); if (path.isAbsolute()) { // look up the root directory DirectoryEntry entry = getRoot(path.root()); if (entry == null) { // root not found; always return null as no real parent directory exists // this prevents new roots from being created in file systems supporting multiple roots return null; } else if (names.isEmpty()) { // root found, no more names to look up return entry; } else { // root found, more names to look up; set dir to the root directory for the path dir = entry.file(); } } else if (isEmpty(names)) { // set names to the canonical list of names for an empty path (singleton list of ".") names = EMPTY_PATH_NAMES; } return lookUp(dir, names, options, linkDepth); } private static boolean isEmpty(ImmutableList<Name> names) { // the empty path (created by FileSystem.getPath("")), has no root and a single name, "" return names.isEmpty() || names.size() == 1 && names.get(0).toString().isEmpty(); } /** * Looks up the given names against the given base file. If the file is not a directory, the * lookup fails. */ @Nullable private DirectoryEntry lookUp(File dir, Iterable<Name> names, Set<? super LinkOption> options, int linkDepth) throws IOException { Iterator<Name> nameIterator = names.iterator(); Name name = nameIterator.next(); while (nameIterator.hasNext()) { Directory directory = toDirectory(dir); if (directory == null) { return null; } DirectoryEntry entry = directory.get(name); if (entry == null) { return null; } File file = entry.file(); if (file.isSymbolicLink()) { DirectoryEntry linkResult = followSymbolicLink(dir, (SymbolicLink) file, linkDepth); if (linkResult == null) { return null; } dir = linkResult.fileOrNull(); } else { dir = file; } name = nameIterator.next(); } return lookUpLast(dir, name, options, linkDepth); } /** * Looks up the last element of a path. */ @Nullable private DirectoryEntry lookUpLast(@Nullable File dir, Name name, Set<? super LinkOption> options, int linkDepth) throws IOException { Directory directory = toDirectory(dir); if (directory == null) { return null; } DirectoryEntry entry = directory.get(name); if (entry == null) { return new DirectoryEntry(directory, name, null); } File file = entry.file(); if (!options.contains(LinkOption.NOFOLLOW_LINKS) && file.isSymbolicLink()) { return followSymbolicLink(dir, (SymbolicLink) file, linkDepth); } return getRealEntry(entry); } /** * Returns the directory entry located by the target path of the given symbolic link, resolved * relative to the given directory. */ @Nullable private DirectoryEntry followSymbolicLink(File dir, SymbolicLink link, int linkDepth) throws IOException { if (linkDepth >= MAX_SYMBOLIC_LINK_DEPTH) { throw new IOException("too many levels of symbolic links"); } return lookUp(dir, link.target(), Options.FOLLOW_LINKS, linkDepth + 1); } /** * Returns the entry for the file in its parent directory. This will be the given entry unless the * name for the entry is "." or "..", in which the directory linking to the file is not the file's * parent directory. In that case, we know the file must be a directory ("." and ".." can only * link to directories), so we can just get the entry in the directory's parent directory that * links to it. So, for example, if we have a directory "foo" that contains a directory "bar" and * we find an entry [bar -> "." -> bar], we instead return the entry for bar in its parent, * [foo -> "bar" -> bar]. */ @Nullable private DirectoryEntry getRealEntry(DirectoryEntry entry) { Name name = entry.name(); if (name.equals(Name.SELF) || name.equals(Name.PARENT)) { return ((Directory) entry.file()).entryInParent(); } else { return entry; } } @Nullable private Directory toDirectory(@Nullable File file) { return file == null || !file.isDirectory() ? null : (Directory) file; } }