| /* |
| * 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 org.apache.flex.ant.tags.filesetClasses |
| { |
| import flash.filesystem.File; |
| import flash.system.Capabilities; |
| |
| import org.apache.flex.ant.Ant; |
| import org.apache.flex.ant.tags.filesetClasses.exceptions.BuildException; |
| import org.apache.flex.ant.tags.filesetClasses.exceptions.IOException; |
| |
| /** |
| * Ported from org.apache.tools.ant.util on 12/3/13. File copying |
| * was removed. So was tempfile, newfile, readfully and contentEquals. |
| * |
| * 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 const PRIMARY_INSTANCE:FileUtils = new FileUtils(); |
| |
| private static const ON_WINDOWS:Boolean = Capabilities.os.indexOf("Win") != -1; |
| private static const ON_DOS:Boolean = Capabilities.os.indexOf("Win") != -1; |
| |
| /** |
| * The granularity of timestamps under Unix. |
| */ |
| public static const UNIX_FILE_TIMESTAMP_GRANULARITY:int = 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 const NTFS_FILE_TIMESTAMP_GRANULARITY:int = 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 function getFileUtils():FileUtils { |
| return PRIMARY_INSTANCE; |
| } |
| |
| /** |
| * Empty constructor. |
| */ |
| public function FileUtils() { |
| } |
| |
| /** |
| * Get the URL for a file taking into account # characters. |
| * |
| * @param file the file whose URL representation is required. |
| * @return The FileURL value. |
| * @throws MalformedURLException if the URL representation cannot be |
| * formed. |
| public URL getFileURL(File file) throws MalformedURLException { |
| return new URL(file.toURI().toASCIIString()); |
| } |
| */ |
| |
| /** |
| * Calls File.setLastModified(long time). Originally written to |
| * to dynamically bind to that call on Java1.2+. |
| * |
| * @param file the file whose modified time is to be set |
| * @param time the time to which the last modified time is to be set. |
| * if this is -1, the current time is used. |
| */ |
| public function setFileLastModified(file:File, time:Number):void { |
| throw new Error("not supported in AIR"); |
| } |
| |
| /** |
| * Interpret the filename as a file relative to the given file |
| * unless the filename already represents an absolute filename. |
| * Differs from <code>new File(file, filename)</code> in that |
| * the resulting File's path will always be a normalized, |
| * absolute pathname. Also, if it is determined that |
| * <code>filename</code> is context-relative, <code>file</code> |
| * will be discarded and the reference will be resolved using |
| * available context/state information about the filesystem. |
| * |
| * @param file the "reference" file for relative paths. This |
| * instance must be an absolute file and must not contain |
| * "./" or "../" sequences (same for \ instead |
| * of /). If it is null, this call is equivalent to |
| * <code>new java.io.File(filename).getAbsoluteFile()</code>. |
| * |
| * @param filename a file name. |
| * |
| * @return an absolute file. |
| * @throws java.lang.NullPointerException if filename is null. |
| */ |
| public function resolveFile(file:File, filename:String):File { |
| if (!isAbsolutePath(filename)) { |
| var sep:String = File.separator; |
| filename = filename.replace(/\//g, sep).replace(/\\/g, sep); |
| if (isContextRelativePath(filename)) { |
| file = null; |
| // on cygwin, our current directory can be a UNC; |
| // assume user.dir is absolute or all hell breaks loose... |
| var udir:String = File.userDirectory.nativePath; |
| if (filename.charAt(0) == sep && udir.charAt(0) == sep) { |
| filename = dissect(udir)[0] + filename.substring(1); |
| } |
| } |
| filename = new File(file.nativePath + File.separator + filename).nativePath; |
| } |
| return normalize(filename); |
| } |
| |
| /** |
| * On DOS and NetWare, the evaluation of certain file |
| * specifications is context-dependent. These are filenames |
| * beginning with a single separator (relative to current root directory) |
| * and filenames with a drive specification and no intervening separator |
| * (relative to current directory of the specified root). |
| * @param filename the filename to evaluate. |
| * @return true if the filename is relative to system context. |
| * @throws java.lang.NullPointerException if filename is null. |
| * @since Ant 1.7 |
| */ |
| public static function isContextRelativePath(filename:String):Boolean { |
| if (!ON_DOS || filename.length == 0) { |
| return false; |
| } |
| var sep:String = File.separator; |
| filename = filename.replace(/\//g, sep).replace(/\\/g, sep); |
| var c:String = filename.charAt(0); |
| var len:int = filename.length; |
| return (c == sep && (len == 1 || filename.charAt(1) != sep)) |
| || (Character.isLetter(c) && len > 1 |
| && filename.indexOf(':') == 1 |
| && (len == 2 || filename.charAt(2) != sep)); |
| } |
| |
| |
| /** |
| * 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 function isAbsolutePath(filename:String):Boolean { |
| var len:int = filename.length; |
| if (len == 0) { |
| return false; |
| } |
| var sep:String = File.separator; |
| filename = filename.replace(/\//g, sep).replace(/\\/g, sep); |
| var c:String = filename.charAt(0); |
| if (!ON_DOS) |
| return (c == sep); |
| |
| if (c == sep) { |
| // CheckStyle:MagicNumber OFF |
| if (!(ON_DOS && len > 4 && filename.charAt(1) == sep)) { |
| return false; |
| } |
| // CheckStyle:MagicNumber ON |
| var nextsep:int = filename.indexOf(sep, 2); |
| return nextsep > 2 && nextsep + 1 < len; |
| } |
| var colon:int = filename.indexOf(':'); |
| return (Character.isLetter(c) && colon == 1 |
| && filename.length > 2 && filename.charAt(2) == sep); |
| } |
| |
| /** |
| * Translate a path into its native (platform specific) format. |
| * <p> |
| * This method uses PathTokenizer to separate the input path |
| * into its components. This handles DOS style paths in a relatively |
| * sensible way. The file separators are then converted to their platform |
| * specific versions. |
| * |
| * @param toProcess The path to be translated. |
| * May be <code>null</code>. |
| * |
| * @return the native version of the specified path or |
| * an empty string if the path is <code>null</code> or empty. |
| * |
| * @since ant 1.7 |
| * @see PathTokenizer |
| */ |
| public static function translatePath(toProcess:String):String { |
| if (toProcess == null || toProcess.length == 0) { |
| return ""; |
| } |
| var path:String = ""; |
| var tokenizer:PathTokenizer = new PathTokenizer(toProcess); |
| while (tokenizer.hasMoreTokens()) { |
| var pathComponent:String = tokenizer.nextToken(); |
| pathComponent = pathComponent.replace(/\//g, File.separator); |
| pathComponent = pathComponent.replace(/\\/g, File.separator); |
| if (path.length != 0) { |
| path += File.separator; |
| } |
| path += pathComponent; |
| } |
| return path; |
| } |
| |
| /** |
| * "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 function normalize(path:String):File { |
| var s:Array = new Array(); |
| var dissect:Vector.<String> = dissect(path); |
| s.push(dissect[0]); |
| |
| var tok:StringTokenizer = new StringTokenizer(dissect[1], File.separator); |
| while (tok.hasMoreTokens()) { |
| var thisToken:String = tok.nextToken(); |
| if ("." == thisToken) { |
| continue; |
| } |
| if (".." == thisToken) { |
| if (s.length < 2) { |
| // Cannot resolve it, so skip it. |
| return new File(path); |
| } |
| s.pop(); |
| } else { // plain component |
| s.push(thisToken); |
| } |
| } |
| var sb:String = ""; |
| var size:int = s.length; |
| for (var i:int = 0; i < size; i++) { |
| if (i > 1) { |
| // not before the filesystem root and not after it, since root |
| // already contains one |
| sb += File.separator; |
| } |
| sb += s[i]; |
| } |
| return new File(sb); |
| } |
| |
| /** |
| * 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 function dissect(path:String):Vector.<String> { |
| var sep:String = File.separator; |
| path = path.replace(/\//g, sep).replace(/\\/g, sep); |
| |
| // make sure we are dealing with an absolute path |
| if (!isAbsolutePath(path)) { |
| throw new BuildException(path + " is not an absolute path"); |
| } |
| var root:String = null; |
| var colon:int = path.indexOf(':'); |
| if (colon > 0 && ON_DOS) { |
| |
| var next:int = colon + 1; |
| root = path.substring(0, next); |
| var ca:Vector.<String> = Vector.<String>(path.split("")); |
| root += sep; |
| //remove the initial separator; the root has it. |
| next = (ca[next] == sep) ? next + 1 : next; |
| |
| var sbPath:String = ""; |
| // Eliminate consecutive slashes after the drive spec: |
| for (var i:int = next; i < ca.length; i++) { |
| if (ca[i] != sep || ca[i - 1] != sep) { |
| sbPath += ca[i]; |
| } |
| } |
| path = sbPath; |
| } else if (path.length > 1 && path.charAt(1) == sep) { |
| // UNC drive |
| var nextsep:int = 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 Vector.<String>([root, path]); |
| } |
| |
| |
| /** |
| * This was originally an emulation of {@link File#getParentFile} for JDK 1.1, but it is now |
| * implemented using that method (Ant 1.6.3 onwards). |
| * |
| * @param f the file whose parent is required. |
| * @return the given file's parent, or null if the file does not have a parent. |
| * @since 1.10 |
| * @deprecated since 1.7. Just use {@link File#getParentFile} directly. |
| */ |
| public function getParentFile(f:File):File { |
| return (f == null) ? null : f.parent; |
| } |
| |
| /** |
| * 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 |
| * @deprecated use SymbolicLinkUtils instead |
| */ |
| public function isSymbolicLink(parent:File, name:String):Boolean |
| { |
| if (parent == null) { |
| return new File(name).isSymbolicLink; |
| } |
| return new File(parent.nativePath + File.separator + name).isSymbolicLink; |
| } |
| |
| /** |
| * 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 function removeLeadingPath(leading:File, path:File):String { |
| var l:String = normalize(leading.nativePath).nativePath; |
| var p:String = normalize(path.nativePath).nativePath; |
| if (l == p) { |
| return ""; |
| } |
| // ensure that l ends with a / |
| // so we never think /foo was a parent directory of /foobar |
| if (l.charAt(l.length - 1) != File.separator) { |
| l += File.separator; |
| } |
| return (p.indexOf(l) == 0) ? p.substring(l.length) : p; |
| } |
| |
| /** |
| * Learn whether one path "leads" another. |
| * @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 true if path starts with leading; false otherwise. |
| * @since Ant 1.7 |
| */ |
| public function isLeadingPath(leading:File, path:File):Boolean { |
| var l:String = normalize(leading.nativePath).nativePath; |
| var p:String = normalize(path.nativePath).nativePath; |
| if (l == p) { |
| return true; |
| } |
| // ensure that l ends with a / |
| // so we never think /foo was a parent directory of /foobar |
| if (l.charAt(l.length - 1) != File.separator) { |
| l += File.separator; |
| } |
| return p.indexOf(l) == 0; |
| } |
| |
| /** |
| * Compares two filenames. |
| * |
| * <p>Unlike java.io.File#equals this method will try to compare |
| * the absolute paths and "normalize" the filenames |
| * before comparing them.</p> |
| * |
| * @param f1 the file whose name is to be compared. |
| * @param f2 the other file whose name is to be compared. |
| * |
| * @return true if the file are for the same file. |
| * |
| * @since Ant 1.5.3 |
| */ |
| public function fileNameEquals(f1:File, f2:File):Boolean { |
| return normalize(f1.nativePath).nativePath == |
| normalize(f2.nativePath).nativePath; |
| } |
| |
| /** |
| * Are the two File instances pointing to the same object on the |
| * file system? |
| * @since Ant 1.8.2 |
| */ |
| public function areSame(f1:File, f2:File):Boolean { |
| if (f1 == null && f2 == null) { |
| return true; |
| } |
| if (f1 == null || f2 == null) { |
| return false; |
| } |
| var f1Normalized:File = normalize(f1.nativePath); |
| var f2Normalized:File = normalize(f2.nativePath); |
| if (f1Normalized.nativePath == f2Normalized.nativePath) |
| return true; |
| f1Normalized.canonicalize(); |
| f2Normalized.canonicalize(); |
| return f1Normalized.nativePath == f2Normalized.nativePath; |
| } |
| |
| /** |
| * Renames a file, even if that involves crossing file system boundaries. |
| * |
| * <p>This will remove <code>to</code> (if it exists), ensure that |
| * <code>to</code>'s parent directory exists and move |
| * <code>from</code>, which involves deleting <code>from</code> as |
| * well.</p> |
| * |
| * @param from the file to move. |
| * @param to the new file name. |
| * |
| * @throws IOException if anything bad happens during this |
| * process. Note that <code>to</code> may have been deleted |
| * already when this happens. |
| * |
| * @since Ant 1.6 |
| */ |
| public function rename(from:File, to:File):void { |
| // identical logic lives in Move.renameFile(): |
| from.canonicalize() |
| from = normalize(from.nativePath); |
| to = normalize(to.nativePath); |
| if (!from.exists) { |
| Ant.currentAnt.output("Cannot rename nonexistent file " + from); |
| return; |
| } |
| if (from.nativePath == to.nativePath) { |
| Ant.currentAnt.output("Rename of " + from + " to " + to + " is a no-op."); |
| return; |
| } |
| if (to.exists) |
| to.deleteFile(); |
| if (to.exists && !(areSame(from, to))) { |
| throw new IOException("Failed to delete " + to + " while trying to rename " + from); |
| } |
| var parent:File = to.parent; |
| if (parent != null && !parent.exists && !parent.createDirectory()) { |
| throw new IOException("Failed to create directory " + parent |
| + " while trying to rename " + from); |
| } |
| if (!from.moveTo(to)) { |
| from.copyTo(to); |
| from.deleteFile(); |
| if (from.exists) { |
| throw new IOException("Failed to delete " + from + " while trying to rename it."); |
| } |
| } |
| } |
| |
| /** |
| * Get the granularity of file timestamps. The choice is made based on OS, which is |
| * incorrect--it should really be by filesystem. We do not have an easy way to probe for file |
| * systems, however, so this heuristic gives us a decent default. |
| * |
| * @return the difference, in milliseconds, which two file timestamps must have in order for the |
| * two files to be considered to have different timestamps. |
| */ |
| public function getFileTimestampGranularity():Number { |
| if (ON_WINDOWS) { |
| return NTFS_FILE_TIMESTAMP_GRANULARITY; |
| } |
| return UNIX_FILE_TIMESTAMP_GRANULARITY; |
| } |
| |
| |
| /** |
| * Returns true if the source is older than the dest. |
| * If the dest file does not exist, then the test returns false; it is |
| * implicitly not up do date. |
| * @param source source file (should be the older). |
| * @param dest dest file (should be the newer). |
| * @param granularity an offset added to the source time. |
| * @return true if the source is older than the dest after accounting |
| * for granularity. |
| * @since Ant 1.6.3 |
| */ |
| public function isUpToDate(source:File, dest:File, granularity:Object = null):Boolean { |
| if (granularity == null) |
| granularity = getFileTimestampGranularity(); |
| //do a check for the destination file existing |
| if (!dest.exists) { |
| //if it does not, then the file is not up to date. |
| return false; |
| } |
| var sourceTime:Date = source.modificationDate; |
| var destTime:Date = dest.modificationDate; |
| return isUpToDateDate(sourceTime.time, destTime.time, granularity as Number); |
| } |
| |
| /** |
| * Compare two timestamps for being up to date using |
| * the specified granularity. |
| * |
| * @param sourceTime timestamp of source file. |
| * @param destTime timestamp of dest file. |
| * @param granularity os/filesys granularity. |
| * @return true if the dest file is considered up to date. |
| */ |
| public function isUpToDateDate(sourceTime:Number, destTime:Number, granularity:Number = NaN):Boolean { |
| if (isNaN(granularity)) |
| granularity = getFileTimestampGranularity(); |
| return destTime != -1 && destTime >= sourceTime + granularity; |
| } |
| |
| |
| /** |
| * Calculates the relative path between two files. |
| * <p> |
| * Implementation note:<br/> This function may throw an IOException if an I/O error occurs |
| * because its use of the canonical pathname may require filesystem queries. |
| * </p> |
| * |
| * @param fromFile the <code>File</code> to calculate the path from |
| * @param toFile the <code>File</code> to calculate the path to |
| * @return the relative path between the files |
| * @throws Exception for undocumented reasons |
| * @see File#getCanonicalPath() |
| * |
| * @since Ant 1.7 |
| */ |
| public static function getRelativePath(fromFile:File, toFile:File):String { |
| fromFile.canonicalize(); |
| toFile.canonicalize(); |
| var fromPath:String = fromFile.nativePath; |
| var toPath:String = toFile.nativePath; |
| |
| // build the path stack info to compare |
| var fromPathStack:Vector.<String> = getPathStack(fromPath); |
| var toPathStack:Vector.<String> = getPathStack(toPath); |
| |
| if (0 < toPathStack.length && 0 < fromPathStack.length) { |
| if (!fromPathStack[0] == toPathStack[0]) { |
| // not the same device (would be "" on Linux/Unix) |
| |
| return getPath(toPathStack); |
| } |
| } else { |
| // no comparison possible |
| return getPath(toPathStack); |
| } |
| |
| var minLength:int = Math.min(fromPathStack.length, toPathStack.length); |
| var same:int = 1; // Used outside the for loop |
| |
| // get index of parts which are equal |
| for (; |
| same < minLength && fromPathStack[same] == toPathStack[same]; |
| same++) { |
| // Do nothing |
| } |
| |
| var relativePathStack:Vector.<String> = new Vector.<String>(); |
| |
| // if "from" part is longer, fill it up with ".." |
| // to reach path which is equal to both paths |
| for (var i:int = same; i < fromPathStack.length; i++) { |
| relativePathStack.push(".."); |
| } |
| |
| // fill it up path with parts which were not equal |
| for (i = same; i < toPathStack.length; i++) { |
| relativePathStack.push(toPathStack[i]); |
| } |
| |
| return getPath(relativePathStack); |
| } |
| |
| /** |
| * Gets all names of the path as an array of <code>String</code>s. |
| * |
| * @param path to get names from |
| * @return <code>String</code>s, never <code>null</code> |
| * |
| * @since Ant 1.7 |
| */ |
| public static function getPathStack(path:String):Vector.<String> { |
| var normalizedPath:String = path.replace(new RegExp(File.separator, "g"), '/'); |
| |
| return Vector.<String>(normalizedPath.split("/")); |
| } |
| |
| /** |
| * Gets path from a <code>List</code> of <code>String</code>s. |
| * |
| * @param pathStack <code>List</code> of <code>String</code>s to be concated as a path. |
| * @param separatorChar <code>char</code> to be used as separator between names in path |
| * @return <code>String</code>, never <code>null</code> |
| * |
| * @since Ant 1.7 |
| */ |
| public static function getPath(pathStack:Vector.<String>, separatorChar:String = "/"):String { |
| var buffer:String = "" |
| |
| if (pathStack.length) { |
| buffer += pathStack.shift(); |
| } |
| while (pathStack.length) { |
| buffer += separatorChar; |
| buffer += pathStack.shift(); |
| } |
| return buffer; |
| } |
| |
| } |
| } |