diff --git a/docs/manual/tutorial-tasks-filesets-properties.html b/docs/manual/tutorial-tasks-filesets-properties.html new file mode 100644 index 000000000..8bc297390 --- /dev/null +++ b/docs/manual/tutorial-tasks-filesets-properties.html @@ -0,0 +1,763 @@ + +
+After reading the tutorial about writing +tasks this tutorial explains how to get and set properties and how to use +nested filesets and paths.
+ +The goal is to write a task, which searchs in a path for a file and saves the +location of that file in a property.
+ + + +We can use the buildfile from the other tutorial and modify it a little bit. +Thatīs the advantage of using properties - we can reuse nearly the whole script. :-)
+
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<project name="FindTask" basedir="." default="test">
+ ...
+ <target name="use.init" description="Taskdefī the Find-Task" depends="jar">
+ <taskdef name="find" classname="Find" classpath="${ant.project.name}.jar"/>
+ </target>
+
+ <!-- the other use.* targets are deleted -->
+ ...
+</project>
+
+
+
+
+Our first step is to set a property to a value and print the value of property. So our scenario +would be +
+ <find property="test" value="test-value"/> + <find print="test"/> ++ok, can be rewritten with the core tasks +
+ <property name="test" value="test-value"/>
+ <echo message="${test}"/>
+
+but I have to start on known ground :-)
+So what to do? Handling three attributes (property, value, print) and an execute. Because this +is only an introduction example I donīt do much checking: + +
+import org.apache.tools.ant.BuildException;
+
+public class Find extends Task {
+
+ private String property;
+ private String value;
+ private String print;
+
+ public void setProperty(String property) {
+ this.property = property;
+ }
+
+ // setter for value and print
+
+ public void execute() {
+ if (print != null) {
+ String propValue = getProject().getProperty(print);
+ log(propValue);
+ } else {
+ if (property == null) throw new BuildException("property not set");
+ if (value == null) throw new BuildException("value not set");
+ getProject().setNewProperty(property, value);
+ }
+ }
+}
+
+
+As said in the other tutorial, the property access is done via Project instance.
+This instance we get via the public getProject() method which we inherit from
+Task (more precise from ProjectComponent). Reading a property is done via
+getProperty(propertyname) (very simple, isnīt it?). This property returns
+the value (String) or null if not set.(by the way: a short word to ants "namespaces" (donīt +be confused with xml namespaces which will be also introduces in the future (1.6 or 1.7): +an <antcall> creates a new space for property names. All properties from the caller +are passed to the callee, but the callee can set its own properties without notice by the +caller.)
+ +There are some other setter, too (but I havenīt used them, so I canīt say something +to them, sorry :-)
+ +After putting our two line example from above into a target names use.simple +we can call that from our testcase: + +
+import org.apache.tools.ant.BuildFileTest;
+
+public class FindTest extends BuildFileTest {
+
+ public FindTest(String name) {
+ super(name);
+ }
+
+ public void setUp() {
+ configureProject("build.xml");
+ }
+
+ public void testSimple() {
+ expectLog("use.simple", "test-value");
+ }
+}
+
+
+and all works fine.
+
+
+
+
+Ant provides a common way of bundling files: the fileset. Because you are reading +this tutorial I think you know them and I donīt have to spend more explanations about +their usage in buildfiles. Our goal is to search a file in path. And on this step the +path is simply a fileset (or more precise: a collection of filesets). So our usage +would be +
+ <find file="ant.jar" location="location.ant-jar">
+ <fileset dir="${ant.home}" includes="**/*.jar"/>
+ </find>
+
+
+
+What do we need? A task with two attributes (file, location) and nested +filesets. Because we had attribute handling already in the example above and the handling +of nested elements is described in the other tutorial the code should be very easy: +
+public class Find extends Task {
+
+ private String file;
+ private String location;
+ private Vector filesets = new Vector();
+
+ public void setFile(String file) {
+ this.file = file;
+ }
+
+ public void setLocation(String location) {
+ this.location = location;
+ }
+
+ public void addFileset(FileSet fileset) {
+ filesets.add(fileset);
+ }
+
+ public void execute() {
+ }
+}
+
+Ok - that task wouldnīt do very much, but we can use it in the described manner without
+failure. On next step we have to implement the execute method. And before that we will
+implement the appropriate testcases (TDD - test driven development).
+
+In the other tutorial we have reused the already written targets of our buildfile. +Now we will configure most of the testcases via java code (sometimes itīs much easier +to write a target than doing it via java coding). What can be tested?
+public class FindTest extends BuildFileTest {
+
+ ... // constructor, setUp as above
+
+ public void testMissingFile() {
+ Find find = new Find();
+ try {
+ find.execute();
+ fail("No 'no-file'-exception thrown.");
+ } catch (Exception e) {
+ // exception expected
+ String expected = "file not set";
+ assertEquals("Wrong exception message.", expected, e.getMessage());
+ }
+ }
+
+ public void testMissingLocation() {
+ Find find = new Find();
+ find.setFile("ant.jar");
+ try {
+ find.execute();
+ fail("No 'no-location'-exception thrown.");
+ } catch (Exception e) {
+ ... // similar to testMissingFile()
+ }
+ }
+
+ public void testMissingFileset() {
+ Find find = new Find();
+ find.setFile("ant.jar");
+ find.setLocation("location.ant-jar");
+ try {
+ find.execute();
+ fail("No 'no-fileset'-exception thrown.");
+ } catch (Exception e) {
+ ... // similar to testMissingFile()
+ }
+ }
+
+ public void testFileNotPresent() {
+ executeTarget("testFileNotPresent");
+ String result = getProject().getProperty("location.ant-jar");
+ assertNull("Property set to wrong value.", result);
+ }
+
+ public void testFilePresent() {
+ executeTarget("testFilePresent");
+ String result = getProject().getProperty("location.ant-jar");
+ assertNotNull("Property not set.", result);
+ assertTrue("Wrong file found.", result.endsWith("ant.jar"));
+ }
+}
+
+
+If we run this test class all test cases (except testFileNotPresent) fail. No we +can implement our task, so that these test cases will pass.
+ +
+ protected void validate() {
+ if (file==null) throw new BuildException("file not set");
+ if (location==null) throw new BuildException("location not set");
+ if (filesets.size()<1) throw new BuildException("fileset not set");
+ }
+
+ public void execute() {
+ validate(); // 1
+ String foundLocation = null;
+ for(Iterator itFSets = filesets.iterator(); itFSets.hasNext(); ) { // 2
+ FileSet fs = (FileSet)itFSets.next();
+ DirectoryScanner ds = fs.getDirectoryScanner(getProject()); // 3
+ String[] includedFiles = ds.getIncludedFiles();
+ for(int i=0; i<includedFiles.length; i++) {
+ String filename = includedFiles[i].replace('\\','/'); // 4
+ filename = filename.substring(filename.lastIndexOf("/")+1);
+ if (foundLocation==null && file.equals(filename)) {
+ File base = ds.getBasedir(); // 5
+ File found = new File(base, includedFiles[i]);
+ foundLocation = found.getAbsolutePath();
+ }
+ }
+ }
+ if (foundLocation!=null) // 6
+ getProject().setNewProperty(location, foundLocation);
+ }
+
+
+On //1 we check the prerequisites for our task. Doing that in a validate-method +is a common way, because we separate the prerequisites from the real work. On //2 we iterate +over all nested filesets. We we donīt want to handle multiple filesets, the addFileset() +method has to reject the further calls. We can get the result of fileset via its DirectoryScanner +like done //3. After that we create a plattform independend String representation of +the file path (//4, can be done in other ways of course). We have to do the replace(), +because we work with a simple string comparison. Ant itself is platform independant and can +therefore run on filesystems with slash (/, e.g. Linux) or backslash (\, e.g. Windows) as +path separator. Therefore we have to unify that. If we found our file we create an absolute +path representation on //5, so that we can use that information without knowing the basedir. +(This is very important on use with multiple filesets, because they can have different basedirs +and the return value of the directory scanner is relative to its basedir.) Finally we store the +location of the file as property, if we had found one (//6).
+ +Ok, much more easier in this simple case would be to add the file as additional +include element to all filesets. But I wanted to show how to handle complex situations +whithout being complex :-)
+ +The test case uses the ant property ant.home as reference. This property is set by the +Launcher class which starts ant. We can use that property in our buildfiles as a build-in +property (see [XXX]). But if we create a new ant environment we have to set that value for our own. +And we use the <junit< task in fork-mode. Therefore we have do modify our buildfile: +
+ <target name="junit" description="Runs the unit tests" depends="jar">
+ <delete dir="${junit.out.dir.xml}" />
+ <mkdir dir="${junit.out.dir.xml}" />
+ <junit printsummary="yes" haltonfailure="no">
+ <classpath refid="classpath.test"/>
+ <sysproperty key="ant.home" value="${ant.home}"/>
+ <formatter type="xml"/>
+ <batchtest fork="yes" todir="${junit.out.dir.xml}">
+ <fileset dir="${src.dir}" includes="**/*Test.java"/>
+ </batchtest>
+ </junit>
+ </target>
+
+
+
+
+A task providing support for filesets is a very comfortable one. But there is another +possibility of bundling files: the <path>. Fileset are easy if the files are all under +a common base directory. But if this is not the case you have a problem. Another disadvantage +is its speed: if you have only a few files in a huge directory structure, why not use a +<fileset> instead? <path>s combines these datatypes in that way that a path contains +other paths, filesets, dirsets and filelists. This is way Ant-Contribs [XXX] +<foreach> task is modified to support paths instead of filesets. So we want that, too.
+ +Changing from fileset to path support is very easy:
+
+Change java code from:
+ private Vector filesets = new Vector();
+ public void addFileset(FileSet fileset) {
+ filesets.add(fileset);
+ }
+to:
+ private Vector paths = new Vector(); *1
+ public void addPath(Path path) { *2
+ paths.add(path);
+ }
+and build file from:
+ <find file="ant.jar" location="location.ant-jar">
+ <fileset dir="${ant.home}" includes="**/*.jar"/>
+ </find>
+to:
+ <find file="ant.jar" location="location.ant-jar">
+ <path> *3
+ <fileset dir="${ant.home}" includes="**/*.jar"/>
+ </path>
+ </find>
+
+On *1 we rename only the vector. Itīs just for better reading the source. On *2 +we have to provide the right method: an addName(Type t). Therefore replace the +fileset with path here. Finally we have to modify our buildfile on *3 because our task +donīt support nested filesets any longer. So we wrap the fileset inside a path.
+ +And now we modify the testcase. Oh, not very much to do :-) Renaming the testMissingFileset() +(not really a must-be but better itīs named like the think it does) and update the +expected-String in that method (now a path not set message is expected). The more complex +test cases base on the buildscript. So the targets testFileNotPresent and testFilePresent have to be +modified in the manner described above.
+ +The test are finished. Now we have to adapt the task implementation. The easiest modification is +in the validate() method where we change le last line to if (paths.size()<1) throw new +BuildException("path not set");. In the execute() method we have a liitle more work. +... mmmh ... in reality itīs lesser work, because the Path class does a the whole DirectoryScanner-handling +and creating absolute paths stuff for us. So the execute method is just:
+ +
+ public void execute() {
+ validate();
+ String foundLocation = null;
+ for(Iterator itPaths = paths.iterator(); itPaths.hasNext(); ) {
+ Path path = (Path)itPaths.next(); // 1
+ String[] includedFiles = path.list(); // 2
+ for(int i=0; i<includedFiles.length; i++) {
+ String filename = includedFiles[i].replace('\\','/');
+ filename = filename.substring(filename.lastIndexOf("/")+1);
+ if (foundLocation==null && file.equals(filename)) {
+ foundLocation = includedFiles[i]; // 3
+ }
+ }
+ }
+ if (foundLocation!=null)
+ getProject().setNewProperty(location, foundLocation);
+ }
+
+
+Of course we have to do the typecase to Path on //1. On //2 and //3 +we see that the Path class does the work for us: no DirectoryScanner (was at 2) and no +creating of the absolute path (was at 3).
+ + + + +So far so good. But could a file be on more than one place in the path? - Of course.
+And would it be good to get all of them? - It depends on ...
+ +
In this section we will extend that task to support returning a list of all files. +Lists as property values are not supported by Ant natively. So we have to see how other +tasks use lists. The most famous task using lists is Ant-Contribs <foreach>. All list +elements are concatenated and separated with a customizable separator (default ',').
+ +So we do the following:
+ ++ <find ... delimiter=""/> ... </find> ++ +
If the delimiter is set we will return all found files as list with that delimiter.
+ +Therefore we have to
So we add as testcase:
+
+in the buildfile:
+ <target name="test.init">
+ <mkdir dir="test1/dir11/dir111"/> *1
+ <mkdir dir="test1/dir11/dir112"/>
+ ...
+ <touch file="test1/dir11/dir111/test"/>
+ <touch file="test1/dir11/dir111/not"/>
+ ...
+ <touch file="test1/dir13/dir131/not2"/>
+ <touch file="test1/dir13/dir132/test"/>
+ <touch file="test1/dir13/dir132/not"/>
+ <touch file="test1/dir13/dir132/not2"/>
+ <mkdir dir="test2"/>
+ <copy todir="test2"> *2
+ <fileset dir="test1"/>
+ </copy>
+ </target>
+
+ <target name="testMultipleFiles" depends="use.init,test.init"> *3
+ <find file="test" location="location.test" delimiter=";">
+ <path>
+ <fileset dir="test1"/>
+ <fileset dir="test2"/>
+ </path>
+ </find>
+ <delete> *4
+ <fileset dir="test1"/>
+ <fileset dir="test2"/>
+ </delete>
+ </target>
+
+in the test class:
+ public void testMultipleFiles() {
+ executeTarget("testMultipleFiles");
+ String result = getProject().getProperty("location.test");
+ assertNotNull("Property not set.", result);
+ assertTrue("Only one file found.", result.indexOf(";") > -1);
+ }
+
+
+Now we need a directory structure where we CAN find files with the same +name in different directories. Because we canīt sure to have one we create +one on *1, *2. And of course we clean up that on *4. The creation +can be done inside our test target or in a separate one, which will be better +for reuse later (*3). + +
The task implementation is modified as followed:
+ +
+ private Vector foundFiles = new Vector();
+ ...
+ private String delimiter = null;
+ ...
+ public void setDelimiter(String delim) {
+ delimiter = delim;
+ }
+ ...
+ public void execute() {
+ validate();
+ // find all files
+ for(Iterator itPaths = paths.iterator(); itPaths.hasNext(); ) {
+ Path path = (Path)itPaths.next();
+ String[] includedFiles = path.list();
+ for(int i=0; i<includedFiles.length; i++) {
+ String filename = includedFiles[i].replace('\\','/');
+ filename = filename.substring(filename.lastIndexOf("/")+1);
+ if (file.equals(filename) && !foundFiles.contains(includedFiles[i])) { // 1
+ foundFiles.add(includedFiles[i]);
+ }
+ }
+ }
+
+ // create the return value (list/single)
+ String rv = null;
+ if (foundFiles.size() > 0) { // 2
+ if (delimiter==null) {
+ // only the first
+ rv = (String)foundFiles.elementAt(0);
+ } else {
+ // create list
+ StringBuffer list = new StringBuffer();
+ for(Iterator it=foundFiles.iterator(); it.hasNext(); ) { // 3
+ list.append(it.next());
+ if (it.hasNext()) list.append(delimiter); // 4
+ }
+ rv = list.toString();
+ }
+ }
+
+ // create the property
+ if (rv!=null)
+ getProject().setNewProperty(location, rv);
+ }
+
+
+The algorithm does: finding all files, creating the return value depending on the users +wish, returning the value as property. On //1 we eliminates the duplicates. //2 +ensures that we create the return value only if we have found one file. On //3 we +iterate over all found files and //4 ensures that the last entry has no trailing +delimiter.
+ +Ok, first searching for all files and then returning only the first one ... You can +tune the performance of your own :-)
+ + + +A task is useless if the only who is able to code the buildfile is the task developer +(and he only the next few weeks :-). So documentation is also very important. In which +form you do that depends on your favourite. But inside Ant there is a common format and +it has advantages if you use that: all task users know that form, this form is requested if +you decide to contribute your task. So we will doc our task in that form.
+ +If you have a look at the manual page of the java [XXX] task you will see
+<html> + +<head> +<meta http-equiv="Content-Language" content="en-us"> +<title> Taskname Task</title> +</head> + +<body> + +<h2><a name="taskname">Taskname</a></h2> +<h3>Description</h3> +<p> Describe the task.</p> + +<h3>Parameters</h3> +<table border="1" cellpadding="2" cellspacing="0"> + <tr> + <td valign="top"><b>Attribute</b></td> + <td valign="top"><b>Description</b></td> + <td align="center" valign="top"><b>Required</b></td> + </tr> + + do this html row for each attribute (including inherited attributes) + <tr> + <td valign="top">classname</td> + <td valign="top">the Java class to execute.</td> + <td align="center" valign="top">Either jar or classname</td> + </tr> + +</table> + +<h3>Parameters specified as nested elements</h3> + +Describe each nested element (including inherited) +<h4>your nested element</h4> +<p> description </p> +<p><em>since Ant 1.6</em>.</p> + +<h3>Examples</h3> +<pre> + A code sample; donīt forget to escape the < of the tags with < +</pre> +what should that example do? + +</body> +</html> ++ +
For our task we have that [XXX]:
+
+<html>
+
+<head>
+<meta http-equiv="Content-Language" content="en-us">
+<title> Find Task</title>
+</head>
+
+<body>
+
+<h2><a name="find">Find</a></h2>
+<h3>Description</h3>
+<p>Searchs in a given path for a file and returns the absolute to it as property.
+If delimiter is set this task returns all found locations.</p>
+
+<h3>Parameters</h3>
+<table border="1" cellpadding="2" cellspacing="0">
+ <tr>
+ <td valign="top"><b>Attribute</b></td>
+ <td valign="top"><b>Description</b></td>
+ <td align="center" valign="top"><b>Required</b></td>
+ </tr>
+ <tr>
+ <td valign="top">file</td>
+ <td valign="top">The name of the file to search.</td>
+ <td align="center" valign="top">yes</td>
+ </tr>
+ <tr>
+ <td valign="top">location</td>
+ <td valign="top">The name of the property where to store the location</td>
+ <td align="center" valign="top">yes</td>
+ </tr>
+ <tr>
+ <td valign="top">delimiter</td>
+ <td valign="top">A delimiter to use when returning the list</td>
+ <td align="center" valign="top">only if the list is required</td>
+ </tr>
+</table>
+
+<h3>Parameters specified as nested elements</h3>
+
+<h4>path</h4>
+<p>The path where to search the file.</p>
+
+<h3>Examples</h3>
+<pre>
+ <find file="ant.jar" location="loc">
+ <path>
+ <fileset dir="${ant.home}"/>
+ <path>
+ </find>
+</pre>
+Searches in Ants home directory for a file <i>ant.jar</i> and stores its location in
+property <i>loc</i> (should be ANT_HOME/bin/ant.jar).
+
+<pre>
+ <find file="ant.jar" location="loc" delimiter=";">
+ <path>
+ <fileset dir="C:/"/>
+ <path>
+ </find>
+ <echo>ant.jar found in: ${loc}</echo>
+</pre>
+Searches in Windows C: drive for all <i>ant.jar</i> and stores their locations in
+property <i>loc</i> delimited with <i>';'</i>. (should need a long time :-)
+After that it prints out the result (e.g. C:/ant-1.5.4/bin/ant.jar;C:/ant-1.6/bin/ant.jar).
+
+</body>
+</html>
+
+
+
+
+Now we will check the "Checklist before submitting a new task" described in that guideline. +
Simply copy the license text from one the other source from the Ant source tree. But +ensure that the current year is used in the * Copyright (c) 2000-2003 The Apache Software +Foundation. All rights reserved. lines. + + +
Copyright © 2003 Apache Software Foundation. All rights +Reserved.
+ + + \ No newline at end of file