/* * 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 * * 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.jstestdriver.directoryscanner; import java.io.File; import java.io.IOException; import java.util.Stack; import java.util.StringTokenizer; /** * This class also encapsulates methods which allow Files to be * referred to using abstract path names which are translated to native * system file paths at runtime as well as copying files or setting * their last modification time. * */ public class FileUtils { private static final FileUtils PRIMARY_INSTANCE = new FileUtils(); private static final boolean ON_NETWARE = Os.isFamily("netware"); private static final boolean ON_DOS = Os.isFamily("dos"); static final int BUF_SIZE = 8192; /** * The granularity of timestamps under FAT. */ public static final long FAT_FILE_TIMESTAMP_GRANULARITY = 2000; /** * The granularity of timestamps under Unix. */ public static final long UNIX_FILE_TIMESTAMP_GRANULARITY = 1000; /** * The granularity of timestamps under the NT File System. * NTFS has a granularity of 100 nanoseconds, which is less * than 1 millisecond, so we round this up to 1 millisecond. */ public static final long NTFS_FILE_TIMESTAMP_GRANULARITY = 1; /** * Method to retrieve The FileUtils, which is shared by all users of this * method. * @return an instance of FileUtils. * @since Ant 1.6.3 */ public static FileUtils getFileUtils() { return PRIMARY_INSTANCE; } /** * Empty constructor. */ protected FileUtils() { } /** * Verifies that the specified filename represents an absolute path. * Differs from new java.io.File("filename").isAbsolute() in that a path * beginning with a double file separator--signifying a Windows UNC--must * at minimum match "\\a\b" to be considered an absolute path. * @param filename the filename to be checked. * @return true if the filename represents an absolute path. * @throws java.lang.NullPointerException if filename is null. * @since Ant 1.6.3 */ public static boolean isAbsolutePath(String filename) { int len = filename.length(); if (len == 0) { return false; } char sep = File.separatorChar; filename = filename.replace('/', sep).replace('\\', sep); char c = filename.charAt(0); if (!(ON_DOS || ON_NETWARE)) { return (c == sep); } if (c == sep) { // CheckStyle:MagicNumber OFF if (!(ON_DOS && len > 4 && filename.charAt(1) == sep)) { return false; } // CheckStyle:MagicNumber ON int nextsep = filename.indexOf(sep, 2); return nextsep > 2 && nextsep + 1 < len; } int colon = filename.indexOf(':'); return (Character.isLetter(c) && colon == 1 && filename.length() > 2 && filename.charAt(2) == sep) || (ON_NETWARE && colon > 0); } /** * "Normalize" the given absolute path. * * <p>This includes: * <ul> * <li>Uppercase the drive letter if there is one.</li> * <li>Remove redundant slashes after the drive spec.</li> * <li>Resolve all ./, .\, ../ and ..\ sequences.</li> * <li>DOS style paths that start with a drive letter will have * \ as the separator.</li> * </ul> * Unlike {@link File#getCanonicalPath()} this method * specifically does not resolve symbolic links. * * @param path the path to be normalized. * @return the normalized version of the path. * * @throws java.lang.NullPointerException if path is null. */ public File normalize(final String path) { Stack s = new Stack(); String[] dissect = dissect(path); s.push(dissect[0]); StringTokenizer tok = new StringTokenizer(dissect[1], File.separator); while (tok.hasMoreTokens()) { String thisToken = tok.nextToken(); if (".".equals(thisToken)) { continue; } if ("..".equals(thisToken)) { if (s.size() < 2) { // Cannot resolve it, so skip it. return new File(path); } s.pop(); } else { // plain component s.push(thisToken); } } StringBuffer sb = new StringBuffer(); for (int i = 0; i < s.size(); i++) { if (i > 1) { // not before the filesystem root and not after it, since root // already contains one sb.append(File.separatorChar); } sb.append(s.elementAt(i)); } return new File(sb.toString()); } /** * Dissect the specified absolute path. * @param path the path to dissect. * @return String[] {root, remaining path}. * @throws java.lang.NullPointerException if path is null. * @since Ant 1.7 */ public String[] dissect(String path) { char sep = File.separatorChar; path = path.replace('/', sep).replace('\\', sep); // make sure we are dealing with an absolute path if (!isAbsolutePath(path)) { throw new DirectoryScannerException(path + " is not an absolute path"); } String root = null; int colon = path.indexOf(':'); if (colon > 0 && (ON_DOS || ON_NETWARE)) { int next = colon + 1; root = path.substring(0, next); char[] ca = path.toCharArray(); root += sep; //remove the initial separator; the root has it. next = (ca[next] == sep) ? next + 1 : next; StringBuffer sbPath = new StringBuffer(); // Eliminate consecutive slashes after the drive spec: for (int i = next; i < ca.length; i++) { if (ca[i] != sep || ca[i - 1] != sep) { sbPath.append(ca[i]); } } path = sbPath.toString(); } else if (path.length() > 1 && path.charAt(1) == sep) { // UNC drive int nextsep = path.indexOf(sep, 2); nextsep = path.indexOf(sep, nextsep + 1); root = (nextsep > 2) ? path.substring(0, nextsep + 1) : path; path = path.substring(root.length()); } else { root = File.separator; path = path.substring(1); } return new String[] {root, path}; } /** * Checks whether a given file is a symbolic link. * * <p>It doesn't really test for symbolic links but whether the * canonical and absolute paths of the file are identical--this * may lead to false positives on some platforms.</p> * * @param parent the parent directory of the file to test * @param name the name of the file to test. * * @return true if the file is a symbolic link. * @throws IOException on error. * @since Ant 1.5 */ public boolean isSymbolicLink(File parent, String name) throws IOException { if (parent == null) { File f = new File(name); parent = f.getParentFile(); name = f.getName(); } File toTest = new File(parent.getCanonicalPath(), name); return !toTest.getAbsolutePath().equals(toTest.getCanonicalPath()); } /** * Removes a leading path from a second path. * * @param leading The leading path, must not be null, must be absolute. * @param path The path to remove from, must not be null, must be absolute. * * @return path's normalized absolute if it doesn't start with * leading; path's path with leading's path removed otherwise. * * @since Ant 1.5 */ public String removeLeadingPath(File leading, File path) { String l = normalize(leading.getAbsolutePath()).getAbsolutePath(); String p = normalize(path.getAbsolutePath()).getAbsolutePath(); if (l.equals(p)) { return ""; } // ensure that l ends with a / // so we never think /foo was a parent directory of /foobar if (!l.endsWith(File.separator)) { l += File.separator; } return (p.startsWith(l)) ? p.substring(l.length()) : p; } }