0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: *
0017: */
0018:
0019: package org.apache.tools.ant.util;
0020:
0021: import java.io.File;
0022: import java.io.IOException;
0023: import java.io.InputStream;
0024: import java.io.InputStreamReader;
0025: import java.io.Reader;
0026: import java.io.UnsupportedEncodingException;
0027: import java.io.Writer;
0028: import java.io.OutputStream;
0029: import java.net.MalformedURLException;
0030: import java.net.URL;
0031: import java.text.DecimalFormat;
0032: import java.util.ArrayList;
0033: import java.util.Arrays;
0034: import java.util.Iterator;
0035: import java.util.List;
0036: import java.util.Random;
0037: import java.util.Stack;
0038: import java.util.StringTokenizer;
0039: import java.util.Vector;
0040: import org.apache.tools.ant.BuildException;
0041: import org.apache.tools.ant.PathTokenizer;
0042: import org.apache.tools.ant.Project;
0043: import org.apache.tools.ant.taskdefs.condition.Os;
0044: import org.apache.tools.ant.types.FilterSetCollection;
0045: import org.apache.tools.ant.types.resources.FileResource;
0046: import org.apache.tools.ant.launch.Locator;
0047:
0048: /**
0049: * This class also encapsulates methods which allow Files to be
0050: * referred to using abstract path names which are translated to native
0051: * system file paths at runtime as well as copying files or setting
0052: * their last modification time.
0053: *
0054: */
0055: public class FileUtils {
0056:
0057: private static final FileUtils PRIMARY_INSTANCE = new FileUtils();
0058:
0059: //get some non-crypto-grade randomness from various places.
0060: private static Random rand = new Random(System.currentTimeMillis()
0061: + Runtime.getRuntime().freeMemory());
0062:
0063: private static boolean onNetWare = Os.isFamily("netware");
0064: private static boolean onDos = Os.isFamily("dos");
0065: private static boolean onWin9x = Os.isFamily("win9x");
0066: private static boolean onWindows = Os.isFamily("windows");
0067:
0068: static final int BUF_SIZE = 8192;
0069:
0070: /**
0071: * The granularity of timestamps under FAT.
0072: */
0073: public static final long FAT_FILE_TIMESTAMP_GRANULARITY = 2000;
0074:
0075: /**
0076: * The granularity of timestamps under Unix.
0077: */
0078: public static final long UNIX_FILE_TIMESTAMP_GRANULARITY = 1000;
0079:
0080: /**
0081: * The granularity of timestamps under the NT File System.
0082: * NTFS has a granularity of 100 nanoseconds, which is less
0083: * than 1 millisecond, so we round this up to 1 millisecond.
0084: */
0085: public static final long NTFS_FILE_TIMESTAMP_GRANULARITY = 1;
0086:
0087: /**
0088: * A one item cache for fromUri.
0089: * fromUri is called for each element when parseing ant build
0090: * files. It is a costly operation. This just caches the result
0091: * of the last call.
0092: */
0093: private Object cacheFromUriLock = new Object();
0094: private String cacheFromUriRequest = null;
0095: private String cacheFromUriResponse = null;
0096:
0097: /**
0098: * Factory method.
0099: *
0100: * @return a new instance of FileUtils.
0101: * @deprecated since 1.7.
0102: * Use getFileUtils instead,
0103: * FileUtils do not have state.
0104: */
0105: public static FileUtils newFileUtils() {
0106: return new FileUtils();
0107: }
0108:
0109: /**
0110: * Method to retrieve The FileUtils, which is shared by all users of this
0111: * method.
0112: * @return an instance of FileUtils.
0113: * @since Ant 1.6.3
0114: */
0115: public static FileUtils getFileUtils() {
0116: return PRIMARY_INSTANCE;
0117: }
0118:
0119: /**
0120: * Empty constructor.
0121: */
0122: protected FileUtils() {
0123: }
0124:
0125: /**
0126: * Get the URL for a file taking into account # characters.
0127: *
0128: * @param file the file whose URL representation is required.
0129: * @return The FileURL value.
0130: * @throws MalformedURLException if the URL representation cannot be
0131: * formed.
0132: */
0133: public URL getFileURL(File file) throws MalformedURLException {
0134: return new URL(toURI(file.getAbsolutePath()));
0135: }
0136:
0137: /**
0138: * Convenience method to copy a file from a source to a destination.
0139: * No filtering is performed.
0140: *
0141: * @param sourceFile Name of file to copy from.
0142: * Must not be <code>null</code>.
0143: * @param destFile Name of file to copy to.
0144: * Must not be <code>null</code>.
0145: *
0146: * @throws IOException if the copying fails.
0147: */
0148: public void copyFile(String sourceFile, String destFile)
0149: throws IOException {
0150: copyFile(new File(sourceFile), new File(destFile), null, false,
0151: false);
0152: }
0153:
0154: /**
0155: * Convenience method to copy a file from a source to a destination
0156: * specifying if token filtering must be used.
0157: *
0158: * @param sourceFile Name of file to copy from.
0159: * Must not be <code>null</code>.
0160: * @param destFile Name of file to copy to.
0161: * Must not be <code>null</code>.
0162: * @param filters the collection of filters to apply to this copy.
0163: *
0164: * @throws IOException if the copying fails.
0165: */
0166: public void copyFile(String sourceFile, String destFile,
0167: FilterSetCollection filters) throws IOException {
0168: copyFile(new File(sourceFile), new File(destFile), filters,
0169: false, false);
0170: }
0171:
0172: /**
0173: * Convenience method to copy a file from a source to a
0174: * destination specifying if token filtering must be used and if
0175: * source files may overwrite newer destination files.
0176: *
0177: * @param sourceFile Name of file to copy from.
0178: * Must not be <code>null</code>.
0179: * @param destFile Name of file to copy to.
0180: * Must not be <code>null</code>.
0181: * @param filters the collection of filters to apply to this copy.
0182: * @param overwrite Whether or not the destination file should be
0183: * overwritten if it already exists.
0184: *
0185: * @throws IOException if the copying fails.
0186: */
0187: public void copyFile(String sourceFile, String destFile,
0188: FilterSetCollection filters, boolean overwrite)
0189: throws IOException {
0190: copyFile(new File(sourceFile), new File(destFile), filters,
0191: overwrite, false);
0192: }
0193:
0194: /**
0195: * Convenience method to copy a file from a source to a
0196: * destination specifying if token filtering must be used, if
0197: * source files may overwrite newer destination files and the
0198: * last modified time of <code>destFile</code> file should be made equal
0199: * to the last modified time of <code>sourceFile</code>.
0200: *
0201: * @param sourceFile Name of file to copy from.
0202: * Must not be <code>null</code>.
0203: * @param destFile Name of file to copy to.
0204: * Must not be <code>null</code>.
0205: * @param filters the collection of filters to apply to this copy.
0206: * @param overwrite Whether or not the destination file should be
0207: * overwritten if it already exists.
0208: * @param preserveLastModified Whether or not the last modified time of
0209: * the resulting file should be set to that
0210: * of the source file.
0211: *
0212: * @throws IOException if the copying fails.
0213: */
0214: public void copyFile(String sourceFile, String destFile,
0215: FilterSetCollection filters, boolean overwrite,
0216: boolean preserveLastModified) throws IOException {
0217: copyFile(new File(sourceFile), new File(destFile), filters,
0218: overwrite, preserveLastModified);
0219: }
0220:
0221: /**
0222: * Convenience method to copy a file from a source to a
0223: * destination specifying if token filtering must be used, if
0224: * source files may overwrite newer destination files and the
0225: * last modified time of <code>destFile</code> file should be made equal
0226: * to the last modified time of <code>sourceFile</code>.
0227: *
0228: * @param sourceFile Name of file to copy from.
0229: * Must not be <code>null</code>.
0230: * @param destFile Name of file to copy to.
0231: * Must not be <code>null</code>.
0232: * @param filters the collection of filters to apply to this copy.
0233: * @param overwrite Whether or not the destination file should be
0234: * overwritten if it already exists.
0235: * @param preserveLastModified Whether or not the last modified time of
0236: * the resulting file should be set to that
0237: * of the source file.
0238: * @param encoding the encoding used to read and write the files.
0239: *
0240: * @throws IOException if the copying fails.
0241: *
0242: * @since Ant 1.5
0243: */
0244: public void copyFile(String sourceFile, String destFile,
0245: FilterSetCollection filters, boolean overwrite,
0246: boolean preserveLastModified, String encoding)
0247: throws IOException {
0248: copyFile(new File(sourceFile), new File(destFile), filters,
0249: overwrite, preserveLastModified, encoding);
0250: }
0251:
0252: // CheckStyle:ParameterNumberCheck OFF - bc
0253: /**
0254: * Convenience method to copy a file from a source to a
0255: * destination specifying if token filtering must be used, if
0256: * filter chains must be used, if source files may overwrite
0257: * newer destination files and the last modified time of
0258: * <code>destFile</code> file should be made equal
0259: * to the last modified time of <code>sourceFile</code>.
0260: *
0261: * @param sourceFile Name of file to copy from.
0262: * Must not be <code>null</code>.
0263: * @param destFile Name of file to copy to.
0264: * Must not be <code>null</code>.
0265: * @param filters the collection of filters to apply to this copy.
0266: * @param filterChains filterChains to apply during the copy.
0267: * @param overwrite Whether or not the destination file should be
0268: * overwritten if it already exists.
0269: * @param preserveLastModified Whether or not the last modified time of
0270: * the resulting file should be set to that
0271: * of the source file.
0272: * @param encoding the encoding used to read and write the files.
0273: * @param project the project instance.
0274: *
0275: * @throws IOException if the copying fails.
0276: *
0277: * @since Ant 1.5
0278: */
0279: public void copyFile(String sourceFile, String destFile,
0280: FilterSetCollection filters, Vector filterChains,
0281: boolean overwrite, boolean preserveLastModified,
0282: String encoding, Project project) throws IOException {
0283: copyFile(new File(sourceFile), new File(destFile), filters,
0284: filterChains, overwrite, preserveLastModified,
0285: encoding, project);
0286: }
0287:
0288: /**
0289: * Convenience method to copy a file from a source to a
0290: * destination specifying if token filtering must be used, if
0291: * filter chains must be used, if source files may overwrite
0292: * newer destination files and the last modified time of
0293: * <code>destFile</code> file should be made equal
0294: * to the last modified time of <code>sourceFile</code>.
0295: *
0296: * @param sourceFile Name of file to copy from.
0297: * Must not be <code>null</code>.
0298: * @param destFile Name of file to copy to.
0299: * Must not be <code>null</code>.
0300: * @param filters the collection of filters to apply to this copy.
0301: * @param filterChains filterChains to apply during the copy.
0302: * @param overwrite Whether or not the destination file should be
0303: * overwritten if it already exists.
0304: * @param preserveLastModified Whether or not the last modified time of
0305: * the resulting file should be set to that
0306: * of the source file.
0307: * @param inputEncoding the encoding used to read the files.
0308: * @param outputEncoding the encoding used to write the files.
0309: * @param project the project instance.
0310: *
0311: * @throws IOException if the copying fails.
0312: *
0313: * @since Ant 1.6
0314: */
0315: public void copyFile(String sourceFile, String destFile,
0316: FilterSetCollection filters, Vector filterChains,
0317: boolean overwrite, boolean preserveLastModified,
0318: String inputEncoding, String outputEncoding, Project project)
0319: throws IOException {
0320: copyFile(new File(sourceFile), new File(destFile), filters,
0321: filterChains, overwrite, preserveLastModified,
0322: inputEncoding, outputEncoding, project);
0323: }
0324:
0325: /**
0326: * Convenience method to copy a file from a source to a destination.
0327: * No filtering is performed.
0328: *
0329: * @param sourceFile the file to copy from.
0330: * Must not be <code>null</code>.
0331: * @param destFile the file to copy to.
0332: * Must not be <code>null</code>.
0333: *
0334: * @throws IOException if the copying fails.
0335: */
0336: public void copyFile(File sourceFile, File destFile)
0337: throws IOException {
0338: copyFile(sourceFile, destFile, null, false, false);
0339: }
0340:
0341: /**
0342: * Convenience method to copy a file from a source to a destination
0343: * specifying if token filtering must be used.
0344: *
0345: * @param sourceFile the file to copy from.
0346: * Must not be <code>null</code>.
0347: * @param destFile the file to copy to.
0348: * Must not be <code>null</code>.
0349: * @param filters the collection of filters to apply to this copy.
0350: *
0351: * @throws IOException if the copying fails.
0352: */
0353: public void copyFile(File sourceFile, File destFile,
0354: FilterSetCollection filters) throws IOException {
0355: copyFile(sourceFile, destFile, filters, false, false);
0356: }
0357:
0358: /**
0359: * Convenience method to copy a file from a source to a
0360: * destination specifying if token filtering must be used and if
0361: * source files may overwrite newer destination files.
0362: *
0363: * @param sourceFile the file to copy from.
0364: * Must not be <code>null</code>.
0365: * @param destFile the file to copy to.
0366: * Must not be <code>null</code>.
0367: * @param filters the collection of filters to apply to this copy.
0368: * @param overwrite Whether or not the destination file should be
0369: * overwritten if it already exists.
0370: *
0371: * @throws IOException if the copying fails.
0372: */
0373: public void copyFile(File sourceFile, File destFile,
0374: FilterSetCollection filters, boolean overwrite)
0375: throws IOException {
0376: copyFile(sourceFile, destFile, filters, overwrite, false);
0377: }
0378:
0379: /**
0380: * Convenience method to copy a file from a source to a
0381: * destination specifying if token filtering must be used, if
0382: * source files may overwrite newer destination files and the
0383: * last modified time of <code>destFile</code> file should be made equal
0384: * to the last modified time of <code>sourceFile</code>.
0385: *
0386: * @param sourceFile the file to copy from.
0387: * Must not be <code>null</code>.
0388: * @param destFile the file to copy to.
0389: * Must not be <code>null</code>.
0390: * @param filters the collection of filters to apply to this copy.
0391: * @param overwrite Whether or not the destination file should be
0392: * overwritten if it already exists.
0393: * @param preserveLastModified Whether or not the last modified time of
0394: * the resulting file should be set to that
0395: * of the source file.
0396: *
0397: * @throws IOException if the copying fails.
0398: */
0399: public void copyFile(File sourceFile, File destFile,
0400: FilterSetCollection filters, boolean overwrite,
0401: boolean preserveLastModified) throws IOException {
0402: copyFile(sourceFile, destFile, filters, overwrite,
0403: preserveLastModified, null);
0404: }
0405:
0406: /**
0407: * Convenience method to copy a file from a source to a
0408: * destination specifying if token filtering must be used, if
0409: * source files may overwrite newer destination files, the last
0410: * modified time of <code>destFile</code> file should be made
0411: * equal to the last modified time of <code>sourceFile</code> and
0412: * which character encoding to assume.
0413: *
0414: * @param sourceFile the file to copy from.
0415: * Must not be <code>null</code>.
0416: * @param destFile the file to copy to.
0417: * Must not be <code>null</code>.
0418: * @param filters the collection of filters to apply to this copy.
0419: * @param overwrite Whether or not the destination file should be
0420: * overwritten if it already exists.
0421: * @param preserveLastModified Whether or not the last modified time of
0422: * the resulting file should be set to that
0423: * of the source file.
0424: * @param encoding the encoding used to read and write the files.
0425: *
0426: * @throws IOException if the copying fails.
0427: *
0428: * @since Ant 1.5
0429: */
0430: public void copyFile(File sourceFile, File destFile,
0431: FilterSetCollection filters, boolean overwrite,
0432: boolean preserveLastModified, String encoding)
0433: throws IOException {
0434: copyFile(sourceFile, destFile, filters, null, overwrite,
0435: preserveLastModified, encoding, null);
0436: }
0437:
0438: /**
0439: * Convenience method to copy a file from a source to a
0440: * destination specifying if token filtering must be used, if
0441: * filter chains must be used, if source files may overwrite
0442: * newer destination files and the last modified time of
0443: * <code>destFile</code> file should be made equal
0444: * to the last modified time of <code>sourceFile</code>.
0445: *
0446: * @param sourceFile the file to copy from.
0447: * Must not be <code>null</code>.
0448: * @param destFile the file to copy to.
0449: * Must not be <code>null</code>.
0450: * @param filters the collection of filters to apply to this copy.
0451: * @param filterChains filterChains to apply during the copy.
0452: * @param overwrite Whether or not the destination file should be
0453: * overwritten if it already exists.
0454: * @param preserveLastModified Whether or not the last modified time of
0455: * the resulting file should be set to that
0456: * of the source file.
0457: * @param encoding the encoding used to read and write the files.
0458: * @param project the project instance.
0459: *
0460: * @throws IOException if the copying fails.
0461: *
0462: * @since Ant 1.5
0463: */
0464: public void copyFile(File sourceFile, File destFile,
0465: FilterSetCollection filters, Vector filterChains,
0466: boolean overwrite, boolean preserveLastModified,
0467: String encoding, Project project) throws IOException {
0468: copyFile(sourceFile, destFile, filters, filterChains,
0469: overwrite, preserveLastModified, encoding, encoding,
0470: project);
0471: }
0472:
0473: /**
0474: * Convenience method to copy a file from a source to a
0475: * destination specifying if token filtering must be used, if
0476: * filter chains must be used, if source files may overwrite
0477: * newer destination files and the last modified time of
0478: * <code>destFile</code> file should be made equal
0479: * to the last modified time of <code>sourceFile</code>.
0480: *
0481: * @param sourceFile the file to copy from.
0482: * Must not be <code>null</code>.
0483: * @param destFile the file to copy to.
0484: * Must not be <code>null</code>.
0485: * @param filters the collection of filters to apply to this copy.
0486: * @param filterChains filterChains to apply during the copy.
0487: * @param overwrite Whether or not the destination file should be
0488: * overwritten if it already exists.
0489: * @param preserveLastModified Whether or not the last modified time of
0490: * the resulting file should be set to that
0491: * of the source file.
0492: * @param inputEncoding the encoding used to read the files.
0493: * @param outputEncoding the encoding used to write the files.
0494: * @param project the project instance.
0495: *
0496: *
0497: * @throws IOException if the copying fails.
0498: *
0499: * @since Ant 1.6
0500: */
0501: public void copyFile(File sourceFile, File destFile,
0502: FilterSetCollection filters, Vector filterChains,
0503: boolean overwrite, boolean preserveLastModified,
0504: String inputEncoding, String outputEncoding, Project project)
0505: throws IOException {
0506: ResourceUtils.copyResource(new FileResource(sourceFile),
0507: new FileResource(destFile), filters, filterChains,
0508: overwrite, preserveLastModified, inputEncoding,
0509: outputEncoding, project);
0510: }
0511:
0512: // CheckStyle:ParameterNumberCheck ON
0513:
0514: /**
0515: * Calls File.setLastModified(long time). Originally written to
0516: * to dynamically bind to that call on Java1.2+.
0517: *
0518: * @param file the file whose modified time is to be set
0519: * @param time the time to which the last modified time is to be set.
0520: * if this is -1, the current time is used.
0521: */
0522: public void setFileLastModified(File file, long time) {
0523: ResourceUtils.setLastModified(new FileResource(file), time);
0524: }
0525:
0526: /**
0527: * Interpret the filename as a file relative to the given file
0528: * unless the filename already represents an absolute filename.
0529: * Differs from <code>new File(file, filename)</code> in that
0530: * the resulting File's path will always be a normalized,
0531: * absolute pathname. Also, if it is determined that
0532: * <code>filename</code> is context-relative, <code>file</code>
0533: * will be discarded and the reference will be resolved using
0534: * available context/state information about the filesystem.
0535: *
0536: * @param file the "reference" file for relative paths. This
0537: * instance must be an absolute file and must not contain
0538: * "./" or "../" sequences (same for \ instead
0539: * of /). If it is null, this call is equivalent to
0540: * <code>new java.io.File(filename).getAbsoluteFile()</code>.
0541: *
0542: * @param filename a file name.
0543: *
0544: * @return an absolute file.
0545: * @throws java.lang.NullPointerException if filename is null.
0546: */
0547: public File resolveFile(File file, String filename) {
0548: if (!isAbsolutePath(filename)) {
0549: char sep = File.separatorChar;
0550: filename = filename.replace('/', sep).replace('\\', sep);
0551: if (isContextRelativePath(filename)) {
0552: file = null;
0553: // on cygwin, our current directory can be a UNC;
0554: // assume user.dir is absolute or all hell breaks loose...
0555: String udir = System.getProperty("user.dir");
0556: if (filename.charAt(0) == sep && udir.charAt(0) == sep) {
0557: filename = dissect(udir)[0] + filename.substring(1);
0558: }
0559: }
0560: filename = new File(file, filename).getAbsolutePath();
0561: }
0562: return normalize(filename);
0563: }
0564:
0565: /**
0566: * On DOS and NetWare, the evaluation of certain file
0567: * specifications is context-dependent. These are filenames
0568: * beginning with a single separator (relative to current root directory)
0569: * and filenames with a drive specification and no intervening separator
0570: * (relative to current directory of the specified root).
0571: * @param filename the filename to evaluate.
0572: * @return true if the filename is relative to system context.
0573: * @throws java.lang.NullPointerException if filename is null.
0574: * @since Ant 1.7
0575: */
0576: public static boolean isContextRelativePath(String filename) {
0577: if (!(onDos || onNetWare) || filename.length() == 0) {
0578: return false;
0579: }
0580: char sep = File.separatorChar;
0581: filename = filename.replace('/', sep).replace('\\', sep);
0582: char c = filename.charAt(0);
0583: int len = filename.length();
0584: return (c == sep && (len == 1 || filename.charAt(1) != sep))
0585: || (Character.isLetter(c) && len > 1
0586: && filename.indexOf(':') == 1 && (len == 2 || filename
0587: .charAt(2) != sep));
0588: }
0589:
0590: /**
0591: * Verifies that the specified filename represents an absolute path.
0592: * Differs from new java.io.File("filename").isAbsolute() in that a path
0593: * beginning with a double file separator--signifying a Windows UNC--must
0594: * at minimum match "\\a\b" to be considered an absolute path.
0595: * @param filename the filename to be checked.
0596: * @return true if the filename represents an absolute path.
0597: * @throws java.lang.NullPointerException if filename is null.
0598: * @since Ant 1.6.3
0599: */
0600: public static boolean isAbsolutePath(String filename) {
0601: int len = filename.length();
0602: if (len == 0) {
0603: return false;
0604: }
0605: char sep = File.separatorChar;
0606: filename = filename.replace('/', sep).replace('\\', sep);
0607: char c = filename.charAt(0);
0608: if (!(onDos || onNetWare)) {
0609: return (c == sep);
0610: }
0611: if (c == sep) {
0612: if (!(onDos && len > 4 && filename.charAt(1) == sep)) {
0613: return false;
0614: }
0615: int nextsep = filename.indexOf(sep, 2);
0616: return nextsep > 2 && nextsep + 1 < len;
0617: }
0618: int colon = filename.indexOf(':');
0619: return (Character.isLetter(c) && colon == 1
0620: && filename.length() > 2 && filename.charAt(2) == sep)
0621: || (onNetWare && colon > 0);
0622: }
0623:
0624: /**
0625: * Translate a path into its native (platform specific) format.
0626: * <p>
0627: * This method uses PathTokenizer to separate the input path
0628: * into its components. This handles DOS style paths in a relatively
0629: * sensible way. The file separators are then converted to their platform
0630: * specific versions.
0631: *
0632: * @param toProcess The path to be translated.
0633: * May be <code>null</code>.
0634: *
0635: * @return the native version of the specified path or
0636: * an empty string if the path is <code>null</code> or empty.
0637: *
0638: * @since ant 1.7
0639: * @see PathTokenizer
0640: */
0641: public static String translatePath(String toProcess) {
0642: if (toProcess == null || toProcess.length() == 0) {
0643: return "";
0644: }
0645: StringBuffer path = new StringBuffer(toProcess.length() + 50);
0646: PathTokenizer tokenizer = new PathTokenizer(toProcess);
0647: while (tokenizer.hasMoreTokens()) {
0648: String pathComponent = tokenizer.nextToken();
0649: pathComponent = pathComponent.replace('/',
0650: File.separatorChar);
0651: pathComponent = pathComponent.replace('\\',
0652: File.separatorChar);
0653: if (path.length() != 0) {
0654: path.append(File.pathSeparatorChar);
0655: }
0656: path.append(pathComponent);
0657: }
0658: return path.toString();
0659: }
0660:
0661: /**
0662: * "Normalize" the given absolute path.
0663: *
0664: * <p>This includes:
0665: * <ul>
0666: * <li>Uppercase the drive letter if there is one.</li>
0667: * <li>Remove redundant slashes after the drive spec.</li>
0668: * <li>Resolve all ./, .\, ../ and ..\ sequences.</li>
0669: * <li>DOS style paths that start with a drive letter will have
0670: * \ as the separator.</li>
0671: * </ul>
0672: * Unlike {@link File#getCanonicalPath()} this method
0673: * specifically does not resolve symbolic links.
0674: *
0675: * @param path the path to be normalized.
0676: * @return the normalized version of the path.
0677: *
0678: * @throws java.lang.NullPointerException if path is null.
0679: */
0680: public File normalize(final String path) {
0681: Stack s = new Stack();
0682: String[] dissect = dissect(path);
0683: s.push(dissect[0]);
0684:
0685: StringTokenizer tok = new StringTokenizer(dissect[1],
0686: File.separator);
0687: while (tok.hasMoreTokens()) {
0688: String this Token = tok.nextToken();
0689: if (".".equals(this Token)) {
0690: continue;
0691: } else if ("..".equals(this Token)) {
0692: if (s.size() < 2) {
0693: // Cannot resolve it, so skip it.
0694: return new File(path);
0695: }
0696: s.pop();
0697: } else { // plain component
0698: s.push(this Token);
0699: }
0700: }
0701: StringBuffer sb = new StringBuffer();
0702: for (int i = 0; i < s.size(); i++) {
0703: if (i > 1) {
0704: // not before the filesystem root and not after it, since root
0705: // already contains one
0706: sb.append(File.separatorChar);
0707: }
0708: sb.append(s.elementAt(i));
0709: }
0710: return new File(sb.toString());
0711: }
0712:
0713: /**
0714: * Dissect the specified absolute path.
0715: * @param path the path to dissect.
0716: * @return String[] {root, remaining path}.
0717: * @throws java.lang.NullPointerException if path is null.
0718: * @since Ant 1.7
0719: */
0720: public String[] dissect(String path) {
0721: char sep = File.separatorChar;
0722: path = path.replace('/', sep).replace('\\', sep);
0723:
0724: // make sure we are dealing with an absolute path
0725: if (!isAbsolutePath(path)) {
0726: throw new BuildException(path + " is not an absolute path");
0727: }
0728: String root = null;
0729: int colon = path.indexOf(':');
0730: if (colon > 0 && (onDos || onNetWare)) {
0731:
0732: int next = colon + 1;
0733: root = path.substring(0, next);
0734: char[] ca = path.toCharArray();
0735: root += sep;
0736: //remove the initial separator; the root has it.
0737: next = (ca[next] == sep) ? next + 1 : next;
0738:
0739: StringBuffer sbPath = new StringBuffer();
0740: // Eliminate consecutive slashes after the drive spec:
0741: for (int i = next; i < ca.length; i++) {
0742: if (ca[i] != sep || ca[i - 1] != sep) {
0743: sbPath.append(ca[i]);
0744: }
0745: }
0746: path = sbPath.toString();
0747: } else if (path.length() > 1 && path.charAt(1) == sep) {
0748: // UNC drive
0749: int nextsep = path.indexOf(sep, 2);
0750: nextsep = path.indexOf(sep, nextsep + 1);
0751: root = (nextsep > 2) ? path.substring(0, nextsep + 1)
0752: : path;
0753: path = path.substring(root.length());
0754: } else {
0755: root = File.separator;
0756: path = path.substring(1);
0757: }
0758: return new String[] { root, path };
0759: }
0760:
0761: /**
0762: * Returns a VMS String representation of a <code>File</code> object.
0763: * This is useful since the JVM by default internally converts VMS paths
0764: * to Unix style.
0765: * The returned String is always an absolute path.
0766: *
0767: * @param f The <code>File</code> to get the VMS path for.
0768: * @return The absolute VMS path to <code>f</code>.
0769: */
0770: public String toVMSPath(File f) {
0771: // format: "DEVICE:[DIR.SUBDIR]FILE"
0772: String osPath;
0773: String path = normalize(f.getAbsolutePath()).getPath();
0774: String name = f.getName();
0775: boolean isAbsolute = path.charAt(0) == File.separatorChar;
0776: // treat directories specified using .DIR syntax as files
0777: boolean isDirectory = f.isDirectory()
0778: && !name.regionMatches(true, name.length() - 4, ".DIR",
0779: 0, 4);
0780:
0781: String device = null;
0782: StringBuffer directory = null;
0783: String file = null;
0784:
0785: int index = 0;
0786:
0787: if (isAbsolute) {
0788: index = path.indexOf(File.separatorChar, 1);
0789: if (index == -1) {
0790: return path.substring(1) + ":[000000]";
0791: } else {
0792: device = path.substring(1, index++);
0793: }
0794: }
0795: if (isDirectory) {
0796: directory = new StringBuffer(path.substring(index).replace(
0797: File.separatorChar, '.'));
0798: } else {
0799: int dirEnd = path.lastIndexOf(File.separatorChar, path
0800: .length());
0801: if (dirEnd == -1 || dirEnd < index) {
0802: file = path.substring(index);
0803: } else {
0804: directory = new StringBuffer(path.substring(index,
0805: dirEnd).replace(File.separatorChar, '.'));
0806: index = dirEnd + 1;
0807: if (path.length() > index) {
0808: file = path.substring(index);
0809: }
0810: }
0811: }
0812: if (!isAbsolute && directory != null) {
0813: directory.insert(0, '.');
0814: }
0815: osPath = ((device != null) ? device + ":" : "")
0816: + ((directory != null) ? "[" + directory + "]" : "")
0817: + ((file != null) ? file : "");
0818: return osPath;
0819: }
0820:
0821: /**
0822: * Create a temporary file in a given directory.
0823: *
0824: * <p>The file denoted by the returned abstract pathname did not
0825: * exist before this method was invoked, any subsequent invocation
0826: * of this method will yield a different file name.</p>
0827: * <p>
0828: * The filename is prefixNNNNNsuffix where NNNN is a random number.
0829: * </p>
0830: * <p>This method is different from File.createTempFile() of JDK 1.2
0831: * as it doesn't create the file itself. It uses the location pointed
0832: * to by java.io.tmpdir when the parentDir attribute is null.</p>
0833: *
0834: * @param prefix prefix before the random number.
0835: * @param suffix file extension; include the '.'.
0836: * @param parentDir Directory to create the temporary file in;
0837: * java.io.tmpdir used if not specified.
0838: *
0839: * @return a File reference to the new temporary file.
0840: * @since Ant 1.5
0841: */
0842: public File createTempFile(String prefix, String suffix,
0843: File parentDir) {
0844: return createTempFile(prefix, suffix, parentDir, false);
0845: }
0846:
0847: /**
0848: * Create a temporary file in a given directory.
0849: *
0850: * <p>The file denoted by the returned abstract pathname did not
0851: * exist before this method was invoked, any subsequent invocation
0852: * of this method will yield a different file name.</p>
0853: * <p>
0854: * The filename is prefixNNNNNsuffix where NNNN is a random number.
0855: * </p>
0856: * <p>This method is different from File.createTempFile() of JDK 1.2
0857: * as it doesn't create the file itself. It uses the location pointed
0858: * to by java.io.tmpdir when the parentDir attribute is null.</p>
0859: *
0860: * @param prefix prefix before the random number.
0861: * @param suffix file extension; include the '.'.
0862: * @param parentDir Directory to create the temporary file in;
0863: * @param deleteOnExit whether to set the tempfile for deletion on
0864: * normal VM exit.
0865: * java.io.tmpdir used if not specified.
0866: *
0867: * @return a File reference to the new temporary file.
0868: * @since Ant 1.7
0869: */
0870: public File createTempFile(String prefix, String suffix,
0871: File parentDir, boolean deleteOnExit) {
0872: File result = null;
0873: String parent = (parentDir == null) ? System
0874: .getProperty("java.io.tmpdir") : parentDir.getPath();
0875:
0876: DecimalFormat fmt = new DecimalFormat("#####");
0877: synchronized (rand) {
0878: do {
0879: result = new File(parent, prefix
0880: + fmt.format(Math.abs(rand.nextInt())) + suffix);
0881: } while (result.exists());
0882: }
0883: if (deleteOnExit) {
0884: result.deleteOnExit();
0885: }
0886: return result;
0887: }
0888:
0889: /**
0890: * Compares the contents of two files.
0891: *
0892: * @param f1 the file whose content is to be compared.
0893: * @param f2 the other file whose content is to be compared.
0894: *
0895: * @return true if the content of the files is the same.
0896: *
0897: * @throws IOException if the files cannot be read.
0898: */
0899: public boolean contentEquals(File f1, File f2) throws IOException {
0900: return contentEquals(f1, f2, false);
0901: }
0902:
0903: /**
0904: * Compares the contents of two files.
0905: *
0906: * @param f1 the file whose content is to be compared.
0907: * @param f2 the other file whose content is to be compared.
0908: * @param textfile true if the file is to be treated as a text file and
0909: * differences in kind of line break are to be ignored.
0910: *
0911: * @return true if the content of the files is the same.
0912: *
0913: * @throws IOException if the files cannot be read.
0914: * @since Ant 1.6.3
0915: */
0916: public boolean contentEquals(File f1, File f2, boolean textfile)
0917: throws IOException {
0918: return ResourceUtils.contentEquals(new FileResource(f1),
0919: new FileResource(f2), textfile);
0920: }
0921:
0922: /**
0923: * This was originally an emulation of {@link File#getParentFile} for JDK 1.1,
0924: * but it is now implemented using that method (Ant 1.6.3 onwards).
0925: * @param f the file whose parent is required.
0926: * @return the given file's parent, or null if the file does not have a
0927: * parent.
0928: * @since 1.10
0929: * @deprecated since 1.7.
0930: * Just use {@link File#getParentFile} directly.
0931: */
0932: public File getParentFile(File f) {
0933: return (f == null) ? null : f.getParentFile();
0934: }
0935:
0936: /**
0937: * Read from reader till EOF.
0938: * @param rdr the reader from which to read.
0939: * @return the contents read out of the given reader.
0940: *
0941: * @throws IOException if the contents could not be read out from the
0942: * reader.
0943: */
0944: public static final String readFully(Reader rdr) throws IOException {
0945: return readFully(rdr, BUF_SIZE);
0946: }
0947:
0948: /**
0949: * Read from reader till EOF.
0950: *
0951: * @param rdr the reader from which to read.
0952: * @param bufferSize the buffer size to use when reading.
0953: *
0954: * @return the contents read out of the given reader.
0955: *
0956: * @throws IOException if the contents could not be read out from the
0957: * reader.
0958: */
0959: public static final String readFully(Reader rdr, int bufferSize)
0960: throws IOException {
0961: if (bufferSize <= 0) {
0962: throw new IllegalArgumentException(
0963: "Buffer size must be greater " + "than 0");
0964: }
0965: final char[] buffer = new char[bufferSize];
0966: int bufferLength = 0;
0967: StringBuffer textBuffer = null;
0968: while (bufferLength != -1) {
0969: bufferLength = rdr.read(buffer);
0970: if (bufferLength > 0) {
0971: textBuffer = (textBuffer == null) ? new StringBuffer()
0972: : textBuffer;
0973: textBuffer.append(new String(buffer, 0, bufferLength));
0974: }
0975: }
0976: return (textBuffer == null) ? null : textBuffer.toString();
0977: }
0978:
0979: /**
0980: * This was originally an emulation of File.createNewFile for JDK 1.1,
0981: * but it is now implemented using that method (Ant 1.6.3 onwards).
0982: *
0983: * <p>This method has historically <strong>not</strong> guaranteed that the
0984: * operation was atomic. In its current implementation it is.
0985: *
0986: * @param f the file to be created.
0987: * @return true if the file did not exist already.
0988: * @throws IOException on error.
0989: * @since Ant 1.5
0990: */
0991: public boolean createNewFile(File f) throws IOException {
0992: return f.createNewFile();
0993: }
0994:
0995: /**
0996: * Create a new file, optionally creating parent directories.
0997: *
0998: * @param f the file to be created.
0999: * @param mkdirs <code>boolean</code> whether to create parent directories.
1000: * @return true if the file did not exist already.
1001: * @throws IOException on error.
1002: * @since Ant 1.6.3
1003: */
1004: public boolean createNewFile(File f, boolean mkdirs)
1005: throws IOException {
1006: File parent = f.getParentFile();
1007: if (mkdirs && !(parent.exists())) {
1008: parent.mkdirs();
1009: }
1010: return f.createNewFile();
1011: }
1012:
1013: /**
1014: * Checks whether a given file is a symbolic link.
1015: *
1016: * <p>It doesn't really test for symbolic links but whether the
1017: * canonical and absolute paths of the file are identical--this
1018: * may lead to false positives on some platforms.</p>
1019: *
1020: * @param parent the parent directory of the file to test
1021: * @param name the name of the file to test.
1022: *
1023: * @return true if the file is a symbolic link.
1024: * @throws IOException on error.
1025: * @since Ant 1.5
1026: */
1027: public boolean isSymbolicLink(File parent, String name)
1028: throws IOException {
1029: if (parent == null) {
1030: File f = new File(name);
1031: parent = f.getParentFile();
1032: name = f.getName();
1033: }
1034: File toTest = new File(parent.getCanonicalPath(), name);
1035: return !toTest.getAbsolutePath().equals(
1036: toTest.getCanonicalPath());
1037: }
1038:
1039: /**
1040: * Removes a leading path from a second path.
1041: *
1042: * @param leading The leading path, must not be null, must be absolute.
1043: * @param path The path to remove from, must not be null, must be absolute.
1044: *
1045: * @return path's normalized absolute if it doesn't start with
1046: * leading; path's path with leading's path removed otherwise.
1047: *
1048: * @since Ant 1.5
1049: */
1050: public String removeLeadingPath(File leading, File path) {
1051: String l = normalize(leading.getAbsolutePath())
1052: .getAbsolutePath();
1053: String p = normalize(path.getAbsolutePath()).getAbsolutePath();
1054: if (l.equals(p)) {
1055: return "";
1056: }
1057:
1058: // ensure that l ends with a /
1059: // so we never think /foo was a parent directory of /foobar
1060: if (!l.endsWith(File.separator)) {
1061: l += File.separator;
1062: }
1063: return (p.startsWith(l)) ? p.substring(l.length()) : p;
1064: }
1065:
1066: /**
1067: * Learn whether one path "leads" another.
1068: * @param leading The leading path, must not be null, must be absolute.
1069: * @param path The path to remove from, must not be null, must be absolute.
1070: * @return true if path starts with leading; false otherwise.
1071: * @since Ant 1.7
1072: */
1073: public boolean isLeadingPath(File leading, File path) {
1074: String l = normalize(leading.getAbsolutePath())
1075: .getAbsolutePath();
1076: String p = normalize(path.getAbsolutePath()).getAbsolutePath();
1077: if (l.equals(p)) {
1078: return true;
1079: }
1080: // ensure that l ends with a /
1081: // so we never think /foo was a parent directory of /foobar
1082: if (!l.endsWith(File.separator)) {
1083: l += File.separator;
1084: }
1085: return p.startsWith(l);
1086: }
1087:
1088: /**
1089: * Constructs a <code>file:</code> URI that represents the
1090: * external form of the given pathname.
1091: *
1092: * <p>Will be an absolute URI if the given path is absolute.</p>
1093: *
1094: * <p>This code encodes non ASCII characters too.</p>
1095: *
1096: * <p>The coding of the output is the same as what File.toURI().toASCIIString() produces</p>
1097: *
1098: * See <a href="http://www.w3.org/TR/xml11/#dt-sysid">dt-sysid</a>
1099: * which makes some mention of how
1100: * characters not supported by URI Reference syntax should be escaped.
1101: *
1102: * @param path the path in the local file system.
1103: * @return the URI version of the local path.
1104: * @since Ant 1.6
1105: */
1106: public String toURI(String path) {
1107: // #8031: first try Java 1.4.
1108: Class uriClazz = null;
1109: try {
1110: uriClazz = Class.forName("java.net.URI");
1111: } catch (ClassNotFoundException e) {
1112: // OK, Java 1.3.
1113: }
1114: if (uriClazz != null) {
1115: try {
1116: File f = new File(path).getAbsoluteFile();
1117: java.lang.reflect.Method toURIMethod = File.class
1118: .getMethod("toURI", new Class[0]);
1119: Object uriObj = toURIMethod.invoke(f, new Object[] {});
1120: java.lang.reflect.Method toASCIIStringMethod = uriClazz
1121: .getMethod("toASCIIString", new Class[0]);
1122: return (String) toASCIIStringMethod.invoke(uriObj,
1123: new Object[] {});
1124: } catch (Exception e) {
1125: // Reflection problems? Should not happen, debug.
1126: e.printStackTrace();
1127: }
1128: }
1129: boolean isDir = new File(path).isDirectory();
1130:
1131: StringBuffer sb = new StringBuffer("file:");
1132:
1133: path = resolveFile(null, path).getPath();
1134: sb.append("//");
1135: // add an extra slash for filesystems with drive-specifiers
1136: if (!path.startsWith(File.separator)) {
1137: sb.append("/");
1138: }
1139: path = path.replace('\\', '/');
1140: try {
1141: sb.append(Locator.encodeURI(path));
1142: } catch (UnsupportedEncodingException exc) {
1143: throw new BuildException(exc);
1144: }
1145: if (isDir && !path.endsWith("/")) {
1146: sb.append('/');
1147: }
1148: return sb.toString();
1149: }
1150:
1151: /**
1152: * Constructs a file path from a <code>file:</code> URI.
1153: *
1154: * <p>Will be an absolute path if the given URI is absolute.</p>
1155: *
1156: * <p>Swallows '%' that are not followed by two characters,
1157: * doesn't deal with non-ASCII characters.</p>
1158: *
1159: * @param uri the URI designating a file in the local filesystem.
1160: * @return the local file system path for the file.
1161: * @since Ant 1.6
1162: */
1163: public String fromURI(String uri) {
1164: synchronized (cacheFromUriLock) {
1165: if (uri.equals(cacheFromUriRequest)) {
1166: return cacheFromUriResponse;
1167: }
1168: String path = Locator.fromURI(uri);
1169: String ret = isAbsolutePath(path) ? normalize(path)
1170: .getAbsolutePath() : path;
1171: cacheFromUriRequest = uri;
1172: cacheFromUriResponse = ret;
1173: return ret;
1174: }
1175: }
1176:
1177: /**
1178: * Compares two filenames.
1179: *
1180: * <p>Unlike java.io.File#equals this method will try to compare
1181: * the absolute paths and "normalize" the filenames
1182: * before comparing them.</p>
1183: *
1184: * @param f1 the file whose name is to be compared.
1185: * @param f2 the other file whose name is to be compared.
1186: *
1187: * @return true if the file are for the same file.
1188: *
1189: * @since Ant 1.5.3
1190: */
1191: public boolean fileNameEquals(File f1, File f2) {
1192: return normalize(f1.getAbsolutePath()).equals(
1193: normalize(f2.getAbsolutePath()));
1194: }
1195:
1196: /**
1197: * Renames a file, even if that involves crossing file system boundaries.
1198: *
1199: * <p>This will remove <code>to</code> (if it exists), ensure that
1200: * <code>to</code>'s parent directory exists and move
1201: * <code>from</code>, which involves deleting <code>from</code> as
1202: * well.</p>
1203: *
1204: * @param from the file to move.
1205: * @param to the new file name.
1206: *
1207: * @throws IOException if anything bad happens during this
1208: * process. Note that <code>to</code> may have been deleted
1209: * already when this happens.
1210: *
1211: * @since Ant 1.6
1212: */
1213: public void rename(File from, File to) throws IOException {
1214: if (to.exists() && !to.delete()) {
1215: throw new IOException("Failed to delete " + to
1216: + " while trying to rename " + from);
1217: }
1218: File parent = to.getParentFile();
1219: if (parent != null && !parent.exists() && !parent.mkdirs()) {
1220: throw new IOException("Failed to create directory "
1221: + parent + " while trying to rename " + from);
1222: }
1223: if (!from.renameTo(to)) {
1224: copyFile(from, to);
1225: if (!from.delete()) {
1226: throw new IOException("Failed to delete " + from
1227: + " while trying to rename it.");
1228: }
1229: }
1230: }
1231:
1232: /**
1233: * Get the granularity of file timestamps.
1234: * The choice is made based on OS, which is incorrect--it should really be
1235: * by filesystem. We do not have an easy way to probe for file systems,
1236: * however, so this heuristic gives us a decent default.
1237: * @return the difference, in milliseconds, which two file timestamps must have
1238: * in order for the two files to be considered to have different timestamps.
1239: */
1240: public long getFileTimestampGranularity() {
1241: if (onWin9x) {
1242: return FAT_FILE_TIMESTAMP_GRANULARITY;
1243: } else if (onWindows) {
1244: return NTFS_FILE_TIMESTAMP_GRANULARITY;
1245: } else if (onDos) {
1246: return FAT_FILE_TIMESTAMP_GRANULARITY;
1247: }
1248: return UNIX_FILE_TIMESTAMP_GRANULARITY;
1249: }
1250:
1251: /**
1252: * Returns true if the source is older than the dest.
1253: * If the dest file does not exist, then the test returns false; it is
1254: * implicitly not up do date.
1255: * @param source source file (should be the older).
1256: * @param dest dest file (should be the newer).
1257: * @param granularity an offset added to the source time.
1258: * @return true if the source is older than the dest after accounting
1259: * for granularity.
1260: * @since Ant 1.6.3
1261: */
1262: public boolean isUpToDate(File source, File dest, long granularity) {
1263: //do a check for the destination file existing
1264: if (!dest.exists()) {
1265: //if it does not, then the file is not up to date.
1266: return false;
1267: }
1268: long sourceTime = source.lastModified();
1269: long destTime = dest.lastModified();
1270: return isUpToDate(sourceTime, destTime, granularity);
1271: }
1272:
1273: /**
1274: * Returns true if the source is older than the dest.
1275: * @param source source file (should be the older).
1276: * @param dest dest file (should be the newer).
1277: * @return true if the source is older than the dest, taking the granularity into account.
1278: * @since Ant 1.6.3
1279: */
1280: public boolean isUpToDate(File source, File dest) {
1281: return isUpToDate(source, dest, getFileTimestampGranularity());
1282: }
1283:
1284: /**
1285: * Compare two timestamps for being up to date using
1286: * the specified granularity.
1287: *
1288: * @param sourceTime timestamp of source file.
1289: * @param destTime timestamp of dest file.
1290: * @param granularity os/filesys granularity.
1291: * @return true if the dest file is considered up to date.
1292: */
1293: public boolean isUpToDate(long sourceTime, long destTime,
1294: long granularity) {
1295: if (destTime == -1) {
1296: return false;
1297: }
1298: return destTime >= sourceTime + granularity;
1299: }
1300:
1301: /**
1302: * Compare two timestamps for being up to date using the
1303: * current granularity.
1304: *
1305: * @param sourceTime timestamp of source file.
1306: * @param destTime timestamp of dest file.
1307: * @return true if the dest file is considered up to date.
1308: */
1309: public boolean isUpToDate(long sourceTime, long destTime) {
1310: return isUpToDate(sourceTime, destTime,
1311: getFileTimestampGranularity());
1312: }
1313:
1314: /**
1315: * Close a Writer without throwing any exception if something went wrong.
1316: * Do not attempt to close it if the argument is null.
1317: * @param device output writer, can be null.
1318: */
1319: public static void close(Writer device) {
1320: if (device != null) {
1321: try {
1322: device.close();
1323: } catch (IOException ioex) {
1324: //ignore
1325: }
1326: }
1327: }
1328:
1329: /**
1330: * Close a stream without throwing any exception if something went wrong.
1331: * Do not attempt to close it if the argument is null.
1332: *
1333: * @param device Reader, can be null.
1334: */
1335: public static void close(Reader device) {
1336: if (device != null) {
1337: try {
1338: device.close();
1339: } catch (IOException ioex) {
1340: //ignore
1341: }
1342: }
1343: }
1344:
1345: /**
1346: * Close a stream without throwing any exception if something went wrong.
1347: * Do not attempt to close it if the argument is null.
1348: *
1349: * @param device stream, can be null.
1350: */
1351: public static void close(OutputStream device) {
1352: if (device != null) {
1353: try {
1354: device.close();
1355: } catch (IOException ioex) {
1356: //ignore
1357: }
1358: }
1359: }
1360:
1361: /**
1362: * Close a stream without throwing any exception if something went wrong.
1363: * Do not attempt to close it if the argument is null.
1364: *
1365: * @param device stream, can be null.
1366: */
1367: public static void close(InputStream device) {
1368: if (device != null) {
1369: try {
1370: device.close();
1371: } catch (IOException ioex) {
1372: //ignore
1373: }
1374: }
1375: }
1376:
1377: /**
1378: * Delete the file with {@link File#delete()} if the argument is not null.
1379: * Do nothing on a null argument.
1380: * @param file file to delete.
1381: */
1382: public static void delete(File file) {
1383: if (file != null) {
1384: file.delete();
1385: }
1386: }
1387:
1388: /**
1389: * Calculates the relative path between two files.
1390: * <p>
1391: * Implementation note:<br/> This function my throw an IOException if an
1392: * I/O error occurs because its use of the canonical pathname may require
1393: * filesystem queries.
1394: * </p>
1395: *
1396: * @param fromFile
1397: * the <code>File</code> to calculate the path from
1398: * @param toFile
1399: * the <code>File</code> to calculate the path to
1400: * @return the relative path between the files
1401: * @throws Exception for undocumented reasons
1402: * @see File#getCanonicalPath()
1403: *
1404: * @since Ant 1.7
1405: */
1406: public static String getRelativePath(File fromFile, File toFile)
1407: throws Exception {
1408: String fromPath = fromFile.getCanonicalPath();
1409: String toPath = toFile.getCanonicalPath();
1410:
1411: // build the path stack info to compare
1412: String[] fromPathStack = getPathStack(fromPath);
1413: String[] toPathStack = getPathStack(toPath);
1414:
1415: if (0 < toPathStack.length && 0 < fromPathStack.length) {
1416: if (!fromPathStack[0].equals(toPathStack[0])) {
1417: // not the same device (would be "" on Linux/Unix)
1418:
1419: return getPath(Arrays.asList(toPathStack));
1420: }
1421: } else {
1422: // no comparison possible
1423: return getPath(Arrays.asList(toPathStack));
1424: }
1425:
1426: int minLength = Math.min(fromPathStack.length,
1427: toPathStack.length);
1428:
1429: int same = 1;
1430:
1431: // get index of parts which are equal
1432: for (; same < minLength; same++) {
1433: if (!fromPathStack[same].equals(toPathStack[same])) {
1434: break;
1435: }
1436: }
1437:
1438: List relativePathStack = new ArrayList();
1439:
1440: // if "from" part is longer, fill it up with ".."
1441: // to reach path which is equal to both paths
1442: for (int i = same; i < fromPathStack.length; i++) {
1443: relativePathStack.add("..");
1444: }
1445:
1446: // fill it up path with parts which were not equal
1447: for (int i = same; i < toPathStack.length; i++) {
1448: relativePathStack.add(toPathStack[i]);
1449: }
1450:
1451: return getPath(relativePathStack);
1452: }
1453:
1454: /**
1455: * Gets all names of the path as an array of <code>String</code>s.
1456: *
1457: * @param path
1458: * to get names from
1459: * @return <code>String</code>s, never <code>null</code>
1460: *
1461: * @since Ant 1.7
1462: */
1463: public static String[] getPathStack(String path) {
1464: String normalizedPath = path.replace(File.separatorChar, '/');
1465:
1466: // since Java 1.4
1467: //return normalizedPath.split("/");
1468: // workaround for Java 1.2-1.3
1469: Object[] tokens = StringUtils.split(normalizedPath, '/')
1470: .toArray();
1471: String[] rv = new String[tokens.length];
1472: System.arraycopy(tokens, 0, rv, 0, tokens.length);
1473:
1474: return rv;
1475: }
1476:
1477: /**
1478: * Gets path from a <code>List</code> of <code>String</code>s.
1479: *
1480: * @param pathStack
1481: * <code>List</code> of <code>String</code>s to be concated
1482: * as a path.
1483: * @return <code>String</code>, never <code>null</code>
1484: *
1485: * @since Ant 1.7
1486: */
1487: public static String getPath(List pathStack) {
1488: // can safely use '/' because Windows understands '/' as separator
1489: return getPath(pathStack, '/');
1490: }
1491:
1492: /**
1493: * Gets path from a <code>List</code> of <code>String</code>s.
1494: *
1495: * @param pathStack
1496: * <code>List</code> of <code>String</code>s to be concated
1497: * as a path.
1498: * @param separatorChar
1499: * <code>char</code> to be used as separator between names in
1500: * path
1501: * @return <code>String</code>, never <code>null</code>
1502: *
1503: * @since Ant 1.7
1504: */
1505: public static String getPath(final List pathStack,
1506: final char separatorChar) {
1507: final StringBuffer buffer = new StringBuffer();
1508:
1509: final Iterator iter = pathStack.iterator();
1510: if (iter.hasNext()) {
1511: buffer.append(iter.next());
1512: }
1513:
1514: while (iter.hasNext()) {
1515: buffer.append(separatorChar);
1516: buffer.append(iter.next());
1517: }
1518:
1519: return buffer.toString();
1520: }
1521:
1522: /**
1523: * Get the default encoding.
1524: * This is done by opening an InputStreamReader on
1525: * a dummy InputStream and getting the encoding.
1526: * Could use System.getProperty("file.encoding"), but cannot
1527: * see where this is documented.
1528: * @return the default file encoding.
1529: */
1530: public String getDefaultEncoding() {
1531: InputStreamReader is = new InputStreamReader(new InputStream() {
1532: public int read() {
1533: return -1;
1534: }
1535: });
1536: try {
1537: return is.getEncoding();
1538: } finally {
1539: close(is);
1540: }
1541: }
1542: }
|