| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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 |
| * |
| * https://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 org.apache.tools.ant.types.selectors; |
| |
| import java.io.File; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| |
| import org.apache.tools.ant.BuildException; |
| import org.apache.tools.ant.util.FileUtils; |
| |
| /** |
| * Container for a path that has been split into its components. |
| * @since 1.8.0 |
| */ |
| public class TokenizedPath { |
| |
| /** |
| * Instance that holds no tokens at all. |
| */ |
| public static final TokenizedPath EMPTY_PATH = |
| new TokenizedPath("", new String[0]); |
| |
| /** Helper. */ |
| private static final FileUtils FILE_UTILS = FileUtils.getFileUtils(); |
| /** iterations for case-sensitive scanning. */ |
| private static final boolean[] CS_SCAN_ONLY = new boolean[] {true}; |
| /** iterations for non-case-sensitive scanning. */ |
| private static final boolean[] CS_THEN_NON_CS = new boolean[] {true, false}; |
| |
| private final String path; |
| private final String[] tokenizedPath; |
| |
| /** |
| * Initialize the TokenizedPath by parsing it. |
| * @param path The path to tokenize. Must not be |
| * <code>null</code>. |
| */ |
| public TokenizedPath(String path) { |
| this(path, SelectorUtils.tokenizePathAsArray(path)); |
| } |
| |
| /** |
| * Creates a new path as a child of another path. |
| * |
| * @param parent the parent path |
| * @param child the child, must not contain the file separator |
| */ |
| public TokenizedPath(TokenizedPath parent, String child) { |
| if (!parent.path.isEmpty() |
| && parent.path.charAt(parent.path.length() - 1) != File.separatorChar) { |
| path = parent.path + File.separatorChar + child; |
| } else { |
| path = parent.path + child; |
| } |
| tokenizedPath = new String[parent.tokenizedPath.length + 1]; |
| System.arraycopy(parent.tokenizedPath, 0, tokenizedPath, 0, |
| parent.tokenizedPath.length); |
| tokenizedPath[parent.tokenizedPath.length] = child; |
| } |
| |
| /* package */ |
| TokenizedPath(String path, String[] tokens) { |
| this.path = path; |
| this.tokenizedPath = tokens; |
| } |
| |
| /** |
| * @return The original path String |
| */ |
| @Override |
| public String toString() { |
| return path; |
| } |
| |
| /** |
| * The depth (or length) of a path. |
| * @return int |
| */ |
| public int depth() { |
| return tokenizedPath.length; |
| } |
| |
| /* package */ |
| String[] getTokens() { |
| return tokenizedPath; |
| } |
| |
| /** |
| * From <code>base</code> traverse the filesystem in order to find |
| * a file that matches the given name. |
| * |
| * @param base base File (dir). |
| * @param cs whether to scan case-sensitively. |
| * @return File object that points to the file in question or null. |
| */ |
| public File findFile(File base, final boolean cs) { |
| String[] tokens = tokenizedPath; |
| if (FileUtils.isAbsolutePath(path)) { |
| if (base == null) { |
| String[] s = FILE_UTILS.dissect(path); |
| base = new File(s[0]); |
| tokens = SelectorUtils.tokenizePathAsArray(s[1]); |
| } else { |
| File f = FILE_UTILS.normalize(path); |
| String s = FILE_UTILS.removeLeadingPath(base, f); |
| if (s.equals(f.getAbsolutePath())) { |
| //removing base from path yields no change; path |
| //not child of base |
| return null; |
| } |
| tokens = SelectorUtils.tokenizePathAsArray(s); |
| } |
| } |
| return findFile(base, tokens, cs); |
| } |
| |
| /** |
| * Do we have to traverse a symlink when trying to reach path from |
| * basedir? |
| * @param base base File (dir). |
| * @return boolean |
| */ |
| public boolean isSymlink(File base) { |
| for (String token : tokenizedPath) { |
| final Path pathToTraverse; |
| if (base == null) { |
| pathToTraverse = Paths.get(token); |
| } else { |
| pathToTraverse = Paths.get(base.toPath().toString(), token); |
| } |
| if (Files.isSymbolicLink(pathToTraverse)) { |
| return true; |
| } |
| base = new File(base, token); |
| } |
| return false; |
| } |
| |
| /** |
| * true if the original paths are equal. |
| * @return boolean |
| */ |
| @Override |
| public boolean equals(Object o) { |
| return o instanceof TokenizedPath |
| && path.equals(((TokenizedPath) o).path); |
| } |
| |
| @Override |
| public int hashCode() { |
| return path.hashCode(); |
| } |
| |
| /** |
| * From <code>base</code> traverse the filesystem in order to find |
| * a file that matches the given stack of names. |
| * |
| * @param base base File (dir) - must not be null. |
| * @param pathElements array of path elements (dirs...file). |
| * @param cs whether to scan case-sensitively. |
| * @return File object that points to the file in question or null. |
| */ |
| private static File findFile(File base, final String[] pathElements, |
| final boolean cs) { |
| for (String pathElement : pathElements) { |
| if (!base.isDirectory()) { |
| return null; |
| } |
| String[] files = base.list(); |
| if (files == null) { |
| throw new BuildException("IO error scanning directory %s", |
| base.getAbsolutePath()); |
| } |
| boolean found = false; |
| boolean[] matchCase = cs ? CS_SCAN_ONLY : CS_THEN_NON_CS; |
| for (int i = 0; !found && i < matchCase.length; i++) { |
| for (int j = 0; !found && j < files.length; j++) { |
| if (matchCase[i] |
| ? files[j].equals(pathElement) |
| : files[j].equalsIgnoreCase(pathElement)) { |
| base = new File(base, files[j]); |
| found = true; |
| } |
| } |
| } |
| if (!found) { |
| return null; |
| } |
| } |
| return pathElements.length == 0 && !base.isDirectory() ? null : base; |
| } |
| |
| /** |
| * Creates a TokenizedPattern from the same tokens that make up |
| * this path. |
| * |
| * @return TokenizedPattern |
| */ |
| public TokenizedPattern toPattern() { |
| return new TokenizedPattern(path, tokenizedPath); |
| } |
| |
| } |