another path (by reference as well as literally, although I doubt the latter is of any use). Circular references should be caught. Suggested by: Barrie Treloar <Barrie.Treloar@camtech.com.au> git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@267962 13f79535-47bb-0310-9956-ffa450edef68master
@@ -465,6 +465,23 @@ you can define them with a <code><path></code> element at the | |||
same level as <em>target</em>s and reference them via their | |||
<em>id</em> attribute - see <a href="#references">References</a> for an | |||
example.</p> | |||
<p>A PATH like structure can include a reference to another PATH like | |||
structure via a nested <code><path></code> elements.</p> | |||
<pre> | |||
<path id="base.path"> | |||
<pathelement path="${classpath}" /> | |||
<fileset dir="lib"> | |||
<include name="**/*.jar" /> | |||
</fileset;> | |||
<pathelement location="classes" /> | |||
</path> | |||
<path id="tests.path"> | |||
<path refid="base.path" /> | |||
<pathelement location="testclasses" /> | |||
</path> | |||
</pre> | |||
<h3><a name="arg">Command line arguments</a></h3> | |||
<p>Several tasks take arguments that shall be passed to another | |||
@@ -60,8 +60,10 @@ import org.apache.tools.ant.Project; | |||
import org.apache.tools.ant.PathTokenizer; | |||
import java.io.File; | |||
import java.util.Vector; | |||
import java.util.Enumeration; | |||
import java.util.StringTokenizer; | |||
import java.util.Stack; | |||
import java.util.Vector; | |||
import java.text.CharacterIterator; | |||
import java.text.StringCharacterIterator; | |||
@@ -97,6 +99,11 @@ public class Path implements Cloneable { | |||
private Vector elements; | |||
private Project project; | |||
private boolean isReference = false; | |||
/** | |||
* Are we sure we don't hold circular references? | |||
*/ | |||
private boolean checked = true; | |||
public static Path systemClasspath = | |||
new Path(null, System.getProperty("java.class.path")); | |||
@@ -140,7 +147,10 @@ public class Path implements Cloneable { | |||
* @param location the location of the element to add (must not be | |||
* <code>null</code> nor empty. | |||
*/ | |||
public void setLocation(File location) { | |||
public void setLocation(File location) throws BuildException { | |||
if (isReference) { | |||
throw tooManyAttributes(); | |||
} | |||
createPathElement().setLocation(location); | |||
} | |||
@@ -149,15 +159,35 @@ public class Path implements Cloneable { | |||
* Parses a path definition and creates single PathElements. | |||
* @param path the path definition. | |||
*/ | |||
public void setPath(String path) { | |||
public void setPath(String path) throws BuildException { | |||
if (isReference) { | |||
throw tooManyAttributes(); | |||
} | |||
createPathElement().setPath(path); | |||
} | |||
/** | |||
* Makes this instance in effect a reference too another Path instance. | |||
* | |||
* <p>You must not set another attribute or nest elements inside | |||
* this element if you make it a reference. | |||
*/ | |||
public void setRefid(Reference r) throws BuildException { | |||
isReference = true; | |||
if (!elements.isEmpty()) { | |||
throw tooManyAttributes(); | |||
} | |||
elements.addElement(r); | |||
checked = false; | |||
} | |||
/** | |||
* Created the nested <pathelement> element. | |||
* Creates the nested <pathelement> element. | |||
*/ | |||
public PathElement createPathElement() { | |||
public PathElement createPathElement() throws BuildException { | |||
if (isReference) { | |||
throw noChildrenAllowed(); | |||
} | |||
PathElement pe = new PathElement(); | |||
elements.addElement(pe); | |||
return pe; | |||
@@ -166,17 +196,36 @@ public class Path implements Cloneable { | |||
/** | |||
* Adds a nested <fileset> element. | |||
*/ | |||
public void addFileset(FileSet fs) { | |||
public void addFileset(FileSet fs) throws BuildException { | |||
if (isReference) { | |||
throw noChildrenAllowed(); | |||
} | |||
elements.addElement(fs); | |||
} | |||
/** | |||
* Adds a nested <filesetref> element. | |||
*/ | |||
public void addFilesetRef(Reference r) { | |||
public void addFilesetRef(Reference r) throws BuildException { | |||
if (isReference) { | |||
throw noChildrenAllowed(); | |||
} | |||
elements.addElement(r); | |||
} | |||
/** | |||
* Creates a nested <path> element. | |||
*/ | |||
public Path createPath() throws BuildException { | |||
if (isReference) { | |||
throw noChildrenAllowed(); | |||
} | |||
Path p = new Path(project); | |||
elements.add(p); | |||
checked = false; | |||
return p; | |||
} | |||
/** | |||
* Append the contents of the other Path instance to this. | |||
*/ | |||
@@ -214,19 +263,26 @@ public class Path implements Cloneable { | |||
} | |||
/** | |||
* Returns all path elements defined by this and netsed path objects. | |||
* Returns all path elements defined by this and nested path objects. | |||
* @return list of path elements. | |||
*/ | |||
public String[] list() { | |||
if (!checked) { | |||
// make sure we don't have a circular reference here | |||
Stack stk = new Stack(); | |||
stk.push(this); | |||
bailOnCircularReference(stk); | |||
} | |||
Vector result = new Vector(2*elements.size()); | |||
for (int i=0; i<elements.size(); i++) { | |||
Object o = elements.elementAt(i); | |||
if (o instanceof Reference) { | |||
Reference r = (Reference) o; | |||
o = r.getReferencedObject(project); | |||
// we only support references to filesets right now | |||
if (o == null || !(o instanceof FileSet)) { | |||
String msg = r.getRefId()+" doesn\'t denote a fileset"; | |||
// we only support references to filesets and paths right now | |||
if (!(o instanceof FileSet) && !(o instanceof Path)) { | |||
String msg = r.getRefId()+" doesn\'t denote a fileset or path"; | |||
throw new BuildException(msg); | |||
} | |||
} | |||
@@ -242,6 +298,11 @@ public class Path implements Cloneable { | |||
for (int j=0; j<parts.length; j++) { | |||
addUnlessPresent(result, parts[j]); | |||
} | |||
} else if (o instanceof Path) { | |||
String[] parts = ((Path) o).list(); | |||
for (int j=0; j<parts.length; j++) { | |||
addUnlessPresent(result, parts[j]); | |||
} | |||
} else if (o instanceof FileSet) { | |||
FileSet fs = (FileSet) o; | |||
DirectoryScanner ds = fs.getDirectoryScanner(project); | |||
@@ -338,6 +399,27 @@ public class Path implements Cloneable { | |||
return p; | |||
} | |||
protected void bailOnCircularReference(Stack stk) throws BuildException { | |||
Enumeration enum = elements.elements(); | |||
while (enum.hasMoreElements()) { | |||
Object o = enum.nextElement(); | |||
if (o instanceof Reference) { | |||
o = ((Reference) o).getReferencedObject(project); | |||
} | |||
if (o instanceof Path) { | |||
if (stk.contains(o)) { | |||
throw circularReference(); | |||
} else { | |||
stk.push(o); | |||
((Path) o).bailOnCircularReference(stk); | |||
stk.pop(); | |||
} | |||
} | |||
} | |||
checked = true; | |||
} | |||
private static String resolveFile(Project project, String relativeName) { | |||
if (project != null) { | |||
return project.resolveFile(relativeName).getAbsolutePath(); | |||
@@ -351,4 +433,15 @@ public class Path implements Cloneable { | |||
} | |||
} | |||
private BuildException tooManyAttributes() { | |||
return new BuildException("You must not specify more than one attribute when using refid"); | |||
} | |||
private BuildException noChildrenAllowed() { | |||
return new BuildException("You must not specify nested elements when using refid"); | |||
} | |||
private BuildException circularReference() { | |||
return new BuildException("This path contains a circular reference."); | |||
} | |||
} |
@@ -90,7 +90,7 @@ public class Reference { | |||
Object o = project.getReferences().get(refid); | |||
if (o == null) { | |||
throw new BuildException("Refernce "+refid+" not found."); | |||
throw new BuildException("Reference "+refid+" not found."); | |||
} | |||
return o; | |||
} | |||
@@ -171,6 +171,9 @@ public class PathTest extends TestCase { | |||
p.append(new Path(project, "\\f")); | |||
l = p.list(); | |||
assertEquals("6 after append", 6, l.length); | |||
p.createPath().setLocation(new File("/g")); | |||
l = p.list(); | |||
assertEquals("7 after append", 7, l.length); | |||
} | |||
public void testEmpyPath() { | |||
@@ -183,6 +186,9 @@ public class PathTest extends TestCase { | |||
p.append(new Path(project)); | |||
l = p.list(); | |||
assertEquals("0 after append", 0, l.length); | |||
p.createPath(); | |||
l = p.list(); | |||
assertEquals("0 after append", 0, l.length); | |||
} | |||
public void testUnique() { | |||
@@ -198,6 +204,127 @@ public class PathTest extends TestCase { | |||
p.append(new Path(project, "/a;\\a:\\a")); | |||
l = p.list(); | |||
assertEquals("1 after append", 1, l.length); | |||
p.createPath().setPath("\\a:/a"); | |||
l = p.list(); | |||
assertEquals("1 after append", 1, l.length); | |||
} | |||
public void testEmptyElementIfIsReference() { | |||
Path p = new Path(project, "/a:/a"); | |||
try { | |||
p.setRefid(new Reference("dummyref")); | |||
fail("Can add reference to Path with elements from constructor"); | |||
} catch (BuildException be) { | |||
assertEquals("You must not specify more than one attribute when using refid", | |||
be.getMessage()); | |||
} | |||
p = new Path(project); | |||
p.setLocation(new File("/a")); | |||
try { | |||
p.setRefid(new Reference("dummyref")); | |||
fail("Can add reference to Path with elements from setLocation"); | |||
} catch (BuildException be) { | |||
assertEquals("You must not specify more than one attribute when using refid", | |||
be.getMessage()); | |||
} | |||
p = new Path(project); | |||
p.setRefid(new Reference("dummyref")); | |||
try { | |||
p.setLocation(new File("/a")); | |||
fail("Can set location in Path that is a reference."); | |||
} catch (BuildException be) { | |||
assertEquals("You must not specify more than one attribute when using refid", | |||
be.getMessage()); | |||
} | |||
try { | |||
p.setPath("/a;\\a"); | |||
fail("Can set path in Path that is a reference."); | |||
} catch (BuildException be) { | |||
assertEquals("You must not specify more than one attribute when using refid", | |||
be.getMessage()); | |||
} | |||
try { | |||
p.createPath(); | |||
fail("Can create nested Path in Path that is a reference."); | |||
} catch (BuildException be) { | |||
assertEquals("You must not specify nested elements when using refid", | |||
be.getMessage()); | |||
} | |||
try { | |||
p.createPathElement(); | |||
fail("Can create nested PathElement in Path that is a reference."); | |||
} catch (BuildException be) { | |||
assertEquals("You must not specify nested elements when using refid", | |||
be.getMessage()); | |||
} | |||
try { | |||
p.addFileset(new FileSet()); | |||
fail("Can add nested FileSet in Path that is a reference."); | |||
} catch (BuildException be) { | |||
assertEquals("You must not specify nested elements when using refid", | |||
be.getMessage()); | |||
} | |||
try { | |||
p.addFilesetRef(new Reference("dummy2")); | |||
fail("Can add nested FileSetRef in Path that is a reference."); | |||
} catch (BuildException be) { | |||
assertEquals("You must not specify nested elements when using refid", | |||
be.getMessage()); | |||
} | |||
} | |||
public void testCircularReferenceCheck() { | |||
Path p = new Path(project); | |||
project.addReference("dummy", p); | |||
p.setRefid(new Reference("dummy")); | |||
try { | |||
p.list(); | |||
fail("Can make Path a Reference to itself."); | |||
} catch (BuildException be) { | |||
assertEquals("This path contains a circular reference.", | |||
be.getMessage()); | |||
} | |||
// dummy1 --> dummy2 --> dummy3 --> dummy1 | |||
Path p1 = new Path(project); | |||
project.addReference("dummy1", p1); | |||
Path p2 = p1.createPath(); | |||
project.addReference("dummy2", p2); | |||
Path p3 = p2.createPath(); | |||
project.addReference("dummy3", p3); | |||
p3.setRefid(new Reference("dummy1")); | |||
try { | |||
p1.list(); | |||
fail("Can make circular reference."); | |||
} catch (BuildException be) { | |||
assertEquals("This path contains a circular reference.", | |||
be.getMessage()); | |||
} | |||
// dummy1 --> dummy2 --> dummy3 (with Path "/a") | |||
p1 = new Path(project); | |||
project.addReference("dummy1", p1); | |||
p2 = p1.createPath(); | |||
project.addReference("dummy2", p2); | |||
p3 = p2.createPath(); | |||
project.addReference("dummy3", p3); | |||
p3.setLocation(new File("/a")); | |||
String[] l = p1.list(); | |||
assertEquals("One element burried deep inside a nested path structure", | |||
1, l.length); | |||
if (isUnixStyle) { | |||
assertEquals("/a", l[0]); | |||
} else { | |||
assertEquals("\\a", l[0]); | |||
} | |||
} | |||
} |