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 &quot;no&quot;.
  +         should be appended. Defaults to &quot;no&quot;.
  +       </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 &quot;yes&quot;.
          </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 @@
     &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;
  +          force=&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 +187,40 @@
     &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>
  +
  +      <p><b>Filter the lines containing project from build.xml and output
  +           them to report.output, prepending with a header</b></p>
  +        <pre>
  +   &lt;concat destfile="${build.dir}/report.output"&gt;
  +      &lt;header filtering="no" trimleading="yes"&gt;
  +          Lines that contain project
  +          ==========================
  +      &lt;/header&gt;
  +      &lt;path path="build.xml"/&gt;
  +      &lt;filterchain&gt;
  +         &lt;linecontains&gt;
  +           &lt;contains value="project"/&gt;
  +         &lt;/linecontains&gt;
  +      &lt;/filterchain&gt;
  +   &lt;/concat&gt;
  +        </pre>
  +
         <hr>
   
         <p align="center">
  -     Copyright &copy; 2002 Apache Software Foundation. All
  +     Copyright &copy; 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 &quot;ignorable whitespace&quot; 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 &quot;ignorable whitespace&quot; 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);
  +    }        
  +        
   }
  
  
  

Reply via email to