bodewig 2003/03/28 04:41:01
Modified: . WHATSNEW docs/manual/CoreTasks concat.html src/etc/testcases/taskdefs concat.xml src/main/org/apache/tools/ant/taskdefs Concat.java src/testcases/org/apache/tools/ant/taskdefs ConcatTest.java Log: Various <concat> enhancements. PR: 18166 Submitted by: Peter Reilly <peter dot reilly at corvil dot com> Revision Changes Path 1.380 +3 -0 ant/WHATSNEW Index: WHATSNEW =================================================================== RCS file: /home/cvs/ant/WHATSNEW,v retrieving revision 1.379 retrieving revision 1.380 diff -u -r1.379 -r1.380 --- WHATSNEW 28 Mar 2003 12:20:50 -0000 1.379 +++ WHATSNEW 28 Mar 2003 12:41:00 -0000 1.380 @@ -198,6 +198,9 @@ * The xml formatter for JUnit will now honor test case names set with setName. Bugzilla Report 17040. +* <concat> now supports filtering and can check timestamps before + overriding a file. Bugzilla Report 18166. + Changes from Ant 1.5.2 to Ant 1.5.3 =================================== 1.7 +99 -10 ant/docs/manual/CoreTasks/concat.html Index: concat.html =================================================================== RCS file: /home/cvs/ant/docs/manual/CoreTasks/concat.html,v retrieving revision 1.6 retrieving revision 1.7 diff -u -r1.6 -r1.7 --- concat.html 4 Sep 2002 11:05:16 -0000 1.6 +++ concat.html 28 Mar 2003 12:41:01 -0000 1.7 @@ -14,16 +14,15 @@ <p> Concatenates a file, or a series of files, to a single file or the console. The destination file will be created if it does - not exist, though the the <var>append</var> attribute may be - used to alter this behavior. + not exist. </p> <p> + <a href="../CoreTypes/path.html">Path</a>s and/or <a href="../CoreTypes/fileset.html">FileSet</a>s and/or <a href="../CoreTypes/filelist.html">FileList</a>s are used to select which files are to be concatenated. There is no - singular 'file' attribute to specify a single file to cat -- a - fileset or filelist must also be used in these cases. + singular 'file' attribute to specify a single file to cat. </p> <h3>Parameters</h3> @@ -51,7 +50,17 @@ <td valign="top">append</td> <td valign="top"> Specifies whether or not the file specified by 'destfile' - should be overwritten. Defaults to "no". + should be appended. Defaults to "no". + </td> + <td valign="top" align="center">No</td> + </tr> + <tr> + <td valign="top">force</td> + <td valign="top"> + Specifies whether or not the file specified by 'destfile' + should be written to even if it is newer than all source files. + <em>since Ant 1.6</em>. + Defaults to "yes". </td> <td valign="top" align="center">No</td> </tr> @@ -71,6 +80,15 @@ </table> <h3>Parameters specified as nested elements</h3> + <h4>path</h4> + <p><em>since Ant 1.6</em>.</p> + + <p> + This is a <a href="../CoreTypes/path.html">Path</a>. This is + used to select file files to be concatenated. Note that + if a file can only appear once in a path. If this is + an issue consider using multiple paths. + </p> <h4>fileset</h4> @@ -81,7 +99,6 @@ <i>not</i> guaranteed. If this is an issue, use multiple filesets or consider using filelists. </p> - <h4>filelist</h4> <p> @@ -90,6 +107,47 @@ <var>files</var> attribute will be the same order in which the files are concatenated. </p> + <h4>filterchain</h4> + <p><em>since Ant 1.6</em>.</p> + <p>The concat task supports nested + <a href="../CoreTypes/filterchain.html"> FilterChain</a>s.</p> + + <h4>header,footer</h4> + <p><em>since Ant 1.6</em>.</p> + <p>Used to prepend or postpend text into the concatenated stream.</p> + <p>The text may be in-line or be in a file.</p> + <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">filtering</td> + <td valign="top"> + Whether to filter the text provided by this sub element, + default is "yes". + <td valign="top" align = "center">No</td> + </tr> + <tr> + <td valign="top">file</td> + <td valign="top">A file to place at the head or tail of the + concatenated text. + <td valign="top" align = "center">No</td> + </tr> + <tr> + <td valign="top">trim</td> + <td valign="top">Whether to trim the value, default is "no"</td> + <td valign="top" align = "center">No</td> + </tr> + <tr> + <td valign="top">trimleading</td> + <td valign="top"> + Whether to trim leading white space on each line, default is "no" + </td> + <td valign="top" align = "center">No</td> + </tr> + </table> <h3>Examples</h3> @@ -115,11 +173,12 @@ </concat> </pre> - <p><b>Concatenate a series of files, overwriting if the - destination file exists:</b></p> + <p><b>Concatenate a series of files, update the destination + file only if is older that all the source files:</b></p> <pre> - <concat destfile="${docbook.dir}/all-sections.xml"> + <concat destfile="${docbook.dir}/all-sections.xml" + force="no"> <filelist dir="${docbook.dir}/sections" files="introduction.xml,overview.xml"/> <fileset dir="${docbook.dir}" @@ -128,10 +187,40 @@ </concat> </pre> + <p><b>Concatenate a series of files, expanding ant properties</b></p> + <pre> + <concat destfile="${build.dir}/subs"> + <path> + <fileset dir="${src.dir}" includes="*.xml"/> + <pathelement location="build.xml"/> + </path> + <filterchain> + <expandproperties/> + </filterchain> + </concat> + </pre> + + <p><b>Filter the lines containing project from build.xml and output + them to report.output, prepending with a header</b></p> + <pre> + <concat destfile="${build.dir}/report.output"> + <header filtering="no" trimleading="yes"> + Lines that contain project + ========================== + </header> + <path path="build.xml"/> + <filterchain> + <linecontains> + <contains value="project"/> + </linecontains> + </filterchain> + </concat> + </pre> + <hr> <p align="center"> - Copyright © 2002 Apache Software Foundation. All + Copyright © 2002-2003 Apache Software Foundation. All Rights Reserved. </p> 1.6 +101 -0 ant/src/etc/testcases/taskdefs/concat.xml Index: concat.xml =================================================================== RCS file: /home/cvs/ant/src/etc/testcases/taskdefs/concat.xml,v retrieving revision 1.5 retrieving revision 1.6 diff -u -r1.5 -r1.6 --- concat.xml 18 Nov 2002 15:22:52 -0000 1.5 +++ concat.xml 28 Mar 2003 12:41:01 -0000 1.6 @@ -7,6 +7,12 @@ <property name="world" value="World" /> + <target name="cleanup"> + <delete file="TESTDEST"/> + <delete file="${tmp.file}"/> + <delete file="${tmp.file.2}"/> + </target> + <target name="test1"> <concat> </concat> @@ -52,4 +58,99 @@ </concat> </target> + <target name="testPath"> + <concat destfile="${tmp.file.2}"> + <path path="${tmp.file}"/> + </concat> + </target> + + <target name="testAppend"> + <concat destfile="${tmp.file.2}"> + <path path="${tmp.file}"/> + </concat> + <concat destfile="${tmp.file.2}" append="true"> + <path path="${tmp.file}"/> + </concat> + </target> + + <target name="testfilter"> + <concat destfile="${tmp.file}">@REPLACEME@</concat> + <concat> + <path path="${tmp.file}"/> + <filterchain> + <replacetokens> + <token key="REPLACEME" value="REPLACED"/> + </replacetokens> + </filterchain> + </concat> + </target> + + <target name="testnooverwrite"> + <touch file="${tmp.file.2}"/> + <!-- concat.xml is now older than tmp.file.2 + so the following should not do anything --> + <concat destfile="${tmp.file.2}" force="false"> + <path path="concat.xml"/> + </concat> + </target> + + <target name="testheaderfooter"> + <concat> + <header filtering="false" trim="yes"> + header + </header> + <path path="${tmp.file}"/> + <footer filtering="no">footer</footer> + </concat> + </target> + + <target name="testfileheader"> + <concat> + <header file="${tmp.file}"/> + <path path="${tmp.file}"/> + </concat> + </target> + + <target name="samefile"> + <touch file="${tmp.file}"/> + <concat destfile="${tmp.file}"> + <path path="${tmp.file}"/> + </concat> + </target> + + <target name="testfilterinline"> + <concat> + @REPLACEME@ + <filterchain> + <replacetokens> + <token key="REPLACEME" value="REPLACED"/> + </replacetokens> + </filterchain> + </concat> + </target> + + <target name="testmultireader"> + <concat destfile="${tmp.file}">Hello, World + </concat> + <concat destfile="${tmp.file.2}">Bye, World + </concat> + <concat> + <path path="${tmp.file}"/> + <path path="${tmp.file}"/> + <path path="${tmp.file}"/> + <path path="${tmp.file}"/> + <path path="${tmp.file}"/> + <path path="${tmp.file}"/> + <path path="${tmp.file}"/> + <path path="${tmp.file}"/> + <path path="${tmp.file}"/> + <!-- tailfilter seems to behave a little stange, place two + here in case the implementation changes --> + <path path="${tmp.file.2}"/> + <path path="${tmp.file.2}"/> + <filterchain> + <tailfilter lines="2"/> + </filterchain> + </concat> + </target> </project> 1.17 +410 -262 ant/src/main/org/apache/tools/ant/taskdefs/Concat.java Index: Concat.java =================================================================== RCS file: /home/cvs/ant/src/main/org/apache/tools/ant/taskdefs/Concat.java,v retrieving revision 1.16 retrieving revision 1.17 diff -u -r1.16 -r1.17 --- Concat.java 18 Feb 2003 13:20:24 -0000 1.16 +++ Concat.java 28 Mar 2003 12:41:01 -0000 1.17 @@ -51,18 +51,23 @@ * information on the Apache Software Foundation, please see * <http://www.apache.org/>. */ + package org.apache.tools.ant.taskdefs; import java.io.BufferedReader; +import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.Reader; import java.io.StringReader; import java.io.Writer; import java.util.Enumeration; @@ -72,8 +77,12 @@ import org.apache.tools.ant.Project; import org.apache.tools.ant.ProjectHelper; import org.apache.tools.ant.Task; +import org.apache.tools.ant.filters.util.ChainReaderHelper; import org.apache.tools.ant.types.FileList; import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.FilterChain; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.util.FileUtils; import org.apache.tools.ant.util.StringUtils; // 1.1 /** @@ -93,6 +102,7 @@ * </pre> * * @author <a href="mailto:[EMAIL PROTECTED]">Derek Slager</a> + * @author Peter Reilly */ public class Concat extends Task { @@ -127,7 +137,20 @@ * Stores a collection of file sets and/or file lists, used to * select multiple files for concatenation. */ - private Vector fileSets = new Vector(); // 1.1 + private Vector sources = new Vector(); + + /** for filtering the concatenated */ + private Vector filterChains = null; + /** ignore dates on input files */ + private boolean forceOverwrite = true; + /** String to place at the start of the concatented stream */ + private TextElement footer; + /** String to place at the end of the concatented stream */ + private TextElement header; + private Vector sourceFiles = new Vector(); + + /** 1.1 utilities and copy utilities */ + private static FileUtils fileUtils = FileUtils.newFileUtils(); // Constructors. @@ -156,27 +179,54 @@ } /** - * Sets the encoding for the input files, used when displaying the - * data via the console. + * Sets the character encoding */ public void setEncoding(String encoding) { this.encoding = encoding; } + /** + * Force overwrite existing destination file + * @since Ant 1.6 + */ + public void setForce(boolean force) { + this.forceOverwrite = force; + } + // Nested element creators. /** + * Path of files to concatenate. + * @since Ant 1.6 + */ + public Path createPath() { + Path path = new Path(getProject()); + sources.addElement(path); + return path; + } + + /** * Set of files to concatenate. */ public void addFileset(FileSet set) { - fileSets.addElement(set); + sources.addElement(set); } /** * List of files to concatenate. */ public void addFilelist(FileList list) { - fileSets.addElement(list); + sources.addElement(list); + } + + /** + * Adds a FilterChain. + * @since Ant 1.6 + */ + public void addFilterChain(FilterChain filterChain) { + if (filterChains == null) + filterChains = new Vector(); + filterChains.addElement(filterChain); } /** @@ -195,6 +245,22 @@ } /** + * Add a header to the concatenated output + * @since Ant 1.6 + */ + public void addHeader(TextElement el) { + this.header = el; + } + + /** + * Add a footer to the concatenated output + * @since Ant 1.6 + */ + public void addFooter(TextElement el) { + this.footer = el; + } + + /** * This method performs the concatenation. */ public void execute() @@ -204,7 +270,7 @@ sanitizeText(); // Sanity check our inputs. - if (fileSets.size() == 0 && textBuffer == null) { + if (sources.size() == 0 && textBuffer == null) { // Nothing to concatenate! throw new BuildException("At least one file " + "must be provided, or " + @@ -214,69 +280,59 @@ // If using filesets, disallow inline text. This is similar to // using GNU 'cat' with file arguments -- stdin is simply // ignored. - if (fileSets.size() > 0 && textBuffer != null) { + if (sources.size() > 0 && textBuffer != null) { throw new BuildException("Cannot include inline text " + "when using filesets."); } - boolean savedAppend = append; - try { - // Iterate the FileSet collection, concatenating each file as - // it is encountered. - for (Enumeration e = fileSets.elements(); e.hasMoreElements();) { - - // Root directory for files. - File fileSetBase = null; - - // List of files. - String[] srcFiles = null; - - // Get the next file set, which could be a FileSet or a - // FileList instance. - Object next = e.nextElement(); - - if (next instanceof FileSet) { - - FileSet fileSet = (FileSet) next; - - // Get a directory scanner from the file set, which will - // determine the files from the set which need to be - // concatenated. - DirectoryScanner scanner = - fileSet.getDirectoryScanner(getProject()); - - // Determine the root path. - fileSetBase = fileSet.getDir(getProject()); - - // Get the list of files. - srcFiles = scanner.getIncludedFiles(); - - } else if (next instanceof FileList) { - - FileList fileList = (FileList) next; - - // Determine the root path. - fileSetBase = fileList.getDir(getProject()); - - // Get the list of files. - srcFiles = fileList.getFiles(getProject()); - - } + // Iterate thru the sources - paths, filesets and filelists + for (Enumeration e = sources.elements(); e.hasMoreElements();) { + Object o = e.nextElement(); + if (o instanceof Path) { + Path path = (Path) o; + checkAddFiles(null, path.list()); + + } else if (o instanceof FileSet) { + FileSet fileSet = (FileSet) o; + DirectoryScanner scanner = + fileSet.getDirectoryScanner(getProject()); + checkAddFiles(fileSet.getDir(getProject()), + scanner.getIncludedFiles()); + + } else if (o instanceof FileList) { + FileList fileList = (FileList) o; + checkAddFiles(fileList.getDir(getProject()), + fileList.getFiles(getProject())); + } + } - // Concatenate the files. - if (srcFiles != null) { - catFiles(fileSetBase, srcFiles); - } else { - log("Warning: Concat received empty fileset.", - Project.MSG_WARN); + // check if the files are outofdate + if (destinationFile != null && !forceOverwrite + && (sourceFiles.size() > 0) && destinationFile.exists()) { + boolean outofdate = false; + for (int i = 0; i < sourceFiles.size(); ++i) { + File file = (File) sourceFiles.elementAt(i); + if (file.lastModified() > destinationFile.lastModified()) { + outofdate = true; + break; } } - } finally { - append = savedAppend; + if (!outofdate) { + log(destinationFile + " is up-to-date.", Project.MSG_VERBOSE); + return; // no need to do anything + } } - - // Now, cat the inline text, if applicable. - catText(); + + // Do nothing if all the sources are not present + // And textBuffer is null + if (textBuffer == null && sourceFiles.size() == 0 + && header == null && footer == null) { + log("No existing files and no nested text, doing nothing", + Project.MSG_INFO); + return; + } + + cat(); } /** @@ -284,254 +340,346 @@ */ public void reset() { append = false; + forceOverwrite = true; destinationFile = null; encoding = null; - fileSets = new Vector(); + sources.removeAllElements(); + sourceFiles.removeAllElements(); + filterChains = null; + footer = null; + header = null; } - /** - * This method concatenates a series of files to a single - * destination. - * - * @param base the base directory for the list of file names. - * - * @param files the names of the files to be concatenated, - * relative to the <code>base</code>. - */ - private void catFiles(File base, String[] files) { - - // First, create a list of absolute paths for the input files. - Vector inputFileNames = new Vector(); - for (int i = 0; i < files.length; i++) { - - File current = new File(base, files[i]); - - // Make sure the file exists. This will rarely fail when - // using file sets, but it could be rather common when - // using file lists. - if (!current.exists()) { - // File does not exist, log an error and continue. - log("File " + current + " does not exist.", - Project.MSG_ERR); + private void checkAddFiles(File base, String[] filenames) { + for (int i = 0; i < filenames.length; ++i) { + File file = new File(base, filenames[i]); + if (!file.exists()) { + log("File " + file + " does not exist.", Project.MSG_ERR); continue; } - - inputFileNames.addElement(current.getAbsolutePath()); + if (destinationFile != null + && fileUtils.fileNameEquals(destinationFile, file)) { + throw new BuildException("Input file \"" + + file + "\" " + + "is the same as the output file."); + } + sourceFiles.addElement(file); } + } + + /** perform the concatenation */ + private void cat() { + OutputStream os = null; + Reader reader = null; + char[] buffer = new char[8192]; - final int len = inputFileNames.size(); - if (len == 0) { - log("Warning: Could not find any of the files specified " + - "in concat task.", Project.MSG_WARN); - return; - } + try { - String[] input = new String[len]; - inputFileNames.copyInto(input); + if (destinationFile == null) { + // Log using WARN so it displays in 'quiet' mode. + os = new LogOutputStream(this, Project.MSG_WARN); + } else { + // ensure that the parent dir of dest file exists + File parent = fileUtils.getParentFile(destinationFile); + if (!parent.exists()) { + parent.mkdirs(); + } - // Next, perform the concatenation. - if (encoding == null) { - OutputStream os = null; - InputStream is = null; + os = new FileOutputStream(destinationFile.getAbsolutePath(), + append); + } - try { + PrintWriter writer = null; + if (encoding == null) { + writer = new PrintWriter( + new BufferedWriter( + new OutputStreamWriter(os))); + } else { + writer = new PrintWriter( + new BufferedWriter( + new OutputStreamWriter(os, encoding))); + } - if (destinationFile == null) { - // Log using WARN so it displays in 'quiet' mode. - os = new LogOutputStream(this, Project.MSG_WARN); + + if (header != null) { + if (header.getFiltering()) { + concatenate( + buffer, writer, new StringReader(header.getValue())); } else { - os = - new FileOutputStream(destinationFile.getAbsolutePath(), - append); - - // This flag should only be recognized for the first - // file. In the context of a single 'cat', we always - // want to append. - append = true; + writer.print(header.getValue()); } - - for (int i = 0; i < len; i++) { + } - // Make sure input != output. - if (destinationFile != null && - destinationFile.getAbsolutePath().equals(input[i])) { - throw new BuildException("Input file \"" - + destinationFile.getName() - + "\" is the same as the output file."); - } + if (textBuffer != null) { + reader = new StringReader( + getProject().replaceProperties(textBuffer.toString())); + } else { + reader = new MultiReader(); + } + + concatenate(buffer, writer, reader); - is = new FileInputStream(input[i]); - byte[] buffer = new byte[8192]; - while (true) { - int bytesRead = is.read(buffer); - if (bytesRead == -1) { // EOF - break; - } - - // Write the read data. - os.write(buffer, 0, bytesRead); - } - os.flush(); - is.close(); - is = null; - } - } catch (IOException ioex) { - throw new BuildException("Error while concatenating: " - + ioex.getMessage(), ioex); - } finally { - if (is != null) { - try { - is.close(); - } catch (Exception ignore) {} - } - if (os != null) { - try { - os.close(); - } catch (Exception ignore) {} + if (footer != null) { + if (footer.getFiltering()) { + concatenate( + buffer, writer, new StringReader(footer.getValue())); + } else { + writer.print(footer.getValue()); } } - } else { // user specified encoding + writer.flush(); + os.flush(); - Writer out = null; - BufferedReader in = null; + } catch (IOException ioex) { + throw new BuildException("Error while concatenating: " + + ioex.getMessage(), ioex); + } finally { + if (reader != null) { + try {reader.close();} catch (IOException ignore) {} + } + if (os != null) { + try {os.close();} catch (IOException ignore) {} + } + } + } - try { - if (destinationFile == null) { - // Log using WARN so it displays in 'quiet' mode. - out = new OutputStreamWriter( - new LogOutputStream(this, Project.MSG_WARN)); - } else { - out = new OutputStreamWriter( - new FileOutputStream(destinationFile - .getAbsolutePath(), - append), - encoding); - - // This flag should only be recognized for the first - // file. In the context of a single 'cat', we always - // want to append. - append = true; - } - for (int i = 0; i < len; i++) { - in = new BufferedReader( - new InputStreamReader(new FileInputStream(input[i]), - encoding)); - - String line; - char[] buffer = new char[4096]; - while (true) { - int charsRead = in.read(buffer); - if (charsRead == -1) { // EOF - break; - } - - // Write the read data. - out.write(buffer, 0, charsRead); - } - out.flush(); - in.close(); - in = null; - } - } catch (IOException ioe) { - throw new BuildException("Error while concatenating: " - + ioe.getMessage(), ioe); - } finally { - // Close resources. - if (in != null) { - try { - in.close(); - } catch (Exception ignore) {} - } - if (out != null) { - try { - out.close(); - } catch (Exception ignore) {} - } + /** Concatenate a single reader to the writer using buffer */ + private void concatenate(char[] buffer, Writer writer, Reader in) + throws IOException { + if (filterChains != null) { + ChainReaderHelper helper = new ChainReaderHelper(); + helper.setBufferSize(8192); + helper.setPrimaryReader(in); + helper.setFilterChains(filterChains); + helper.setProject(getProject()); + in = new BufferedReader(helper.getAssembledReader()); + } + + while (true) { + int nRead = in.read(buffer, 0, buffer.length); + if (nRead == -1) { + break; } + writer.write(buffer, 0, nRead); } + + writer.flush(); } /** - * This method concatenates the text which was added inside the - * 'concat' tags. If the text between the tags consists only of - * whitespace characters, it is ignored. + * Treat empty nested text as no text. + * + * <p>Depending on the XML parser, addText may have been called + * for "ignorable whitespace" as well.</p> */ - private void catText() { + private void sanitizeText() { + if (textBuffer != null) { + if (textBuffer.toString().trim().length() == 0) { + textBuffer = null; + } + } + } - // Check the buffer. - if (textBuffer == null) { - // No text to write. - return; + /** + * sub element points to a file or contains text + */ + public static class TextElement { + private String value; + private boolean trimLeading = false; + private boolean trim = false; + private boolean filtering = true; + + /** + * whether to filter the text in this element + * or not. + * + * @param filtering true if the text should be filtered. + * the default value is true. + */ + public void setFiltering(boolean filtering) { + this.filtering = filtering; + } + + /** return the filtering attribute */ + private boolean getFiltering() { + return filtering; } + + /** + * set the text using a file + * @param file the file to use + * @throws BuildException if the file does not exist, or cannot be + * read + */ + public void setFile(File file) { + // non-existing files are not allowed + if (!file.exists()) { + throw new BuildException("File " + file + " does not exist."); + } - String text = textBuffer.toString(); + BufferedReader reader = null; + try { + reader = new BufferedReader(new FileReader(file)); + value = fileUtils.readFully(reader); + } catch (IOException ex) { + throw new BuildException(ex); + } finally { + if (reader != null) { + try {reader.close();} catch (Throwable t) {} + } + } + } - // Replace ${property} strings. - text = getProject().replaceProperties(text); + /** + * set the text using inline + */ + public void addText(String value) { + if (value.trim().length() == 0) { + return; + } + this.value = value; + } - // Set up a writer if necessary. - FileWriter writer = null; - if (destinationFile != null) { - try { - writer = new FileWriter(destinationFile.getAbsolutePath(), - append); - } catch (IOException ioe) { - throw new BuildException("Error creating destination " + - "file.", ioe); + /** + * s:^\s*:: on each line of input + * @param strip if true do the trim + */ + public void setTrimLeading(boolean strip) { + this.trimLeading = strip; + } + + /** + * whether to call text.trim() + */ + public void setTrim(boolean trim) { + this.trim = trim; + } + + /** + * return the text, after possible trimming + */ + public String getValue() { + if (value == null) { + value = ""; } + if (value.trim().length() == 0) { + value = ""; + } + if (trimLeading) { + char[] current = value.toCharArray(); + StringBuffer b = new StringBuffer(current.length); + boolean startOfLine = true; + int pos = 0; + while (pos < current.length) { + char ch = current[pos++]; + if (startOfLine) { + if (ch == ' ' || ch == '\t') { + continue; + } + startOfLine = false; + } + b.append(ch); + if (ch == '\n' || ch == '\r') { + startOfLine = true; + } + } + value = b.toString(); + } + if (trim) { + value = value.trim(); + } + return value; } + } - // Reads the text, line by line. - BufferedReader reader = null; - try { - reader = new BufferedReader(new StringReader(text)); + /** + * This class reads from each of the source files in turn. + * The concatentated result can then be filtered as + * a single stream. + */ + private class MultiReader extends Reader { + private int pos = 0; + private Reader reader = null; - String line; - while ((line = reader.readLine()) != null) { - if (destinationFile == null) { - // Log the line, using WARN so it displays in - // 'quiet' mode. - log(line, Project.MSG_WARN); + private Reader getReader() throws IOException { + if (reader == null) { + if (encoding == null) { + reader = new BufferedReader( + new FileReader((File) sourceFiles.elementAt(pos))); } else { - writer.write(line); - writer.write(StringUtils.LINE_SEP); - writer.flush(); - } + // invoke the zoo of io readers + reader = new BufferedReader( + new InputStreamReader( + new FileInputStream( + (File) sourceFiles.elementAt(pos)), + encoding)); + } } + return reader; + } - } catch (IOException ioe) { - throw new BuildException("Error while concatenating " + - "text.", ioe); - } finally { - // Close resources. - if (reader != null) { - try { + /** + * Read a character from the current reader object. Advance + * to the next if the reader is finished. + * @return the character read, -1 for EOF on the last reader. + * @exception IOException - possiblly thrown by the read for a reader + * object. + */ + public int read() throws IOException { + while (pos < sourceFiles.size()) { + int ch = getReader().read(); + if (ch == -1) { reader.close(); - } catch (Exception ignore) {} + reader = null; + } else { + return ch; + } + pos++; } + return -1; + } - if (writer != null) { - try { - writer.close(); - } catch (Exception ignore) {} + /** + * Read into the buffer <code>cbuf</code>. + * @param cbuf The array to be read into. + * @param off The offset. + * @param len The length to read. + * @exception IOException - possiblely thrown by the reads to the + * reader objects. + */ + public int read(char cbuf[], int off, int len) + throws IOException { + int amountRead = 0; + int iOff = off; + while (pos < sourceFiles.size()) { + int nRead = getReader().read(cbuf, off, len); + if (nRead == -1 || nRead == 0) { + reader.close(); + reader = null; + pos++; + } else { + len -= nRead; + off += nRead; + amountRead += nRead; + if (len == 0) { + return amountRead; + } + } + } + if (amountRead == 0) { + return -1; + } else { + return amountRead; } } - } - /** - * Treat empty nested text as no text. - * - * <p>Depending on the XML parser, addText may have been called - * for "ignorable whitespace" as well.</p> - */ - private void sanitizeText() { - if (textBuffer != null) { - if (textBuffer.toString().trim().length() == 0) { - textBuffer = null; + public void close() throws IOException { + if (reader != null) { + reader.close(); } } } + } -} 1.6 +78 -10 ant/src/testcases/org/apache/tools/ant/taskdefs/ConcatTest.java Index: ConcatTest.java =================================================================== RCS file: /home/cvs/ant/src/testcases/org/apache/tools/ant/taskdefs/ConcatTest.java,v retrieving revision 1.5 retrieving revision 1.6 diff -u -r1.5 -r1.6 --- ConcatTest.java 10 Feb 2003 14:14:45 -0000 1.5 +++ ConcatTest.java 28 Mar 2003 12:41:01 -0000 1.6 @@ -1,7 +1,7 @@ /* * The Apache Software License, Version 1.1 * - * Copyright (c) 2002 The Apache Software Foundation. All rights + * Copyright (c) 2002-2003 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without @@ -97,14 +97,7 @@ * test. */ public void tearDown() { - // Remove temporary files. - String[] rm = new String[] { tempFile, tempFile2 }; - for (int i = 0; i < rm.length; i++) { - File f = new File(getProjectDir(), rm[i]); - if (f.exists()) { - f.delete(); - } - } + executeTarget("cleanup"); } /** @@ -140,7 +133,6 @@ * Cats the file created in test3 three times. */ public void test4() { - test3(); File file = new File(getProjectDir(), tempFile); @@ -174,4 +166,80 @@ public void testConcatNoNewlineEncoding() { expectLog("testConcatNoNewlineEncoding", "ab"); } + + public void testPath() { + test3(); + + File file = new File(getProjectDir(), tempFile); + final long origSize = file.length(); + + executeTarget("testPath"); + + File file2 = new File(getProjectDir(), tempFile2); + final long newSize = file2.length(); + + assertEquals(origSize, newSize); + + } + public void testAppend() { + test3(); + + File file = new File(getProjectDir(), tempFile); + final long origSize = file.length(); + + executeTarget("testAppend"); + + File file2 = new File(getProjectDir(), tempFile2); + final long newSize = file2.length(); + + assertEquals(origSize*2, newSize); + + } + + public void testFilter() { + executeTarget("testfilter"); + assertTrue(getLog().indexOf("REPLACED") > -1); + } + + public void testNoOverwrite() { + executeTarget("testnooverwrite"); + File file2 = new File(getProjectDir(), tempFile2); + long size = file2.length(); + assertEquals(size, 0); + } + + public void testheaderfooter() { + test3(); + expectLog("testheaderfooter", "headerHello, World!footer"); + } + + public void testfileheader() { + test3(); + expectLog("testfileheader", "Hello, World!Hello, World!"); + } + + /** + * Expect an exception when attempting to cat an file to itself + */ + public void testsame() { + expectBuildException("samefile", "output file same as input"); + } + + /** + * Check if filter inline works + */ + public void testfilterinline() { + executeTarget("testfilterinline"); + assertTrue(getLog().indexOf("REPLACED") > -1); + } + + /** + * Check if multireader works + */ + public void testmultireader() { + executeTarget("testmultireader"); + assertTrue(getLog().indexOf("Bye") > -1); + assertTrue(getLog().indexOf("Hello") == -1); + } + }