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]); | |||
| } | |||
| } | |||
| } | |||