Index: docs/manual/CoreTasks/concat.html
===================================================================
RCS file: /home/cvspublic/ant/docs/manual/CoreTasks/concat.html,v
retrieving revision 1.6
diff -u -r1.6 concat.html
--- docs/manual/CoreTasks/concat.html	4 Sep 2002 11:05:16 -0000	1.6
+++ docs/manual/CoreTasks/concat.html	21 Mar 2003 09:43:08 -0000
@@ -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,16 @@
 	  <td valign="top">append</td>
 	  <td valign="top">
 	    Specifies whether or not the file specified by 'destfile'
-	    should be overwritten. Defaults to &quot;no&quot;.
+	    should be appended. Defaults to &quot;no&quot;.
+	  </td>
+	  <td valign="top" align="center">No</td>
+	</tr>
+	<tr>
+	  <td valign="top">overwrite</td>
+	  <td valign="top">
+	    Specifies whether or not the file specified by 'destfile'
+	    should be written to if is newer that all the source files.
+        Defaults to &quot;yes&quot;.
 	  </td>
 	  <td valign="top" align="center">No</td>
 	</tr>
@@ -71,6 +79,11 @@
       </table>
 
       <h3>Parameters specified as nested elements</h3>
+        <h4>path</h4>
+        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.
 
       <h4>fileset</h4>
 
@@ -81,7 +94,6 @@
 	<i>not</i> guaranteed. If this is an issue, use multiple
 	filesets or consider using filelists.
       </p>
-
       <h4>filelist</h4>
 
       <p>
@@ -90,6 +102,37 @@
         <var>files</var> attribute will be the same order in which the
         files are concatenated.
       </p>
+      <h4>filterchain</h4>
+      <p>The concat task supports nested 
+        <a href="../CoreTypes/filterchain.html"> FilterChain</a>s.</p>
+
+      <h4>header,footer</h4>
+      <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">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, default is "no"
+          </td>
+          <td valign="top" align = "center">No</td>
+        </tr>
+      </table>
 
       <h3>Examples</h3>
 
@@ -115,11 +158,12 @@
   &lt;/concat&gt;
       </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>
-  &lt;concat destfile=&quot;${docbook.dir}/all-sections.xml&quot;&gt;
+  &lt;concat destfile=&quot;${docbook.dir}/all-sections.xml&quot;
+          overwrite=&quot;no&quot;&gt;
     &lt;filelist dir=&quot;${docbook.dir}/sections&quot; 
          files=&quot;introduction.xml,overview.xml&quot;/&gt;
     &lt;fileset dir=&quot;${docbook.dir}&quot; 
@@ -128,10 +172,22 @@
   &lt;/concat&gt;
       </pre>
 
+      <p><b>Concatenate a series of files, expanding ant properties</b></p>
+        <pre>
+   &lt;concat destfile="${build.dir}/subs"&gt;
+      &lt;path&gt;
+        &lt;fileset dir="${src.dir}" includes="*.xml"/&gt;
+        &lt;pathelement location="build.xml"/&gt;
+      &lt;/path&gt;
+      &lt;filterchain&gt;
+        &lt;expandproperties/&gt;
+      &lt;/filterchain&gt;
+   &lt;/concat&gt;
+        </pre>
       <hr>
 
       <p align="center">
-	Copyright &copy; 2002 Apache Software Foundation. All
+	Copyright &copy; 2003 Apache Software Foundation. All
 	Rights Reserved.
       </p>
 
Index: src/etc/testcases/taskdefs/concat.xml
===================================================================
RCS file: /home/cvspublic/ant/src/etc/testcases/taskdefs/concat.xml,v
retrieving revision 1.5
diff -u -r1.5 concat.xml
--- src/etc/testcases/taskdefs/concat.xml	18 Nov 2002 15:22:52 -0000	1.5
+++ src/etc/testcases/taskdefs/concat.xml	21 Mar 2003 09:43:22 -0000
@@ -32,6 +32,8 @@
   </target>
 
   <target name="test6">
+    <!-- remove non existant file if is does exist due to previous fails -->
+    <delete quiet="yes" file="TESTDEST"/>
     <concat destfile="TESTDEST" append="true">
       <filelist dir="${basedir}" files="thisfiledoesnotexist"/>
     </concat>
@@ -52,4 +54,62 @@
     </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">
+    <delete quiet="yes" file="${tmp.file.2}"/>
+    <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}" overwrite="false">
+      <path path="concat.xml"/>
+    </concat>
+  </target>
+
+  <target name="testheaderfooter">
+    <concat>
+      <header>header</header>
+      <path path="${tmp.file}"/>
+      <footer>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>
 </project>
Index: src/main/org/apache/tools/ant/taskdefs/Concat.java
===================================================================
RCS file: /home/cvspublic/ant/src/main/org/apache/tools/ant/taskdefs/Concat.java,v
retrieving revision 1.16
diff -u -r1.16 Concat.java
--- src/main/org/apache/tools/ant/taskdefs/Concat.java	18 Feb 2003 13:20:24 -0000	1.16
+++ src/main/org/apache/tools/ant/taskdefs/Concat.java	21 Mar 2003 09:43:25 -0000
@@ -51,30 +51,22 @@
  * 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.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-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.StringReader;
-import java.io.Writer;
-import java.util.Enumeration;
-import java.util.Vector;
-import org.apache.tools.ant.BuildException;
-import org.apache.tools.ant.DirectoryScanner;
-import org.apache.tools.ant.Project;
-import org.apache.tools.ant.ProjectHelper;
-import org.apache.tools.ant.Task;
-import org.apache.tools.ant.types.FileList;
-import org.apache.tools.ant.types.FileSet;
-import org.apache.tools.ant.util.StringUtils; // 1.1
+
+import java.util.regex.*;
+import java.util.*;
+
+import org.apache.tools.ant.types.*;
+import org.apache.tools.ant.filters.*;
+import org.apache.tools.ant.filters.util.ChainReaderHelper;
+import org.apache.tools.ant.util.regexp.*;
+import org.apache.tools.ant.util.*;
+import org.apache.tools.ant.*;
+import org.apache.tools.ant.taskdefs.*;
+import java.io.*;
+
 
 /**
  * This class contains the 'concat' task, used to concatenate a series
@@ -93,58 +85,31 @@
  * </pre>
  *
  * @author <a href="mailto:derek@activate.net">Derek Slager</a>
+ * @author Peter Reilly
  */
-public class Concat extends Task {
-
-    // Attributes.
-
-    /**
-     * The destination of the stream. If <code>null</code>, the system
-     * console is used.
-     */
-    private File destinationFile = null;
-
-    /**
-     * Whether or not the stream should be appended if the destination file 
-     * exists.
-     * Defaults to <code>false</code>.
-     */
-    private boolean append = false;
-
-    /**
-     * Stores the input file encoding.
-     */
-    private String encoding = null;
-
-    // Child elements.
-
-    /**
-     * This buffer stores the text within the 'concat' element.
-     */
-    private StringBuffer textBuffer;
-
-    /**
-     * Stores a collection of file sets and/or file lists, used to
-     * select multiple files for concatenation.
-     */
-    private Vector fileSets = new Vector(); // 1.1
-
-    // Constructors.
-
-    /**
-     * Public, no-argument constructor. Required by Ant.
-     */
-    public Concat() {}
-
-    // Attribute setters.
-
+public class Concat
+    extends Task
+{
+    private Vector        sources = new Vector();
+    private Vector        filterChains = null;
+    private File          destFile = null;
+    private boolean       forceOverwrite = true;
+    private TextElement   footer;
+    private TextElement   header;
+    private String        encoding = null;
+    private boolean       append = false;
+    private StringBuffer  textBuffer = null;
+    private Vector        sourceFiles = new Vector();
+    private FileUtils     fileUtils = FileUtils.newFileUtils();
+    
     /**
      * Sets the destination file, or uses the console if not specified.
      */
-    public void setDestfile(File destinationFile) {
-        this.destinationFile = destinationFile;
+    public void setDestfile(File destFile) {
+        this.destFile = destFile;
     }
 
+
     /**
      * Sets the behavior when the destination file exists. If set to
      * <code>true</code> the stream data will be appended to the
@@ -156,370 +121,331 @@
     }
 
     /**
-     * Sets the encoding for the input files, used when displaying the
-     * data via the console.
+     * Sets the character encoding
      */
-    public void setEncoding(String encoding) {
+    public void setEncoding (String encoding) {
         this.encoding = encoding;
     }
 
-    // Nested element creators.
+
+    /**
+     * Path of files to concatenate.
+     */
+     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);
     }
-
+    
     /**
      * This method adds text which appears in the 'concat' element.
      */
     public void addText(String text) {
         if (textBuffer == null) {
-            // Initialize to the size of the first text fragment, with
-            // the hopes that it's the only one.
             textBuffer = new StringBuffer(text.length());
         }
-
-        // Append the fragment -- we defer property replacement until
-        // later just in case we get a partial property in a fragment.
+            
         textBuffer.append(text);
     }
 
     /**
-     * This method performs the concatenation.
+     * Adds a FilterChain.
+     */
+    public void addFilterChain(FilterChain filterChain) {
+        if (filterChains == null)
+            filterChains = new Vector();
+        filterChains.addElement(filterChain);
+    }
+
+    /**
+     * Overwrite existing destination file
+     */
+    public void setOverwrite(boolean overwrite) {
+        this.forceOverwrite = overwrite;
+    }
+
+    /**
+     * Add a header to the concatenated output
+     */
+    public void addHeader(TextElement el) {
+        this.header = el;
+    }
+
+    /**
+     * Add a footer to the concatenated output
      */
-    public void execute() 
-        throws BuildException {
+    public void addFooter(TextElement el) {
+        this.footer = el;
+    }
 
+    /**
+     * This method performs the concatenation.
+     */
+    public void execute() {
         // treat empty nested text as no text
         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 " + 
                                      "some text.");
         }
-
+        
         // 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());
-                    
-                }
-
-                // Concatenate the files.
-                if (srcFiles != null) {
-                    catFiles(fileSetBase, srcFiles);
-                } else {
-                    log("Warning: Concat received empty fileset.", 
-                        Project.MSG_WARN);
+        // 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()));
+            }
+        }
+        // check if the files are outofdate
+        if (destFile != null
+            && !forceOverwrite
+            && (sourceFiles.size() > 0)
+            && destFile.exists())
+        {
+            boolean outofdate = false;
+            for (int i = 0; i < sourceFiles.size(); ++i) {
+                File file = (File) sourceFiles.elementAt(i);
+                if (file.lastModified() > destFile.lastModified())
+                {
+                    outofdate = true;
+                    break;
                 }
             }
-        } finally {
-            append = savedAppend;
+            if (! outofdate)
+                return; // no need to do anything
         }
-        
-        // Now, cat the inline text, if applicable.
-        catText();
-    }
-
-    /**
-     * Reset state to default.
-     */
-    public void reset() {
-        append = false;
-        destinationFile = null;
-        encoding = null;
-        fileSets = new Vector();
+        // Do nothing if all the sources are not present
+        // And textBuffer is null
+        if (textBuffer == null && sourceFiles.size() == 0) {
+            log("No existing files, doing nothing", Project.MSG_WARN);
+            return;
+        }
+        catFiles();
     }
 
-    /**
-     * 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 (destFile != null &&
+                destFile.getAbsolutePath().equals(
+                    file.getAbsolutePath()))
+            {
+                throw new BuildException(
+                    "Input file \"" +
+                    file.getName() + "\" is the same as the output file.");
+            }
+            sourceFiles.addElement(file);
         }
+    }
+    
 
-        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;
-        }
+    /** perform the concatenation */
+    private void catFiles() {
+        OutputStream os = null;
+        InputStream  is = null;
+        char[]       buffer = new char[8192];
 
-        String[] input = new String[len];
-        inputFileNames.copyInto(input);
+        try {
 
-        // Next, perform the concatenation.
-        if (encoding == null) {
-            OutputStream os = null;
-            InputStream is = null;
+            if (destFile == 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(destFile);
+                if (! parent.exists())
+                    parent.mkdirs();
+                
+                os =
+                    new FileOutputStream(destFile.getAbsolutePath(), append);
 
-            try {
+            }
 
-                if (destinationFile == null) {
-                    // Log using WARN so it displays in 'quiet' mode.
-                    os = new LogOutputStream(this, Project.MSG_WARN);
-                } 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;
-                }
-            
-                for (int i = 0; i < len; i++) {
+            PrintWriter writer = null;
+            if (encoding == null) {
+                writer = new PrintWriter(
+                    new BufferedWriter(
+                        new OutputStreamWriter(os)));
+            }
+            else {
+                writer = new PrintWriter(
+                    new BufferedWriter(
+                        new OutputStreamWriter(os,encoding)));
+            }
 
-                    // 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.");
-                    }
 
-                    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 (header != null) {
+                writer.println(header.getValue());
             }
 
-        } else { // user specified encoding
-
-            Writer out = null;
-            BufferedReader in = null;
+            if (textBuffer != null) {
+                writer.write(getProject().replaceProperties(
+                                 textBuffer.toString()));
+            }
 
-            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 < sourceFiles.size(); ++i) {
+                File file = (File) sourceFiles.elementAt(i);
+                is = new FileInputStream(file);
+                BufferedReader in = null;
+                if (encoding == null) {
+                    in = new BufferedReader(new InputStreamReader(is));
                 }
-
-                for (int i = 0; i < len; i++) {
+                else {
                     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) {}
+                        new InputStreamReader(is, encoding));
                 }
-                if (out != null) {
-                    try {
-                        out.close();
-                    } catch (Exception ignore) {}
+                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();
+                os.flush();
+                is.close();
+                is = null;
+            }
+
+            if (footer != null) {
+                writer.println(footer.getValue());
             }
+            writer.flush();
+            os.flush();
+
+        } catch (IOException ioex) {
+            throw new BuildException("Error while concatenating: "
+                                     + ioex.getMessage(), ioex);
+        }
+        catch (Throwable t) {
+            throw new BuildException("Error while concatenating: ", t);
+        }
+        finally {
+            try {is.close();} catch (Throwable ignore) {}
+            try {os.close();} catch (Throwable ignore) {}
         }
     }
 
+
     /**
-     * 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.
+     * sub element points to a file or contains text
      */
-    private void catText() {
-
-        // Check the buffer.
-        if (textBuffer == null) {
-            // No text to write.
-            return;
-        }
-
-        String text = textBuffer.toString();
 
-        // Replace ${property} strings.
-        text = getProject().replaceProperties(text);
+    public static class TextElement {
+        private FileUtils    fileUtils = FileUtils.newFileUtils();
+        private String   value;
+        private boolean  trimLeading = false;
+        private boolean  trim = false;
 
-        // Set up a writer if necessary.
-        FileWriter writer = null;
-        if (destinationFile != null) {
+        public void setFile(File file) {
+            // Ignore non existing files
+            if (! file.exists())
+                return;
+            BufferedReader reader = null;
             try {
-                writer = new FileWriter(destinationFile.getAbsolutePath(), 
-                                        append);
-            } catch (IOException ioe) {
-                throw new BuildException("Error creating destination " + 
-                                         "file.", ioe);
+                reader = new BufferedReader(
+                    new FileReader(file));
+                value = fileUtils.readFully(reader);
             }
-        }
-
-        // Reads the text, line by line.
-        BufferedReader reader = null;
-        try {
-            reader = new BufferedReader(new StringReader(text));
-
-            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);
-                } else {
-                    writer.write(line);
-                    writer.write(StringUtils.LINE_SEP);
-                    writer.flush();
-                }
+            catch (IOException ex) {
+                throw new BuildException(ex);
             }
-
-        } catch (IOException ioe) {
-            throw new BuildException("Error while concatenating " + 
-                                     "text.", ioe);
-        } finally {
-            // Close resources.
-            if (reader != null) {
-                try {
-                    reader.close();
-                } catch (Exception ignore) {}
+            finally {
+                try {reader.close();} catch (Throwable t) {}
             }
-
-            if (writer != null) {
-                try {
-                    writer.close();
-                } catch (Exception ignore) {}
+        }
+        
+        public void addText(String value) {
+            if (value.trim().length() == 0)
+                return;
+            this.value = value;
+        }
+        public void setTrimLeading(boolean strip) {
+            this.trimLeading = strip;
+        }
+        public void setTrim(boolean trim) {
+            this.trim = trim;
+        }
+        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;
         }
     }
-
     /**
      * Treat empty nested text as no text.
      *
@@ -533,5 +459,5 @@
             }
         }
     }
+ }
 
-}
Index: src/testcases/org/apache/tools/ant/taskdefs/ConcatTest.java
===================================================================
RCS file: /home/cvspublic/ant/src/testcases/org/apache/tools/ant/taskdefs/ConcatTest.java,v
retrieving revision 1.5
diff -u -r1.5 ConcatTest.java
--- src/testcases/org/apache/tools/ant/taskdefs/ConcatTest.java	10 Feb 2003 14:14:45 -0000	1.5
+++ src/testcases/org/apache/tools/ant/taskdefs/ConcatTest.java	21 Mar 2003 09:43:27 -0000
@@ -140,7 +140,6 @@
      * Cats the file created in test3 three times.
      */
     public void test4() {
-
         test3();
 
         File file = new File(getProjectDir(), tempFile);
@@ -173,5 +172,63 @@
 
     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");
     }
 }
