Author: mbenson
Date: Wed May  9 09:41:35 2007
New Revision: 536579

URL: http://svn.apache.org/viewvc?view=rev&rev=536579
Log:
massive refactorings to Concat: fix failing testcases in HEAD, and implement 
ResourceCollection

Added:
    ant/core/trunk/src/tests/antunit/taskdefs/utf-16.expected   (with props)
    ant/core/trunk/src/tests/antunit/types/resources/concat-resource-test.xml   
(with props)
Modified:
    ant/core/trunk/WHATSNEW
    ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/Concat.java
    ant/core/trunk/src/tests/antunit/taskdefs/concat-test.xml

Modified: ant/core/trunk/WHATSNEW
URL: 
http://svn.apache.org/viewvc/ant/core/trunk/WHATSNEW?view=diff&rev=536579&r1=536578&r2=536579
==============================================================================
--- ant/core/trunk/WHATSNEW (original)
+++ ant/core/trunk/WHATSNEW Wed May  9 09:41:35 2007
@@ -118,6 +118,8 @@
 * <scriptdef> now sources scripts from nested resources/resource collections. 
This lets you
   define scripts in JARs, remote URLs, or any other supported resource. 
Bugzilla report 41597.
 
+* <concat> is now usable as a single-element ResourceCollection.
+
 
 Changes from Ant 1.6.5 to Ant 1.7.0
 ===================================

Modified: ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/Concat.java
URL: 
http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/Concat.java?view=diff&rev=536579&r1=536578&r2=536579
==============================================================================
--- ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/Concat.java (original)
+++ ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/Concat.java Wed May  
9 09:41:35 2007
@@ -15,7 +15,6 @@
  *  limitations under the License.
  *
  */
-
 package org.apache.tools.ant.taskdefs;
 
 import java.io.File;
@@ -24,18 +23,18 @@
 import java.io.FileReader;
 import java.io.InputStream;
 import java.io.IOException;
-import java.io.PrintWriter;
 import java.io.OutputStream;
 import java.io.StringReader;
 import java.io.BufferedReader;
-import java.io.BufferedWriter;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStreamWriter;
 import java.util.Arrays;
-import java.util.Vector;
+import java.util.Collections;
 import java.util.Iterator;
+import java.util.Vector;
+
 import org.apache.tools.ant.Task;
 import org.apache.tools.ant.Project;
 import org.apache.tools.ant.BuildException;
@@ -54,8 +53,10 @@
 import org.apache.tools.ant.types.resources.selectors.Not;
 import org.apache.tools.ant.types.resources.selectors.Exists;
 import org.apache.tools.ant.types.resources.selectors.ResourceSelector;
-import org.apache.tools.ant.util.FileUtils;
 import org.apache.tools.ant.util.ConcatResourceInputStream;
+import org.apache.tools.ant.util.FileUtils;
+import org.apache.tools.ant.util.ReaderInputStream;
+import org.apache.tools.ant.util.StringUtils;
 
 /**
  * This class contains the 'concat' task, used to concatenate a series
@@ -74,7 +75,7 @@
  * </pre>
  *
  */
-public class Concat extends Task {
+public class Concat extends Task implements ResourceCollection {
 
     // The size of buffers to be used
     private static final int BUFFER_SIZE = 8192;
@@ -84,151 +85,504 @@
     private static final ResourceSelector EXISTS = new Exists();
     private static final ResourceSelector NOT_EXISTS = new Not(EXISTS);
 
-    // Attributes.
-
     /**
-     * The destination of the stream. If <code>null</code>, the system
-     * console is used.
-     */
-    private File destinationFile;
-
-    /**
-     * Whether or not the stream should be appended if the destination file
-     * exists.
-     * Defaults to <code>false</code>.
+     * sub element points to a file or contains text
      */
-    private boolean append;
+    public static class TextElement extends ProjectComponent {
+        private String   value = "";
+        private boolean  trimLeading = false;
+        private boolean  trim = false;
+        private boolean  filtering = true;
+        private String   encoding = null;
 
-    /**
-     * Stores the input file encoding.
-     */
-    private String encoding;
+        /**
+         * 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;
+        }
 
-    /** Stores the output file encoding. */
-    private String outputEncoding;
+        /** return the filtering attribute */
+        private boolean getFiltering() {
+            return filtering;
+        }
 
-    /** Stores the binary attribute */
-    private boolean binary;
+        /**
+         * The encoding of the text element
+         *
+         * @param encoding the name of the charset used to encode
+         */
+        public void setEncoding(String encoding) {
+            this.encoding = encoding;
+        }
 
-    // Child elements.
+        /**
+         * 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) throws BuildException {
+            // non-existing files are not allowed
+            if (!file.exists()) {
+                throw new BuildException("File " + file + " does not exist.");
+            }
 
-    /**
-     * This buffer stores the text within the 'concat' element.
-     */
-    private StringBuffer textBuffer;
+            BufferedReader reader = null;
+            try {
+                if (this.encoding == null) {
+                    reader = new BufferedReader(new FileReader(file));
+                } else {
+                    reader = new BufferedReader(
+                        new InputStreamReader(new FileInputStream(file),
+                                              this.encoding));
+                }
+                value = FileUtils.readFully(reader);
+            } catch (IOException ex) {
+                throw new BuildException(ex);
+            } finally {
+                FileUtils.close(reader);
+            }
+        }
 
-    /**
-     * Stores a collection of file sets and/or file lists, used to
-     * select multiple files for concatenation.
-     */
-    private Resources rc;
+        /**
+         * set the text using inline
+         * @param value the text to place inline
+         */
+        public void addText(String value) {
+            this.value += getProject().replaceProperties(value);
+        }
 
-    /** for filtering the concatenated */
-    private Vector filterChains;
-    /** 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;
-    /** add missing line.separator to files **/
-    private boolean fixLastLine = false;
-    /** endofline for fixlast line */
-    private String eolString;
-    /** outputwriter */
-    private Writer outputWriter = null;
+        /**
+         * s:^\s*:: on each line of input
+         * @param strip if true do the trim
+         */
+        public void setTrimLeading(boolean strip) {
+            this.trimLeading = strip;
+        }
 
-    /**
-     * Construct a new Concat task.
-     */
-    public Concat() {
-        reset();
-    }
+        /**
+         * whether to call text.trim()
+         * @param trim if true trim the text
+         */
+        public void setTrim(boolean trim) {
+            this.trim = trim;
+        }
 
-    /**
-     * Reset state to default.
-     */
-    public void reset() {
-        append = false;
-        forceOverwrite = true;
-        destinationFile = null;
-        encoding = null;
-        outputEncoding = null;
-        fixLastLine = false;
-        filterChains = null;
-        footer = null;
-        header = null;
-        binary = false;
-        outputWriter = null;
-        textBuffer = null;
-        eolString = System.getProperty("line.separator");
-        rc = null;
+        /**
+         * @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;
+        }
     }
 
-    // Attribute setters.
-
-    /**
-     * Sets the destination file, or uses the console if not specified.
-     * @param destinationFile the destination file
-     */
-    public void setDestfile(File destinationFile) {
-        this.destinationFile = destinationFile;
+    private interface ReaderFactory {
+        Reader getReader(Object o) throws IOException;
     }
 
     /**
-     * Sets the behavior when the destination file exists. If set to
-     * <code>true</code> the stream data will be appended to the
-     * existing file, otherwise the existing file will be
-     * overwritten. Defaults to <code>false</code>.
-     * @param append if true append to the file.
+     * This class reads from each of the source files in turn.
+     * The concatentated result can then be filtered as
+     * a single stream.
      */
-    public void setAppend(boolean append) {
-        this.append = append;
-    }
+    private class MultiReader extends Reader {
+        private Reader reader = null;
+        private int    lastPos = 0;
+        private char[] lastChars = new char[eolString.length()];
+        private boolean needAddSeparator = false;
+        private Iterator readerSources;
+        private ReaderFactory factory;
 
-    /**
-     * Sets the character encoding
-     * @param encoding the encoding of the input stream and unless
-     *        outputencoding is set, the outputstream.
-     */
-    public void setEncoding(String encoding) {
-        this.encoding = encoding;
-        if (outputEncoding == null) {
-            outputEncoding = encoding;
+        private MultiReader(Iterator readerSources, ReaderFactory factory) {
+            this.readerSources = readerSources;
+            this.factory = factory;
         }
-    }
-
-    /**
-     * Sets the character encoding for outputting
-     * @param outputEncoding the encoding for the output file
-     * @since Ant 1.6
-     */
-    public void setOutputEncoding(String outputEncoding) {
-        this.outputEncoding = outputEncoding;
-    }
 
-    /**
-     * Force overwrite existing destination file
-     * @param force if true always overwrite, otherwise only overwrite
-     *              if the output file is older any of the input files.
-     * @since Ant 1.6
-     */
-    public void setForce(boolean force) {
-        this.forceOverwrite = force;
-    }
+        private Reader getReader() throws IOException {
+            if (reader == null && readerSources.hasNext()) {
+                reader = factory.getReader(readerSources.next());
+                Arrays.fill(lastChars, (char) 0);
+            }
+            return reader;
+        }
 
-    // Nested element creators.
+        private void nextReader() throws IOException {
+            close();
+            reader = null;
+        }
 
-    /**
-     * Path of files to concatenate.
-     * @return the path used for concatenating
-     * @since Ant 1.6
-     */
-     public Path createPath() {
-        Path path = new Path(getProject());
-        add(path);
-        return path;
-    }
+        /**
+         * 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 - possibly thrown by the read for a reader
+         *            object.
+         */
+        public int read() throws IOException {
+            if (needAddSeparator) {
+                int ret = eolString.charAt(lastPos++);
+                if (lastPos >= eolString.length()) {
+                    lastPos = 0;
+                    needAddSeparator = false;
+                }
+                return ret;
+            }
+            while (getReader() != null) {
+                int ch = getReader().read();
+                if (ch == -1) {
+                    nextReader();
+                    if (fixLastLine && isMissingEndOfLine()) {
+                        needAddSeparator = true;
+                        lastPos = 0;
+                    }
+                } else {
+                    addLastChar((char) ch);
+                    return ch;
+                }
+            }
+            return -1;
+        }
+
+        /**
+         * 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 - possibly thrown by the reads to the
+         *            reader objects.
+         */
+        public int read(char[] cbuf, int off, int len)
+            throws IOException {
+
+            int amountRead = 0;
+            while (getReader() != null || needAddSeparator) {
+                if (needAddSeparator) {
+                    cbuf[off] = eolString.charAt(lastPos++);
+                    if (lastPos >= eolString.length()) {
+                        lastPos = 0;
+                        needAddSeparator = false;
+                    }
+                    len--;
+                    off++;
+                    amountRead++;
+                    if (len == 0) {
+                        return amountRead;
+                    }
+                    continue;
+                }
+                int nRead = getReader().read(cbuf, off, len);
+                if (nRead == -1 || nRead == 0) {
+                    nextReader();
+                    if (fixLastLine && isMissingEndOfLine()) {
+                        needAddSeparator = true;
+                        lastPos = 0;
+                    }
+                } else {
+                    if (fixLastLine) {
+                        for (int i = nRead;
+                                 i > (nRead - lastChars.length);
+                                 --i) {
+                            if (i <= 0) {
+                                break;
+                            }
+                            addLastChar(cbuf[off + i - 1]);
+                        }
+                    }
+                    len -= nRead;
+                    off += nRead;
+                    amountRead += nRead;
+                    if (len == 0) {
+                        return amountRead;
+                    }
+                }
+            }
+            if (amountRead == 0) {
+                return -1;
+            } else {
+                return amountRead;
+            }
+        }
+
+        /**
+         * Close the current reader
+         */
+        public void close() throws IOException {
+            if (reader != null) {
+                reader.close();
+            }
+        }
+
+        /**
+         * if checking for end of line at end of file
+         * add a character to the lastchars buffer
+         */
+        private void addLastChar(char ch) {
+            for (int i = lastChars.length - 2; i >= 0; --i) {
+                lastChars[i] = lastChars[i + 1];
+            }
+            lastChars[lastChars.length - 1] = ch;
+        }
+
+        /**
+         * return true if the lastchars buffer does
+         * not contain the lineseparator
+         */
+        private boolean isMissingEndOfLine() {
+            for (int i = 0; i < lastChars.length; ++i) {
+                if (lastChars[i] != eolString.charAt(i)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    private class ConcatResource extends Resource {
+        private ResourceCollection c;
+
+        private ConcatResource(ResourceCollection c) {
+            this.c = c;
+        }
+        public InputStream getInputStream() throws IOException {
+            if (binary) {
+                ConcatResourceInputStream result = new 
ConcatResourceInputStream(c);
+                result.setManagingComponent(this);
+                return result;
+            }
+            Reader resourceReader = getFilteredReader(
+                    new MultiReader(c.iterator(), resourceReaderFactory));
+            Reader rdr;
+            if (header == null && footer == null) {
+                rdr = resourceReader;
+            } else {
+                int readerCount = 1;
+                if (header != null) {
+                    readerCount++;
+                }
+                if (footer != null) {
+                    readerCount++;
+                }
+                Reader[] readers = new Reader[readerCount];
+                int pos = 0;
+                if (header != null) {
+                    readers[pos] = new StringReader(header.getValue());
+                    if (header.getFiltering()) {
+                        readers[pos] = getFilteredReader(readers[pos]);
+                    }
+                    pos++;
+                }
+                readers[pos++] = resourceReader;
+                if (footer != null) {
+                    readers[pos] = new StringReader(footer.getValue());
+                    if (footer.getFiltering()) {
+                        readers[pos] = getFilteredReader(readers[pos]);
+                    }
+                }
+                rdr = new MultiReader(Arrays.asList(readers).iterator(),
+                        identityReaderFactory);
+            }
+            return outputEncoding == null ? new ReaderInputStream(rdr)
+                    : new ReaderInputStream(rdr, outputEncoding);
+        }
+        public String getName() {
+            return "concat (" + String.valueOf(c) + ")";
+        }
+    }
+
+    // Attributes.
+
+    /**
+     * The destination of the stream. If <code>null</code>, the system
+     * console is used.
+     */
+    private File destinationFile;
+
+    /**
+     * Whether or not the stream should be appended if the destination file
+     * exists.
+     * Defaults to <code>false</code>.
+     */
+    private boolean append;
+
+    /**
+     * Stores the input file encoding.
+     */
+    private String encoding;
+
+    /** Stores the output file encoding. */
+    private String outputEncoding;
+
+    /** Stores the binary attribute */
+    private boolean binary;
+
+    // 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 ResourceCollection rc;
+
+    /** for filtering the concatenated */
+    private Vector filterChains;
+    /** 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;
+    /** add missing line.separator to files **/
+    private boolean fixLastLine = false;
+    /** endofline for fixlast line */
+    private String eolString;
+    /** outputwriter */
+    private Writer outputWriter = null;
+
+    private ReaderFactory resourceReaderFactory  = new ReaderFactory() {
+        public Reader getReader(Object o) throws IOException {
+            InputStream is = ((Resource) o).getInputStream();
+            return new BufferedReader(encoding == null
+                ? new InputStreamReader(is)
+                : new InputStreamReader(is, encoding));
+        }
+    };
+
+    private ReaderFactory identityReaderFactory = new ReaderFactory() {
+        public Reader getReader(Object o) {
+            return (Reader) o;
+        }
+    };
+
+    /**
+     * Construct a new Concat task.
+     */
+    public Concat() {
+        reset();
+    }
+
+    /**
+     * Reset state to default.
+     */
+    public void reset() {
+        append = false;
+        forceOverwrite = true;
+        destinationFile = null;
+        encoding = null;
+        outputEncoding = null;
+        fixLastLine = false;
+        filterChains = null;
+        footer = null;
+        header = null;
+        binary = false;
+        outputWriter = null;
+        textBuffer = null;
+        eolString = StringUtils.LINE_SEP;
+        rc = null;
+    }
+
+    // Attribute setters.
+
+    /**
+     * Sets the destination file, or uses the console if not specified.
+     * @param destinationFile the destination file
+     */
+    public void setDestfile(File destinationFile) {
+        this.destinationFile = destinationFile;
+    }
+
+    /**
+     * Sets the behavior when the destination file exists. If set to
+     * <code>true</code> the stream data will be appended to the
+     * existing file, otherwise the existing file will be
+     * overwritten. Defaults to <code>false</code>.
+     * @param append if true append to the file.
+     */
+    public void setAppend(boolean append) {
+        this.append = append;
+    }
+
+    /**
+     * Sets the character encoding
+     * @param encoding the encoding of the input stream and unless
+     *        outputencoding is set, the outputstream.
+     */
+    public void setEncoding(String encoding) {
+        this.encoding = encoding;
+        if (outputEncoding == null) {
+            outputEncoding = encoding;
+        }
+    }
+
+    /**
+     * Sets the character encoding for outputting
+     * @param outputEncoding the encoding for the output file
+     * @since Ant 1.6
+     */
+    public void setOutputEncoding(String outputEncoding) {
+        this.outputEncoding = outputEncoding;
+    }
+
+    /**
+     * Force overwrite existing destination file
+     * @param force if true always overwrite, otherwise only overwrite
+     *              if the output file is older any of the input files.
+     * @since Ant 1.6
+     */
+    public void setForce(boolean force) {
+        this.forceOverwrite = force;
+    }
+
+    // Nested element creators.
+
+    /**
+     * Path of files to concatenate.
+     * @return the path used for concatenating
+     * @since Ant 1.6
+     */
+     public Path createPath() {
+        Path path = new Path(getProject());
+        add(path);
+        return path;
+    }
 
     /**
      * Set of files to concatenate.
@@ -251,9 +605,18 @@
      * @param c the ResourceCollection to add.
      * @since Ant 1.7
      */
-    public void add(ResourceCollection c) {
-        rc = rc == null ? new Resources() : rc;
-        rc.add(c);
+    public synchronized void add(ResourceCollection c) {
+        if (rc == null) {
+            rc = c;
+            return;
+        }
+        if (!(rc instanceof Resources)) {
+            Resources newRc = new Resources();
+            newRc.setProject(getProject());
+            newRc.add(rc);
+            rc = newRc;
+       }
+        ((Resources) rc).add(c);
     }
 
     /**
@@ -354,545 +717,216 @@
     }
 
     /**
-     * Validate configuration options.
-     */
-    private ResourceCollection validate() {
-
-        // treat empty nested text as no text
-        sanitizeText();
-
-        // if binary check if incompatible attributes are used
-        if (binary) {
-            if (destinationFile == null) {
-                throw new BuildException(
-                    "destfile attribute is required for binary concatenation");
-            }
-            if (textBuffer != null) {
-                throw new BuildException(
-                    "Nested text is incompatible with binary concatenation");
-            }
-            if (encoding != null || outputEncoding != null) {
-                throw new BuildException(
-                    "Seting input or output encoding is incompatible with 
binary"
-                    + " concatenation");
-            }
-            if (filterChains != null) {
-                throw new BuildException(
-                    "Setting filters is incompatible with binary 
concatenation");
-            }
-            if (fixLastLine) {
-                throw new BuildException(
-                    "Setting fixlastline is incompatible with binary 
concatenation");
-            }
-            if (header != null || footer != null) {
-                throw new BuildException(
-                    "Nested header or footer is incompatible with binary 
concatenation");
-            }
-        }
-        if (destinationFile != null && outputWriter != null) {
-            throw new BuildException(
-                "Cannot specify both a destination file and an output writer");
-        }
-        // Sanity check our inputs.
-        if (rc == null && textBuffer == null) {
-            // Nothing to concatenate!
-            throw new BuildException(
-                "At least one resource must be provided, or some text.");
-        }
-        if (rc != null) {
-            // If using resources, disallow inline text. This is similar to
-            // using GNU 'cat' with file arguments -- stdin is simply
-            // ignored.
-            if (textBuffer != null) {
-                throw new BuildException(
-                    "Cannot include inline text when using resources.");
-            }
-            Restrict noexistRc = new Restrict();
-            noexistRc.add(NOT_EXISTS);
-            noexistRc.add(rc);
-            for (Iterator i = noexistRc.iterator(); i.hasNext();) {
-                log(i.next() + " does not exist.", Project.MSG_ERR);
-            }
-            if (destinationFile != null) {
-                for (Iterator i = rc.iterator(); i.hasNext();) {
-                    Object o = i.next();
-                    if (o instanceof FileResource) {
-                        File f = ((FileResource) o).getFile();
-                        if (FILE_UTILS.fileNameEquals(f, destinationFile)) {
-                            throw new BuildException("Input file \""
-                                + f + "\" is the same as the output file.");
-                        }
-                    }
-                }
-            }
-            Restrict existRc = new Restrict();
-            existRc.add(EXISTS);
-            existRc.add(rc);
-            boolean outofdate = destinationFile == null || forceOverwrite;
-            if (!outofdate) {
-                for (Iterator i = existRc.iterator(); !outofdate && 
i.hasNext();) {
-                    Resource r = (Resource) i.next();
-                    outofdate =
-                        (r.getLastModified() == 0L
-                         || r.getLastModified() > 
destinationFile.lastModified());
-                }
-            }
-            if (!outofdate) {
-                log(destinationFile + " is up-to-date.", Project.MSG_VERBOSE);
-                return null; // no need to do anything
-            }
-            return existRc;
-        } else {
-            StringResource s = new StringResource();
-            s.setProject(getProject());
-            s.setValue(textBuffer.toString());
-            return s;
-        }
-    }
-
-    /**
      * Execute the concat task.
      */
     public void execute() {
-        ResourceCollection c = validate();
-        if (c == null) {
+        validate();
+        if (binary && destinationFile == null) {
+            throw new BuildException(
+                "destfile attribute is required for binary concatenation");
+        }
+        ResourceCollection c = getResources();
+        if (isUpToDate(c)) {
+            log(destinationFile + " is up-to-date.", Project.MSG_VERBOSE);
             return;
         }
-        // Do nothing if no resources (including nested text)
-        if (c.size() < 1 && header == null && footer == null) {
-            log("No existing resources and no nested text, doing nothing",
-                Project.MSG_INFO);
+        if (c.size() == 0) {
             return;
         }
-        if (binary) {
-            binaryCat(c);
+        OutputStream out;
+        if (destinationFile == null) {
+            // Log using WARN so it displays in 'quiet' mode.
+            out = new LogOutputStream(this, Project.MSG_WARN);
         } else {
-            cat(c);
-        }
-    }
-
-    /** perform the binary concatenation */
-    private void binaryCat(ResourceCollection c) {
-        log("Binary concatenation of " + c.size()
-            + " resources to " + destinationFile);
-        FileOutputStream out = null;
-        InputStream in = null;
-        try {
-            try {
-                out = new FileOutputStream(destinationFile.getPath(), append); 
// JDK 1.2 compatibility
-            } catch (Exception t) {
-                throw new BuildException("Unable to open "
-                    + destinationFile + " for writing", t);
-            }
-            in = new ConcatResourceInputStream(c);
-            ((ConcatResourceInputStream) in).setManagingComponent(this);
-            Thread t = new Thread(new StreamPumper(in, out));
-            t.start();
-            try {
-                t.join();
-            } catch (InterruptedException e) {
-                try {
-                    t.join();
-                } catch (InterruptedException ee) {
-                    // Empty
-                }
-            }
-        } finally {
-            FileUtils.close(in);
-            if (out != null) {
-                try {
-                    out.close();
-                } catch (Exception ex) {
-                    throw new BuildException(
-                        "Unable to close " + destinationFile, ex);
-                }
-            }
-        }
-    }
-
-    /** perform the concatenation */
-    private void cat(ResourceCollection c) {
-        OutputStream os = null;
-        char[] buffer = new char[BUFFER_SIZE];
-
-        try {
-            PrintWriter writer = null;
-
-            if (outputWriter != null) {
-                writer = new PrintWriter(outputWriter);
-            } else {
-                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 = destinationFile.getParentFile();
-                    if (!parent.exists()) {
-                        parent.mkdirs();
-                    }
-                    os = new 
FileOutputStream(destinationFile.getAbsolutePath(),
-                                              append);
-                }
-                if (outputEncoding == null) {
-                    writer = new PrintWriter(
-                        new BufferedWriter(
-                            new OutputStreamWriter(os)));
-                } else {
-                    writer = new PrintWriter(
-                        new BufferedWriter(
-                            new OutputStreamWriter(os, outputEncoding)));
-                }
-            }
-            if (header != null) {
-                if (header.getFiltering()) {
-                    concatenate(
-                        buffer, writer, new StringReader(header.getValue()));
-                } else {
-                    writer.print(header.getValue());
-                }
-            }
-            if (c.size() > 0) {
-                concatenate(buffer, writer, new MultiReader(c));
-            }
-            if (footer != null) {
-                if (footer.getFiltering()) {
-                    concatenate(
-                        buffer, writer, new StringReader(footer.getValue()));
-                } else {
-                    writer.print(footer.getValue());
-                }
-            }
-            writer.flush();
-            if (os != null) {
-                os.flush();
-            }
-        } catch (IOException ioex) {
-            throw new BuildException("Error while concatenating: "
-                                     + ioex.getMessage(), ioex);
-        } finally {
-            FileUtils.close(os);
-        }
-    }
-
-    /** 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(BUFFER_SIZE);
-            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();
-    }
-
-    /**
-     * 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.substring(0).trim().length() == 0) {
-                textBuffer = null;
-            }
-        }
-    }
-
-    /**
-     * sub element points to a file or contains text
-     */
-    public static class TextElement extends ProjectComponent {
-        private String   value = "";
-        private boolean  trimLeading = false;
-        private boolean  trim = false;
-        private boolean  filtering = true;
-        private String   encoding = null;
-
-        /**
-         * 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;
-        }
-
-        /**
-         * The encoding of the text element
-         *
-         * @param encoding the name of the charset used to encode
-         */
-        public void setEncoding(String encoding) {
-            this.encoding = encoding;
-        }
-
-        /**
-         * 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) throws BuildException {
-            // non-existing files are not allowed
-            if (!file.exists()) {
-                throw new BuildException("File " + file + " does not exist.");
-            }
-
-            BufferedReader reader = null;
             try {
-                if (this.encoding == null) {
-                    reader = new BufferedReader(new FileReader(file));
-                } else {
-                    reader = new BufferedReader(
-                        new InputStreamReader(new FileInputStream(file),
-                                              this.encoding));
-                }
-                value = FileUtils.readFully(reader);
-            } catch (IOException ex) {
-                throw new BuildException(ex);
-            } finally {
-                FileUtils.close(reader);
+                // ensure that the parent dir of dest file exists
+                File parent = destinationFile.getParentFile();
+                if (!parent.exists()) {
+                    parent.mkdirs();
+                }
+                // use getPath() for pre-JDK 1.4 compatibility:
+                out = new FileOutputStream(destinationFile.getPath(), append);
+            } catch (Throwable t) {
+                throw new BuildException("Unable to open "
+                    + destinationFile + " for writing", t);
             }
         }
-
-        /**
-         * set the text using inline
-         * @param value the text to place inline
-         */
-        public void addText(String value) {
-            this.value += getProject().replaceProperties(value);
+        InputStream catStream;
+        try {
+            catStream = new ConcatResource(c).getInputStream();
+        } catch (IOException e) {
+            throw new BuildException("error getting concatenated resource 
content", e);
         }
+        pump(catStream, out);
+    }
 
-        /**
-         * s:^\s*:: on each line of input
-         * @param strip if true do the trim
-         */
-        public void setTrimLeading(boolean strip) {
-            this.trimLeading = strip;
-        }
+    /**
+     * Implement ResourceCollection.
+     * @return Iterator<Resource>.
+     */
+    public Iterator iterator() {
+        validate();
+        return Collections.singletonList(new 
ConcatResource(getResources())).iterator();
+    }
 
-        /**
-         * whether to call text.trim()
-         * @param trim if true trim the text
-         */
-        public void setTrim(boolean trim) {
-            this.trim = trim;
-        }
+    /**
+     * Implement ResourceCollection.
+     * @return 1.
+     */
+    public int size() {
+        return 1;
+    }
 
-        /**
-         * @return the text, after possible trimming
-         */
-        public String getValue() {
-            if (value == null) {
-                value = "";
+    /**
+     * Implement ResourceCollection.
+     * @return false.
+     */
+    public boolean isFilesystemOnly() {
+        return false;
+    }
+
+    /**
+     * Validate configuration options.
+     */
+    private void validate() {
+
+        // treat empty nested text as no text
+        sanitizeText();
+
+        // if binary check if incompatible attributes are used
+        if (binary) {
+            if (textBuffer != null) {
+                throw new BuildException(
+                    "Nested text is incompatible with binary concatenation");
             }
-            if (value.trim().length() == 0) {
-                value = "";
+            if (encoding != null || outputEncoding != null) {
+                throw new BuildException(
+                    "Setting input or output encoding is incompatible with 
binary"
+                    + " concatenation");
             }
-            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 (filterChains != null) {
+                throw new BuildException(
+                    "Setting filters is incompatible with binary 
concatenation");
             }
-            if (trim) {
-                value = value.trim();
+            if (fixLastLine) {
+                throw new BuildException(
+                    "Setting fixlastline is incompatible with binary 
concatenation");
             }
-            return value;
+            if (header != null || footer != null) {
+                throw new BuildException(
+                    "Nested header or footer is incompatible with binary 
concatenation");
+            }
+        }
+        if (destinationFile != null && outputWriter != null) {
+            throw new BuildException(
+                "Cannot specify both a destination file and an output writer");
+        }
+        // Sanity check our inputs.
+        if (rc == null && textBuffer == null) {
+            // Nothing to concatenate!
+            throw new BuildException(
+                "At least one resource must be provided, or some text.");
+        }
+        if (rc != null && textBuffer != null) {
+            // If using resources, disallow inline text. This is similar to
+            // using GNU 'cat' with file arguments--stdin is simply ignored.
+            throw new BuildException(
+                "Cannot include inline text when using resources.");
         }
     }
 
     /**
-     * This class reads from each of the source files in turn.
-     * The concatentated result can then be filtered as
-     * a single stream.
+     * Get the resources to concatenate.
      */
-    private class MultiReader extends Reader {
-        private Reader reader = null;
-        private int    lastPos = 0;
-        private char[] lastChars = new char[eolString.length()];
-        private boolean needAddSeparator = false;
-        private Iterator i;
-
-        private MultiReader(ResourceCollection c) {
-            i = c.iterator();
+    private ResourceCollection getResources() {
+        if (rc == null) {
+            return new StringResource(getProject(), textBuffer.toString());
         }
-
-        private Reader getReader() throws IOException {
-            if (reader == null && i.hasNext()) {
-                Resource r = (Resource) i.next();
-                log("Concating " + r.toLongString(), Project.MSG_VERBOSE);
-                InputStream is = r.getInputStream();
-                reader = new BufferedReader(encoding == null
-                    ? new InputStreamReader(is)
-                    : new InputStreamReader(is, encoding));
-                Arrays.fill(lastChars, (char) 0);
-            }
-            return reader;
-        }
-
-        private void nextReader() throws IOException {
-            close();
-            reader = null;
+        Restrict noexistRc = new Restrict();
+        noexistRc.add(NOT_EXISTS);
+        noexistRc.add(rc);
+        for (Iterator i = noexistRc.iterator(); i.hasNext();) {
+            log(i.next() + " does not exist.", Project.MSG_ERR);
         }
-
-        /**
-         * 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 - possibly thrown by the read for a reader
-         *            object.
-         */
-        public int read() throws IOException {
-            if (needAddSeparator) {
-                int ret = eolString.charAt(lastPos++);
-                if (lastPos >= eolString.length()) {
-                    lastPos = 0;
-                    needAddSeparator = false;
-                }
-                return ret;
-            }
-            while (getReader() != null) {
-                int ch = getReader().read();
-                if (ch == -1) {
-                    nextReader();
-                    if (fixLastLine && isMissingEndOfLine()) {
-                        needAddSeparator = true;
-                        lastPos = 0;
+        if (destinationFile != null) {
+            for (Iterator i = rc.iterator(); i.hasNext();) {
+                Object o = i.next();
+                if (o instanceof FileResource) {
+                    File f = ((FileResource) o).getFile();
+                    if (FILE_UTILS.fileNameEquals(f, destinationFile)) {
+                        throw new BuildException("Input file \""
+                            + f + "\" is the same as the output file.");
                     }
-                } else {
-                    addLastChar((char) ch);
-                    return ch;
                 }
             }
-            return -1;
         }
+        Restrict result = new Restrict();
+        result.add(EXISTS);
+        result.add(rc);
+        return result;
+    }
 
-        /**
-         * 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 - possibly thrown by the reads to the
-         *            reader objects.
-         */
-        public int read(char[] cbuf, int off, int len)
-            throws IOException {
-
-            int amountRead = 0;
-            while (getReader() != null || needAddSeparator) {
-                if (needAddSeparator) {
-                    cbuf[off] = eolString.charAt(lastPos++);
-                    if (lastPos >= eolString.length()) {
-                        lastPos = 0;
-                        needAddSeparator = false;
-                    }
-                    len--;
-                    off++;
-                    amountRead++;
-                    if (len == 0) {
-                        return amountRead;
-                    }
-                    continue;
-                }
-                int nRead = getReader().read(cbuf, off, len);
-                if (nRead == -1 || nRead == 0) {
-                    nextReader();
-                    if (fixLastLine && isMissingEndOfLine()) {
-                        needAddSeparator = true;
-                        lastPos = 0;
-                    }
-                } else {
-                    if (fixLastLine) {
-                        for (int i = nRead;
-                                 i > (nRead - lastChars.length);
-                                 --i) {
-                            if (i <= 0) {
-                                break;
-                            }
-                            addLastChar(cbuf[off + i - 1]);
-                        }
-                    }
-                    len -= nRead;
-                    off += nRead;
-                    amountRead += nRead;
-                    if (len == 0) {
-                        return amountRead;
-                    }
-                }
-            }
-            if (amountRead == 0) {
-                return -1;
-            } else {
-                return amountRead;
+    private boolean isUpToDate(ResourceCollection c) {
+        if (destinationFile == null || forceOverwrite) {
+            return false;
+        }
+        for (Iterator i = c.iterator(); i.hasNext();) {
+            Resource r = (Resource) i.next();
+            if (r.getLastModified() == 0L
+                 || r.getLastModified() > destinationFile.lastModified()) {
+                return false;
             }
         }
+        return true;
+    }
 
-        /**
-         * Close the current reader
-         */
-        public void close() throws IOException {
-            if (reader != null) {
-                reader.close();
+    /**
+     * 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.substring(0).trim().length() == 0) {
+                textBuffer = null;
             }
         }
+    }
 
-        /**
-         * if checking for end of line at end of file
-         * add a character to the lastchars buffer
-         */
-        private void addLastChar(char ch) {
-            for (int i = lastChars.length - 2; i >= 0; --i) {
-                lastChars[i] = lastChars[i + 1];
+    /**
+     * Transfer the contents of <code>in</code> to <code>out</code>.
+     * @param in InputStream
+     * @param out OutputStream
+     */
+    private void pump(InputStream in, OutputStream out) {
+        Thread t = new Thread(new StreamPumper(in, out));
+        t.start();
+        try {
+            t.join();
+        } catch (InterruptedException e) {
+            try {
+                t.join();
+            } catch (InterruptedException ee) {
+                // Empty
             }
-            lastChars[lastChars.length - 1] = ch;
+        } finally {
+            FileUtils.close(in);
+            FileUtils.close(out);
         }
+    }
 
-        /**
-         * return true if the lastchars buffer does
-         * not contain the lineseparator
-         */
-        private boolean isMissingEndOfLine() {
-            for (int i = 0; i < lastChars.length; ++i) {
-                if (lastChars[i] != eolString.charAt(i)) {
-                    return true;
-                }
-            }
-            return false;
+    private Reader getFilteredReader(Reader r) {
+        if (filterChains == null) {
+            return r;
         }
+        ChainReaderHelper helper = new ChainReaderHelper();
+        helper.setBufferSize(BUFFER_SIZE);
+        helper.setPrimaryReader(r);
+        helper.setFilterChains(filterChains);
+        helper.setProject(getProject());
+        //used to be a BufferedReader here, but we should be buffering lower:
+        return helper.getAssembledReader();
     }
 
 }
-

Modified: ant/core/trunk/src/tests/antunit/taskdefs/concat-test.xml
URL: 
http://svn.apache.org/viewvc/ant/core/trunk/src/tests/antunit/taskdefs/concat-test.xml?view=diff&rev=536579&r1=536578&r2=536579
==============================================================================
--- ant/core/trunk/src/tests/antunit/taskdefs/concat-test.xml (original)
+++ ant/core/trunk/src/tests/antunit/taskdefs/concat-test.xml Wed May  9 
09:41:35 2007
@@ -1,6 +1,7 @@
 <project xmlns:au="antlib:org.apache.ant.antunit">
   <target name="tearDown">
     <delete file="binaryAppendDest" />
+    <delete file="encodeStringDest" />
   </target>
 
   <target name="testBinaryAppend">
@@ -21,4 +22,17 @@
       <length file="binaryAppendDest" length="2" />
     </au:assertTrue>
   </target>
+
+  <target name="testStringEncoding">
+    <property name="br" value="${line.separator}" />
+    <concat destfile="encodeStringDest"
+            outputEncoding="utf-16">foo${br}bar${br}baz${br}</concat>
+    <au:assertTrue>
+      <resourcesmatch astext="true">
+        <file file="utf-16.expected" />
+        <file file="encodeStringDest" />
+      </resourcesmatch>
+    </au:assertTrue>
+  </target>
+
 </project>

Added: ant/core/trunk/src/tests/antunit/taskdefs/utf-16.expected
URL: 
http://svn.apache.org/viewvc/ant/core/trunk/src/tests/antunit/taskdefs/utf-16.expected?view=auto&rev=536579
==============================================================================
Binary file - no diff available.

Propchange: ant/core/trunk/src/tests/antunit/taskdefs/utf-16.expected
------------------------------------------------------------------------------
    svn:executable = *

Propchange: ant/core/trunk/src/tests/antunit/taskdefs/utf-16.expected
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: ant/core/trunk/src/tests/antunit/types/resources/concat-resource-test.xml
URL: 
http://svn.apache.org/viewvc/ant/core/trunk/src/tests/antunit/types/resources/concat-resource-test.xml?view=auto&rev=536579
==============================================================================
--- ant/core/trunk/src/tests/antunit/types/resources/concat-resource-test.xml 
(added)
+++ ant/core/trunk/src/tests/antunit/types/resources/concat-resource-test.xml 
Wed May  9 09:41:35 2007
@@ -0,0 +1,90 @@
+<?xml version="1.0"?>
+
+<project name="test-concat" basedir="." default="antunit"
+         xmlns:au="antlib:org.apache.ant.antunit">
+
+  <import file="../../antunit-base.xml" />
+
+  <property name="br" value="${line.separator}" />
+  <property name="world" value="World" />
+
+  <target name="testCountEquals1">
+    <au:assertTrue>
+      <resourcecount count="1">
+        <concat>Hello, ${world}!</concat>
+      </resourcecount>
+    </au:assertTrue>
+  </target>
+
+  <target name="testReplacement">
+    <au:assertTrue>
+      <resourcesmatch>
+        <string>Hello, ${world}!</string>
+        <concat>Hello, ${world}!</concat>
+      </resourcesmatch>
+    </au:assertTrue>
+  </target>
+
+  <target name="testResources">
+    <au:assertTrue>
+      <resourcesmatch>
+        <string>foobarbaz</string>
+        <concat>
+          <string value="foo" />
+          <string value="bar" />
+          <string value="baz" />
+        </concat>
+      </resourcesmatch>
+    </au:assertTrue>
+  </target>
+
+  <target name="testFixLastLineResources">
+    <au:assertTrue>
+      <resourcesmatch>
+        
<string>foo${line.separator}bar${line.separator}baz${line.separator}</string>
+        <concat fixlastline="true">
+          <string value="foo" />
+          <string value="bar" />
+          <string value="baz" />
+        </concat>
+      </resourcesmatch>
+    </au:assertTrue>
+  </target>
+
+  <target name="testEncoding">
+    <au:assertTrue>
+      <resourcesmatch astext="true">
+        <file file="utf-16.in" />
+        <concat outputEncoding="utf-16">foo${br}bar${br}baz${br}</concat>
+        <concat outputEncoding="utf-16" fixlastline="true">
+          <string value="foo" />
+          <string value="bar" />
+          <string value="baz" />
+        </concat>
+      </resourcesmatch>
+    </au:assertTrue>
+  </target>
+
+  <target name="testFiltering">
+    <au:assertTrue>
+      <resourcesmatch>
+        <concat>foo${br}bar${br}baz${br}</concat>
+        <concat>
+foo
+#comment 1
+bar
+#comment 2
+baz
+#comment 3
+          <filterchain>
+            <striplinecomments>
+              <comment value="#" />
+            </striplinecomments>
+            <ignoreblank />
+          </filterchain>
+        </concat>
+      </resourcesmatch>
+    </au:assertTrue>
+  </target>
+
+</project>

Propchange: 
ant/core/trunk/src/tests/antunit/types/resources/concat-resource-test.xml
------------------------------------------------------------------------------
    svn:eol-style = native



---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to