diff --git a/src/main/org/apache/tools/ant/Project.java b/src/main/org/apache/tools/ant/Project.java
index ef3422092..e6f759326 100644
--- a/src/main/org/apache/tools/ant/Project.java
+++ b/src/main/org/apache/tools/ant/Project.java
@@ -339,11 +339,12 @@ public class Project {
}
public void setBaseDir(File baseDir) throws BuildException {
+ baseDir = fileUtils.normalize(baseDir.getAbsolutePath());
if (!baseDir.exists())
throw new BuildException("Basedir " + baseDir.getAbsolutePath() + " does not exist");
if (!baseDir.isDirectory())
throw new BuildException("Basedir " + baseDir.getAbsolutePath() + " is not a directory");
- this.baseDir = new File(baseDir.getAbsolutePath());
+ this.baseDir = baseDir;
setProperty( "basedir", this.baseDir.getPath());
String msg = "Project base dir set to: " + this.baseDir;
log(msg, MSG_VERBOSE);
@@ -594,66 +595,15 @@ public class Project {
*
*
If fileName is a relative file name, resolve it relative to
* rootDir.
+ *
+ * @deprecated
*/
public File resolveFile(String fileName, File rootDir) {
- fileName = fileName.replace('/', File.separatorChar).replace('\\', File.separatorChar);
-
- // deal with absolute files
- if (fileName.startsWith(File.separator)) {
- return new File(fileName);
- }
-
- // Eliminate consecutive slashes after the drive spec
- if (fileName.length() >= 2 &&
- Character.isLetter(fileName.charAt(0)) &&
- fileName.charAt(1) == ':') {
- char[] ca = fileName.replace('/', '\\').toCharArray();
- char c;
- StringBuffer sb = new StringBuffer();
-
- for (int i = 0; i < ca.length; i++) {
- if ((ca[i] != '\\') ||
- (ca[i] == '\\' &&
- i > 0 &&
- ca[i - 1] != '\\')) {
- if (i == 0 &&
- Character.isLetter(ca[i]) &&
- i < ca.length - 1 &&
- ca[i + 1] == ':') {
- c = Character.toUpperCase(ca[i]);
- } else {
- c = ca[i];
- }
-
- sb.append(c);
- }
- }
-
- return new File(sb.toString());
- }
-
- File file = new File(rootDir.getAbsolutePath());
- StringTokenizer tok = new StringTokenizer(fileName, File.separator, false);
- while (tok.hasMoreTokens()) {
- String part = tok.nextToken();
- if (part.equals("..")) {
- String parentFile = file.getParent();
- if (parentFile == null) {
- throw new BuildException("The file or path you specified (" + fileName + ") is invalid relative to " + rootDir.getAbsolutePath());
- }
- file = new File(parentFile);
- } else if (part.equals(".")) {
- // Do nothing here
- } else {
- file = new File(file, part);
- }
- }
-
- return new File(file.getAbsolutePath());
+ return fileUtils.resolveFile(rootDir, fileName);
}
public File resolveFile(String fileName) {
- return resolveFile(fileName, baseDir);
+ return fileUtils.resolveFile(baseDir, fileName);
}
/**
diff --git a/src/main/org/apache/tools/ant/taskdefs/XSLTProcess.java b/src/main/org/apache/tools/ant/taskdefs/XSLTProcess.java
index d53817cb2..9496c32e0 100644
--- a/src/main/org/apache/tools/ant/taskdefs/XSLTProcess.java
+++ b/src/main/org/apache/tools/ant/taskdefs/XSLTProcess.java
@@ -63,6 +63,7 @@ import java.util.Vector;
import org.apache.tools.ant.*;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.Reference;
+import org.apache.tools.ant.util.FileUtils;
/**
@@ -111,10 +112,13 @@ public class XSLTProcess extends MatchingTask {
private boolean force = false;
+ private FileUtils fileUtils;
+
/**
* Creates a new XSLTProcess Task.
**/
public XSLTProcess() {
+ fileUtils = FileUtils.newFileUtils();
} //-- XSLTProcess
/**
@@ -137,9 +141,9 @@ public class XSLTProcess extends MatchingTask {
liaison = getLiaison();
log("Using "+liaison.getClass().toString(), Project.MSG_VERBOSE);
- File stylesheet = project.resolveFile(xslFile, project.getBaseDir());
+ File stylesheet = project.resolveFile(xslFile);
if (!stylesheet.exists()) {
- stylesheet = project.resolveFile(xslFile, baseDir);
+ stylesheet = fileUtils.resolveFile(baseDir, xslFile);
/*
* shouldn't throw out deprecation warnings before we know,
* the wrong version has been used.
diff --git a/src/main/org/apache/tools/ant/util/FileUtils.java b/src/main/org/apache/tools/ant/util/FileUtils.java
index 2b42e9e37..03d3b9691 100644
--- a/src/main/org/apache/tools/ant/util/FileUtils.java
+++ b/src/main/org/apache/tools/ant/util/FileUtils.java
@@ -56,22 +56,22 @@ package org.apache.tools.ant.util;
import java.io.*;
import java.lang.reflect.Method;
+import java.util.StringTokenizer;
+import java.util.Stack;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.types.FilterSet;
/**
- * Central representation of an Ant project. This class defines a
- * Ant project with all of it's targets and tasks. It also provides
- * the mechanism to kick off a build using a particular target name.
- *
- * This class also encapsulates methods which allow Files to be refered
- * to using abstract path names which are translated to native system
- * file paths at runtime as well as defining various project properties.
+ * This class also encapsulates methods which allow Files to be
+ * refered to using abstract path names which are translated to native
+ * system file paths at runtime as well as copying files or setting
+ * there last modification time.
*
* @author duncan@x180.com
* @author Conor MacNeill
+ * @author Stefan Bodewig
*/
public class FileUtils {
@@ -292,5 +292,170 @@ public class FileUtils {
}
}
+ /**
+ * Interpret the filename as a file relative to the given file -
+ * unless the filename already represents an absolute filename.
+ *
+ * @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 /).
+ * @param filename a file name
+ *
+ * @return an absolute file that doesn't contain "./" or
+ * "../" sequences and uses the correct separator for
+ * the current platform.
+ */
+ public File resolveFile(File file, String filename) {
+ filename = filename.replace('/', File.separatorChar)
+ .replace('\\', File.separatorChar);
+
+ // deal with absolute files
+ if (filename.startsWith(File.separator) ||
+
+ (filename.length() >= 2 &&
+ Character.isLetter(filename.charAt(0)) &&
+ filename.charAt(1) == ':')
+
+ ) {
+ return normalize(filename);
+ }
+
+ if (filename.length() >= 2 &&
+ Character.isLetter(filename.charAt(0)) &&
+ filename.charAt(1) == ':') {
+ return normalize(filename);
+ }
+
+ File helpFile = new File(file.getAbsolutePath());
+ StringTokenizer tok = new StringTokenizer(filename, File.separator);
+ while (tok.hasMoreTokens()) {
+ String part = tok.nextToken();
+ if (part.equals("..")) {
+ String parentFile = helpFile.getParent();
+ if (parentFile == null) {
+ String msg = "The file or path you specified ("
+ + filename + ") is invalid relative to "
+ + file.getPath();
+ throw new BuildException(msg);
+ }
+ helpFile = new File(parentFile);
+ } else if (part.equals(".")) {
+ // Do nothing here
+ } else {
+ helpFile = new File(helpFile, part);
+ }
+ }
+
+ return new File(helpFile.getAbsolutePath());
+ }
+
+ /**
+ * "normalize" the given absolute path.
+ *
+ *
This includes:
+ *
+ * - Uppercase the drive letter if there is one.
+ * - Remove redundant slashes after the drive spec.
+ * - resolve all ./, .\, ../ and ..\ sequences.
+ * - DOS style paths that start with a drive letter will have
+ * \ as the separator.
+ *
+ */
+ public File normalize(String path) {
+ String orig = path;
+
+ path = path.replace('/', File.separatorChar)
+ .replace('\\', File.separatorChar);
+
+ // make sure we are dealing with an absolute path
+ if (!path.startsWith(File.separator) &&
+ ! (path.length() >= 2 &&
+ Character.isLetter(path.charAt(0)) &&
+ path.charAt(1) == ':')
+ ) {
+ String msg = path + " is not an absolute path";
+ throw new BuildException(msg);
+ }
+
+ boolean dosWithDrive = false;
+ String root = null;
+ // Eliminate consecutive slashes after the drive spec
+ if (path.length() >= 2 &&
+ Character.isLetter(path.charAt(0)) &&
+ path.charAt(1) == ':') {
+
+ dosWithDrive = true;
+
+ char[] ca = path.replace('/', '\\').toCharArray();
+ StringBuffer sb = new StringBuffer();
+ sb.append(Character.toUpperCase(ca[0])).append(':');
+
+ for (int i = 2; i < ca.length; i++) {
+ if ((ca[i] != '\\') ||
+ (ca[i] == '\\' && ca[i - 1] != '\\')
+ ) {
+ sb.append(ca[i]);
+ }
+ }
+
+ path = sb.toString().replace('\\', File.separatorChar);
+ if (path.length() == 2) {
+ root = path;
+ path = "";
+ } else {
+ root = path.substring(0, 3);
+ path = path.substring(3);
+ }
+
+ } else {
+ if (path.length() == 1) {
+ root = File.separator;
+ path = "";
+ } else if (path.charAt(1) == File.separatorChar) {
+ // UNC drive
+ root = File.separator+File.separator;
+ path = path.substring(2);
+ } else {
+ root = File.separator;
+ path = path.substring(1);
+ }
+ }
+
+ Stack s = new Stack();
+ s.push(root);
+ StringTokenizer tok = new StringTokenizer(path, File.separator);
+ while (tok.hasMoreTokens()) {
+ String thisToken = tok.nextToken();
+ if (".".equals(thisToken)) {
+ continue;
+ } else if ("..".equals(thisToken)) {
+ if (s.size() < 2) {
+ throw new BuildException("Cannot resolve path "+orig);
+ } else {
+ s.pop();
+ }
+ } else { // plain component
+ s.push(thisToken);
+ }
+ }
+
+ StringBuffer sb = new StringBuffer();
+ for (int i=0; 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));
+ }
+
+
+ path = sb.toString();
+ if (dosWithDrive) {
+ path = path.replace('/', '\\');
+ }
+ return new File(path);
+ }
}
diff --git a/src/testcases/org/apache/tools/ant/ProjectTest.java b/src/testcases/org/apache/tools/ant/ProjectTest.java
index f1807f0d9..83c64ce56 100644
--- a/src/testcases/org/apache/tools/ant/ProjectTest.java
+++ b/src/testcases/org/apache/tools/ant/ProjectTest.java
@@ -93,6 +93,9 @@ public class ProjectTest extends TestCase {
assert("Path", p.createDataType("path") instanceof Path);
}
+ /**
+ * This test has been a starting point for moving the code to FileUtils.
+ */
public void testResolveFile() {
/*
* Start with simple absolute file names.
diff --git a/src/testcases/org/apache/tools/ant/util/FileUtilsTest.java b/src/testcases/org/apache/tools/ant/util/FileUtilsTest.java
index af1daecb8..cdceb38e9 100644
--- a/src/testcases/org/apache/tools/ant/util/FileUtilsTest.java
+++ b/src/testcases/org/apache/tools/ant/util/FileUtilsTest.java
@@ -58,6 +58,8 @@ import java.io.*;
import junit.framework.TestCase;
+import org.apache.tools.ant.BuildException;
+
/**
* Tests for org.apache.tools.ant.util.FileUtils.
*
@@ -65,12 +67,19 @@ import junit.framework.TestCase;
*/
public class FileUtilsTest extends TestCase {
+ private FileUtils fu;
private File removeThis;
+ private String root;
public FileUtilsTest(String name) {
super(name);
}
+ public void setUp() {
+ fu = FileUtils.newFileUtils();
+ root = new File(File.separator).getAbsolutePath();
+ }
+
public void tearDown() {
if (removeThis != null && removeThis.exists()) {
removeThis.delete();
@@ -78,7 +87,6 @@ public class FileUtilsTest extends TestCase {
}
public void testSetLastModified() throws IOException {
- FileUtils fu = FileUtils.newFileUtils();
removeThis = new File("dummy");
FileOutputStream fos = new FileOutputStream(removeThis);
fos.write(new byte[0]);
@@ -125,4 +133,135 @@ public class FileUtilsTest extends TestCase {
}
}
+ public void testResolveFile() {
+ /*
+ * Start with simple absolute file names.
+ */
+ assertEquals(File.separator,
+ fu.resolveFile(null, "/").getPath());
+ assertEquals(File.separator,
+ fu.resolveFile(null, "\\").getPath());
+
+ /*
+ * throw in drive letters
+ */
+ String driveSpec = "C:";
+ assertEquals(driveSpec + "\\",
+ fu.resolveFile(null, driveSpec + "/").getPath());
+ assertEquals(driveSpec + "\\",
+ fu.resolveFile(null, driveSpec + "\\").getPath());
+ String driveSpecLower = "c:";
+ assertEquals(driveSpec + "\\",
+ fu.resolveFile(null, driveSpecLower + "/").getPath());
+ assertEquals(driveSpec + "\\",
+ fu.resolveFile(null, driveSpecLower + "\\").getPath());
+ /*
+ * promised to eliminate consecutive slashes after drive letter.
+ */
+ assertEquals(driveSpec + "\\",
+ fu.resolveFile(null, driveSpec + "/////").getPath());
+ assertEquals(driveSpec + "\\",
+ fu.resolveFile(null, driveSpec + "\\\\\\\\\\\\").getPath());
+
+ /*
+ * Now test some relative file name magic.
+ */
+ assertEquals(localize("/1/2/3/4"),
+ fu.resolveFile(new File(localize("/1/2/3")), "4").getPath());
+ assertEquals(localize("/1/2/3/4"),
+ fu.resolveFile(new File(localize("/1/2/3")), "./4").getPath());
+ assertEquals(localize("/1/2/3/4"),
+ fu.resolveFile(new File(localize("/1/2/3")), ".\\4").getPath());
+ assertEquals(localize("/1/2/3/4"),
+ fu.resolveFile(new File(localize("/1/2/3")), "./.\\4").getPath());
+ assertEquals(localize("/1/2/3/4"),
+ fu.resolveFile(new File(localize("/1/2/3")), "../3/4").getPath());
+ assertEquals(localize("/1/2/3/4"),
+ fu.resolveFile(new File(localize("/1/2/3")), "..\\3\\4").getPath());
+ assertEquals(localize("/1/2/3/4"),
+ fu.resolveFile(new File(localize("/1/2/3")), "../../5/.././2/./3/6/../4").getPath());
+ assertEquals(localize("/1/2/3/4"),
+ fu.resolveFile(new File(localize("/1/2/3")), "..\\../5/..\\./2/./3/6\\../4").getPath());
+
+ try {
+ fu.resolveFile(new File(localize("/1")), "../../b");
+ fail("successfully crawled beyond the filesystem root");
+ } catch (BuildException e) {
+ // Expected Exception caught
+ }
+
+ }
+
+ public void testNormalize() {
+ /*
+ * Start with simple absolute file names.
+ */
+ assertEquals(File.separator,
+ fu.normalize("/").getPath());
+ assertEquals(File.separator,
+ fu.normalize("\\").getPath());
+
+ /*
+ * throw in drive letters
+ */
+ String driveSpec = "C:";
+ assertEquals(driveSpec + "\\",
+ fu.normalize(driveSpec + "/").getPath());
+ assertEquals(driveSpec + "\\",
+ fu.normalize(driveSpec + "\\").getPath());
+ String driveSpecLower = "c:";
+ assertEquals(driveSpec + "\\",
+ fu.normalize(driveSpecLower + "/").getPath());
+ assertEquals(driveSpec + "\\",
+ fu.normalize(driveSpecLower + "\\").getPath());
+ /*
+ * promised to eliminate consecutive slashes after drive letter.
+ */
+ assertEquals(driveSpec + "\\",
+ fu.normalize(driveSpec + "/////").getPath());
+ assertEquals(driveSpec + "\\",
+ fu.normalize(driveSpec + "\\\\\\\\\\\\").getPath());
+
+ /*
+ * Now test some relative file name magic.
+ */
+ assertEquals(localize("/1/2/3/4"),
+ fu.normalize(localize("/1/2/3/4")).getPath());
+ assertEquals(localize("/1/2/3/4"),
+ fu.normalize(localize("/1/2/3/./4")).getPath());
+ assertEquals(localize("/1/2/3/4"),
+ fu.normalize(localize("/1/2/3/.\\4")).getPath());
+ assertEquals(localize("/1/2/3/4"),
+ fu.normalize(localize("/1/2/3/./.\\4")).getPath());
+ assertEquals(localize("/1/2/3/4"),
+ fu.normalize(localize("/1/2/3/../3/4")).getPath());
+ assertEquals(localize("/1/2/3/4"),
+ fu.normalize(localize("/1/2/3/..\\3\\4")).getPath());
+ assertEquals(localize("/1/2/3/4"),
+ fu.normalize(localize("/1/2/3/../../5/.././2/./3/6/../4")).getPath());
+ assertEquals(localize("/1/2/3/4"),
+ fu.normalize(localize("/1/2/3/..\\../5/..\\./2/./3/6\\../4")).getPath());
+
+ try {
+ fu.normalize("foo");
+ fail("foo is not an absolute path");
+ } catch (BuildException e) {
+ // Expected exception caught
+ }
+
+ try {
+ fu.normalize(localize("/1/../../b"));
+ fail("successfully crawled beyond the filesystem root");
+ } catch (BuildException e) {
+ // Expected exception caught
+ }
+ }
+
+ /**
+ * adapt file separators to local conventions
+ */
+ private String localize(String path) {
+ path = root + path.substring(1);
+ return path.replace('\\', File.separatorChar).replace('/', File.separatorChar);
+ }
}