|
2.3. Handling Data Using TypesAnt supports a number of types, and the rest of this chapter is devoted to understanding them and how to work with them. These types work much like data types in programming languages, and as you're going to see, types and properties are intertwined. The data structures you create using types can be assigned to properties, and the data you use to set up those data structures is often stored in properties. Now that you've got properties under your belt, it's time to move on to types. Much of what a build tool like Ant does is work with files in directory structures, so you might expect that many of the Ant types have to do with handling files and directories. You can see the available Ant core (that is, built-in) types in Table 2-7.
Many Ant tasks in the upcoming chapters depend on the types you see in Table 2-7, so it's worth going through them in detail, especially the ones that handle files. Understanding Ant types is central to using Ant; if you don't understand paths and FileSets, for example, you'll be severely hampered by what you can do with Ant. 2.3.1. Path-Like StructuresIn Ant, paths are often handled by the path type, called a path-like structure. As you can imagine, you have to work with paths frequently. For example, a task like javac supports the path attributes srcdir, classpath, sourcepath, bootclasspath, and exTDirs, all of which are handled as path-like structures. That means you can set them as attributes: <javac destdir="${build}" classpath="classes.jar" srcdir="${src}"/> debug="on"> </javac> You can set paths as nested elements, not just as attributes, and you do that with the src, classpath, sourcepath, bootclasspath and exTDirs elements. For the javac task, the srcdir attribute becomes the src element. Nested elements are a good idea when you have multiple paths you want to work with that would otherwise be assigned to the same attribute. Here's an example: <javac destdir="${build}" classpath="classes.jar" debug="on"> <src path="${src}"/> <src path="${src2}"/> </javac> In addition, when you want to specify path-like values, a nested element can be used with internal pathelement elements, which specify paths, like this: <javac destdir="${build}" classpath="classes.jar" debug="on"> <src path="${src}"/> <src> <pathelement path="${src2}"/> <pathelement path="${src3}"/> </src> </javac> In pathelement, the location attribute specifies a single file or directory relative to the project's base directory (or an absolute filename), while the path attribute holds colon-separated or semicolon-separated lists of locations (such as "classes.jar;classes2.jar").
If you want to build your own paths and reference them later, you can use the path element with enclosed pathelement elements. For example, to create a new path and give it the ID build.classpath, you can use this path element and then refer to the new path with the refid attribute that path-like structure elements support: <path > <pathelement path="${classes}"/> <pathelement path="${classes2}"/> </path> <target name="compile"> <javac destdir="${build}" classpath="classes.jar" debug="on"> <src path="${src}"/> <classpath ref/> </javac> </target>
2.3.2. Working with Groups of FilesFileSet s are types that represent groups of files, and they're common in Ant because handling a group of files is a common thing to do. The fileset element can contain nested include, includesfile, exclude and excludesfile elements; here's an example using fileset, as well as include and exclude: <fileset dir="${source}"> <include name="**/*.java"/> <exclude name="**/*test*"/> </fileset> Here's an example that lets you include certain files using the filename selector (more on selectors later in this chapter): <fileset dir="${source}"> <filename name="**/*.java"/> <filename name="test.cpp"/> </fileset> Many Ant tasks support file sets, such as the copy task (see Chapter 4); here's an example, which copies all but .java files from the src directory to the dest directory: <copy todir="../dest"> <fileset dir="src"> <exclude name="**/*.java"/> </fileset> </copy> You can see the attributes of the fileset type in Table 2-8.
As mentioned, the fileset element can contain include, includesfile, exclude and excludesfile elements, and you can see the allowed attributes of these elements in Table 2-9. The fileset element can contain patternset and selector elements, which you'll see later in this chapter.
Some tasks form implicit file sets, such as the javac task. This means that a task supports all attributes of fileset (see Table 2-8). You don't have to use a nested fileset element at all if you don't want to because the built-in attributes of the task give you all the support you need. Each time we come across such a task, I'll be sure to mention this.
2.3.2.1 Default excludesSome files are excluded by default from file sets since they correspond to system or temporary files of various kinds. Here are the patterns excluded by default (recall that ** means the current directory, or any subdirectory of the current directory):
2.3.3. Working with Groups of DirectoriesDirectory sets (DirSets) are types corresponding to groups of directories. This type is great for grouping directories together and handling them at one time with various tasks. DirSets can appear inside various tasks or at the same level as target; like file sets, directory sets can contain nested include, includesfile, exclude, excludesfile, and patternset elements. Here's an example: <dirset dir="${build.dir}"> <include name="apps/**/classes"/> <exclude name="apps/**/*Test*"/> </dirset> You can find the attributes of this type in Table 2-10.
2.3.4. Creating Lists of FilesFileLists are types corresponding to explicitly named lists of files. While FileSets act as filters, returning only those files that exist in the filesystem and match specified patterns, FileLists are useful for specifying files individually. Here's an example: <filelist dir="${doc}" files="type.html,using.html"/> You can see the filelist attributes in Table 2-11.
2.3.5. Working with PatternsA powerful way of working with multiple files is to use patterns, such as the pattern "*.java", which will match all files with the extension .java, "*.class", which matches files with the extension .class, and so on. If you want to work with multiple patterns simultaneously in Ant, patterns can be grouped into sets and later referenced by their id attribute. These sets are defined with the patternset type, which can appear nested into a FileSet or an implicit FileSet. Here's an example, where I'm creating a file set, using a pattern set, that will match all of a project's documentation files, excluding beta documentation: <fileset dir="${src}" casesensitive="yes"> <patternset> <include name="docs/**/*.html"/> <include name="prof/**/*.html" if="professional"/> <exclude name="**/*beta*"/> </patternset> </fileset> You can see the attributes of this type in Table 2-12.
PatternSets can include nested include, includesfile, exclude, and excludesfile elements, and you can nest PatternSets within one another. 2.3.6. SelectorsBesides all the types we've seen, selectors are powerful types and go far beyond selecting files based on filenames. They let you select files that make up a file set based on many criteria, such as what text a file contains, the date a file was modified, the size of a file, and more. Selectors have become one of the coolest things in Ant, and you can see Ant's core selectors, which come built into Ant, in Table 2-13.
You can nest selectors inside file sets to select the files you want. For example, here's how to create a file set of HTML documentation files that contain the text "selector" using the contains selector: <fileset dir="${docs}" includes="**/*.html"> <contains text="selector" casesensitive="no"/> </fileset> The date selector lets you choose file sets based on date, as here, where I'm selecting all documentation files before 1/1/2005: <fileset dir="${docs}" includes="**/*.html"> <date datetime="01/01/2005 12:00 AM" when="before"/> </fileset> You can use the filename selector much like the include and exclude tags inside a fileset. Here's an example: <fileset dir="${source}" includes="**/*"> <filename name="**/*.cpp"/> </fileset> The containsregexp selector limits the files in a fileset to only those whose contents contain a match to the regular expression specified by the expression attribute: <fileset dir="${source}" includes="*.java"> <containsregexp expression="$println(.);"/> </fileset> The type tag selects files of a certain type: directory or regular. Here's an example: <fileset dir="${src}"> <type type="dir"/> </fileset> FileSets are no longer about filenames. Now you've got access to more data, including what's inside files.
2.3.7. File FiltersFilters let you filter the data in files, modifying that data if you want to. FilterSets are groups of filters, and you can use filters to replace tokens in files with new content. For example, say that your files contain a token, like @DATE@; you can use filters and a filter set to copy those files with the copy task, while replacing that token with the current date. You can find the attributes of the filter set type in Table 2-14; to use FilterSets, you can use the filter task, which is coming up next (with examples).
You specify the filters inside a filter set with the filter task, coming up next. 2.3.7.1 Using the filter taskThe filter task supports the actual filters in a filter set. Here's a filter set example where the build file instructs Ant to copy build.java from the ${archives} directory to the ${source} directory, and replace the token @DATE@ in the files with today's date: <copy file="${archives}/build.java" toFile="${source}/build.java"> <filter set> <filter token="DATE" value="${TODAY}"/> </filter set> </copy> Here's the same example where the token to replace is %DATE;: <copy file="${archives}/build.java" toFile="${source}/build.java"> <filter set begintoken="%" endtoken=";"> <filter token="DATE" value="${TODAY}"/> </filter set> </copy> You can define a FilterSet and reference it later: <filter set begintoken="%" endtoken=";"> <filter token="DATE" value="${TODAY}"/> </filter set> <copy file="${archives}/build.java" toFile="${source}/build.java"> <filter set ref/> </copy>
You can see the attributes of this task in Table 2-15.
2.3.8. Filtering and Modifying Text with FilterChains and FilterReadersA FilterReader filters text and can modify that text. A FilterChain is a group of FilterReaders, applied in order, and functions much like piping one command to another in Unix. Using FilterReaders and FilterChains, you can process text data in advanced ways. A number of Ant tasks support filterchain elements:
You can create filter chains with your own Java classes or with one of the elements you see in Table 2-16.
Each of these filters correspond to a Java class; for example, the head filter corresponds to org.apache.tools.ant.filters.HeadFilter. Here are a few examples: Suppose you want to read the first three lines of the file given by ${sourcefile} into the property sourcefile.header. You could use filterreader and the Ant filter reader class org.apache.tools.ant.filters.HeadFilter: <loadfile srcfile="${sourcefile}" property="sourcefile.header"> <filterchain> <filterreader classname="org.apache.tools.ant.filters.HeadFilter"> <param name="lines" value="3"/> </filterreader> </filterchain> </loadfile> Ant gives you a shortcut with filter readers like this one, where you can use a task named headfilter to do the same thing: <loadfile srcfile="${sourcefile}" property="sourcefile.header"> <filterchain> <headfilter lines="3"/> </filterchain> </loadfile> The linecontains filter reader includes only those lines that contain all the strings you specify with contains elements. Here's an example, where I'm filtering only lines containing "apples" and "oranges": <linecontains> <contains value="apples"> <contains value="oranges"> </linecontains> The linecontainsregexp filter reader includes only those lines that match the regular expression you've specified. For example, here's how to match only lines that contain lowercase letters: <linecontainsregexp> <regexp pattern="^[a-z]$"> </linecontainsregexp> The stripjavacomments filter reader strips away comments from the data, using Java syntax guidelines. Here's an example: <loadfile srcfile="${src.file}" property="java.text"> <filterchain> <stripjavacomments/> </filterchain> </loadfile> The striplinecomments filter reader removes all those lines that begin with strings that represent comments as specified by the user. For example, here's how to remove all lines that begin with #, REM, rem, and //: <striplinecomments> <comment value="#"/> <comment value="REM "/> <comment value="rem "/> <comment value="//"/> </striplinecomments> The tabstospaces filter reader replaces tabs with spaces; here's an example: <loadfile srcfile="${src.file}" property="src.file.detabbed"> <filterchain> <tabstospaces/> </filterchain> </loadfile> The classconstants filter readers recovers constants defined in Java .class files. For example, say that you have a constant named MAXFILES: public interface Files { public static final String MAXFILES ="4"; } To load the MAXFILES constant and make it accessible by name, you can use the loadproperties task and the classconstants filter reader: <loadproperties srcfile="Files.class"> <filterchain> <classconstants/> . . . </filterchain> </loadproperties> As you can gather from the name, you can use multiple filter readers in a filter chain, and we'll add the prefixlines filter reader to prefix constants we recover with the text "Files.": <loadproperties srcfile="Files.class"> <filterchain> <classconstants/> <prefixlines prefix="Files."/> </filterchain> </loadproperties> Now you've recovered the constant Files.MAXFILES from a compiled .class file, and can display it with echo: <echo>${Files.MAXFILES}</echo> 2.3.9. Transforming One Set of Files to Another with MappersMappers are another Ant type, and they're used to map one set of files to another. For example, one of the mappers available in Ant is the regexp mapper that lets you grab a set of files and rename them. You can use mappers in copy, move, apply, uptodate, and a number of additional tasks. Here's an example, where I'm copying a file set from a directory named development to a directory named backup, renaming .java files to .backup files. This works because whatever matches to the expression inside the parentheses in the regular expression in the from attribute can be referenced in the to attribute as \1; a match to a second pair of parentheses may be referenced as \2, and so on: <copy todir="backup"> <fileset dir="development" includes="**/*.java"/> <mapper type="regexp" from="([a-z]*).java" to="\1.backup" /> </copy> Another mapper is the flatten mapper, which lets you copy files while stripping off any directory information from the filenames. That's how mappers typically work; you specify a file set and then add a mapper to refine or manipulate the set of files you want to work with. To use a mapper, you use the Ant mapper element, which has the attributes you see in Table 2-17.
Experience shows that Ant will not automatically convert / or \ characters in the to and from attributes to the correct directory separator for your current platform. If you need to specify a directory separator here, use ${file.separator}. What mappers can you use with the mapper element? You can see the available Ant mappers in Table 2-18.
When you use the identity mapper, the target filename is the same as the source filenamein other words, this one's transparent to Ant. Here's the way you'd use this mapper in a file set: <mapper type="identity"/> The flatten mapper is more interesting; this one strips all leading directory information off, allowing you to copy all files in a directory tree to a single directory. Here's an example: <copy todir="backup"> <fileset dir="development" includes="**/*.java"/> <mapper type="flatten" /> </copy> The merge mapper copies multiple files to the same target file. Here's an example: <mapper type="merge" to="backup.tar"/> In the glob mapper, you can use filename patterns in the to and from attributes, and those patterns may contain at most one asterisk (*): <copy todir="backup"> <fileset dir="development" includes="**/*.java"/> <mapper type="glob" from="*.java" to="*.old"/> </copy> The regexp mapper lets you use regular expressions in the to and from attributes. You can create regular expression matches using parentheses in the to attribute value and refer to the matched values as \1 through \9 in the from attribute value. We've seen an example of this mapper at work: <copy todir="backup"> <fileset dir="development" includes="**/*.java"/> <mapper type="regexp" from="([a-z]*).java" to="\1.backup" /> </copy>
The package mapper is an interesting one as it replaces directory separators found in the matched source pattern with dots in the target pattern placeholder, reproducing Java package syntax. This inspiration here is that this mapper was designed to be useful to flatten directory structures where files have the fully qualified classname as part of their name (this mapper was designed for use with the uptodate and junit tasks). Here's an example: <mapper type="package" from="*Beta.java" to="Beta*Beta.xml"/> For example, if you used this mapper element on org/steve/tools/ant/util/AntTaskBeta.java, it will create Beta.org.steve.tools.ant.util.AntTaskBeta.xml, flattening the directory structure but still letting you see where files come from. The unpackage mapper, new since Ant 1.6, is the reverse of the package mapper; that is, it replaces dots in a package name with directory separators. Here's an example: <mapper type="package" from="Beta*Beta.xml" to="*Beta.java"/> This is a useful one if you want to restore the original directory structure after flattening it with the package mapper. This example will take files such as Beta.org.steve.tools.ant.util.AntTaskBeta.xml back to org/steve/tools/ant/util/AntTaskBeta.java. |
|