#64855 Add filterbeforeconcat option to Concat
diff --git a/manual/Tasks/concat.html b/manual/Tasks/concat.html
index 513797c..71aba61 100644
--- a/manual/Tasks/concat.html
+++ b/manual/Tasks/concat.html
@@ -125,6 +125,15 @@
         <td>No; default is <q>false</q></td>
       </tr>
       <tr>
+        <td>filterbeforeconcat</td>
+        <td>If this attribute is set to <q>true</q>, the task applies the filterchain to each
+          input after applying <code>fixlastline</code>. If this attribute is <q>false</q>, concat
+          will apply the filterchain only once to the already concatenated inputs. Filtering of
+          <code>header</code> and <code>footer</code> is not affected by this setting.
+          <em>Since Ant 1.10.10</em></td>
+        <td>No; default is <q>false</q></td>
+      </tr>
+      <tr>
         <td>ignoreempty</td>
         <td><em>Since Ant 1.8.0</em> Specifies whether or not the file specified
           by <var>destfile</var> should be created if the source resource list is empty.
diff --git a/src/main/org/apache/tools/ant/taskdefs/Concat.java b/src/main/org/apache/tools/ant/taskdefs/Concat.java
index 333bad9..8a56046 100644
--- a/src/main/org/apache/tools/ant/taskdefs/Concat.java
+++ b/src/main/org/apache/tools/ant/taskdefs/Concat.java
@@ -212,27 +212,167 @@
     }
 
     /**
+     * This class reads a Reader and ensures it ends with the desired linebreak
+     * @since Ant 1.10.10
+     */
+    private final class LastLineFixingReader extends Reader {
+        private final Reader reader;
+        private int lastPos = 0;
+        private final char[] lastChars = new char[eolString.length()];
+        private boolean needAddSeparator = false;
+
+        private LastLineFixingReader(Reader reader) {
+            this.reader = reader;
+        }
+
+        /**
+         * 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.
+         */
+        @Override
+        public int read() throws IOException {
+            if (needAddSeparator) {
+                if (lastPos >= eolString.length()) {
+                    return -1;
+                } else {
+                    return eolString.charAt(lastPos++);
+                }
+            }
+            int ch = reader.read();
+            if (ch == -1) {
+                if (isMissingEndOfLine()) {
+                    needAddSeparator = true;
+                    lastPos = 1;
+                    return eolString.charAt(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.
+         */
+        @Override
+        public int read(char[] cbuf, int off, int len)
+            throws IOException {
+
+            int amountRead = 0;
+            while (true) {
+                if (needAddSeparator) {
+                    if (lastPos >= eolString.length()) {
+                        break;
+                    }
+                    cbuf[off] = eolString.charAt(lastPos++);
+                    len--;
+                    off++;
+                    amountRead++;
+                    if (len == 0) {
+                        return amountRead;
+                    }
+                    continue;
+                }
+                int nRead = reader.read(cbuf, off, len);
+                if (nRead == -1 || nRead == 0) {
+                    if (isMissingEndOfLine()) {
+                        needAddSeparator = true;
+                        lastPos = 0;
+                    } else {
+                        break;
+                    }
+                } else {
+                    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;
+            }
+            return amountRead;
+        }
+
+        /**
+         * Close the current reader
+         */
+        @Override
+        public void close() throws IOException {
+            reader.close();
+        }
+
+        /**
+         * if checking for end of line at end of file
+         * add a character to the lastchars buffer
+         */
+        private void addLastChar(char ch) {
+            System.arraycopy(lastChars, 1, lastChars, 0, lastChars.length - 2 + 1);
+            lastChars[lastChars.length - 1] = ch;
+        }
+
+        /**
+         * return true if the lastchars buffer does
+         * not contain the line separator
+         */
+        private boolean isMissingEndOfLine() {
+            for (int i = 0; i < lastChars.length; ++i) {
+                if (lastChars[i] != eolString.charAt(i)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    /**
      * This class reads from each of the source files in turn.
      * The concatenated result can then be filtered as
      * a single stream.
      */
     private final class MultiReader<S> extends Reader {
         private Reader reader = null;
-        private int    lastPos = 0;
-        private char[] lastChars = new char[eolString.length()];
-        private boolean needAddSeparator = false;
         private Iterator<S> readerSources;
         private ReaderFactory<S> factory;
+        private final boolean filterBeforeConcat;
 
-        private MultiReader(Iterator<S> readerSources, ReaderFactory<S> factory) {
+        private MultiReader(Iterator<S> readerSources, ReaderFactory<S> factory,
+                boolean filterBeforeConcat) {
             this.readerSources = readerSources;
             this.factory = factory;
+            this.filterBeforeConcat = filterBeforeConcat;
         }
 
         private Reader getReader() throws IOException {
             if (reader == null && readerSources.hasNext()) {
                 reader = factory.getReader(readerSources.next());
-                Arrays.fill(lastChars, (char) 0);
+                if(isFixLastLine())
+                {
+                    reader = new LastLineFixingReader(reader);
+                }
+                if(filterBeforeConcat)
+                {
+                    reader = getFilteredReader(reader);
+                }
             }
             return reader;
         }
@@ -251,25 +391,11 @@
          */
         @Override
         public int read() throws IOException {
-            if (needAddSeparator) {
-                if (lastPos >= eolString.length()) {
-                    lastPos = 0;
-                    needAddSeparator = false;
-                } else {
-                    return eolString.charAt(lastPos++);
-                }
-            }
             while (getReader() != null) {
                 int ch = getReader().read();
                 if (ch == -1) {
                     nextReader();
-                    if (isFixLastLine() && isMissingEndOfLine()) {
-                        needAddSeparator = true;
-                        lastPos = 1;
-                        return eolString.charAt(0);
-                    }
                 } else {
-                    addLastChar((char) ch);
                     return ch;
                 }
             }
@@ -289,39 +415,11 @@
             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;
-                }
+            while (getReader() != null) {
                 int nRead = getReader().read(cbuf, off, len);
                 if (nRead == -1 || nRead == 0) {
                     nextReader();
-                    if (isFixLastLine() && isMissingEndOfLine()) {
-                        needAddSeparator = true;
-                        lastPos = 0;
-                    }
                 } else {
-                    if (isFixLastLine()) {
-                        for (int i = nRead;
-                                 i > (nRead - lastChars.length);
-                                 --i) {
-                            if (i <= 0) {
-                                break;
-                            }
-                            addLastChar(cbuf[off + i - 1]);
-                        }
-                    }
                     len -= nRead;
                     off += nRead;
                     amountRead += nRead;
@@ -346,28 +444,6 @@
             }
         }
 
-        /**
-         * if checking for end of line at end of file
-         * add a character to the lastchars buffer
-         */
-        private void addLastChar(char ch) {
-            System.arraycopy(lastChars, 1, lastChars, 0, lastChars.length - 2 + 1);
-            lastChars[lastChars.length - 1] = ch;
-        }
-
-        /**
-         * return true if the lastchars buffer does
-         * not contain the line separator
-         */
-        private boolean isMissingEndOfLine() {
-            for (int i = 0; i < lastChars.length; ++i) {
-                if (lastChars[i] != eolString.charAt(i)) {
-                    return true;
-                }
-            }
-            return false;
-        }
-
         private boolean isFixLastLine() {
             return fixLastLine && textBuffer == null;
         }
@@ -386,8 +462,14 @@
                 result.setManagingComponent(this);
                 return result;
             }
-            Reader resourceReader = getFilteredReader(
-                    new MultiReader<>(c.iterator(), resourceReaderFactory));
+            Reader resourceReader;
+            if(filterBeforeConcat) {
+                resourceReader = new MultiReader<>(c.iterator(),
+                        resourceReaderFactory, true);
+            } else {
+                resourceReader = getFilteredReader(
+                        new MultiReader<>(c.iterator(), resourceReaderFactory, false));
+            }
             Reader rdr;
             if (header == null && footer == null) {
                 rdr = resourceReader;
@@ -416,7 +498,7 @@
                     }
                 }
                 rdr = new MultiReader<>(Arrays.asList(readers).iterator(),
-                        identityReaderFactory);
+                        identityReaderFactory, false);
             }
             return outputEncoding == null ? new ReaderInputStream(rdr)
                     : new ReaderInputStream(rdr, outputEncoding);
@@ -454,6 +536,9 @@
     /** Stores the binary attribute */
     private boolean binary;
 
+    /** Stores the filterBeforeConcat attribute */
+    private boolean filterBeforeConcat;
+
     // Child elements.
 
     /**
@@ -780,6 +865,17 @@
     }
 
     /**
+     * Set the filterBeforeConcat attribute. If true, concat will filter each
+     * input through the filterchain before concatenating the results. This
+     * allows to e.g. use the FileTokenizer to tokenize each input.
+     * @param filterBeforeConcat if true, filter each input before concatenation
+     * @since Ant 1.10.10
+     */
+    public void setFilterBeforeConcat(final boolean filterBeforeConcat) {
+        this.filterBeforeConcat = filterBeforeConcat;
+    }
+
+    /**
      * Execute the concat task.
      */
     @Override
diff --git a/src/tests/antunit/taskdefs/concat-test.xml b/src/tests/antunit/taskdefs/concat-test.xml
index c596d90..82182ff 100644
--- a/src/tests/antunit/taskdefs/concat-test.xml
+++ b/src/tests/antunit/taskdefs/concat-test.xml
@@ -118,6 +118,50 @@
     </au:assertTrue>
   </target>
 
+  <target name="testFilterIsAppliedToConcatenation"
+          depends="-fixlastline-setup"
+          description="https://bz.apache.org/bugzilla/show_bug.cgi?id=64855">
+    <au:assertTrue>
+      <resourcesmatch>
+        <string>1${line.separator}2${line.separator}1${line.separator}2${line.separator}</string>
+        <concat fixlastline="true">
+          <filelist dir="${input}">
+            <file name="1"/>
+            <file name="2"/>
+          </filelist>
+          <filterchain>
+	    <tokenfilter>
+          <filetokenizer/>
+          <replaceregex pattern="(.*)" flags="s" replace="\1\1"/>
+	    </tokenfilter>
+	  </filterchain>
+        </concat>
+      </resourcesmatch>
+    </au:assertTrue>
+  </target>
+
+  <target name="testFilterBeforeConcatActuallyAppliesFilterToEachInput"
+          depends="-fixlastline-setup"
+          description="https://bz.apache.org/bugzilla/show_bug.cgi?id=64855">
+    <au:assertTrue>
+      <resourcesmatch>
+        <string>1${line.separator}1${line.separator}2${line.separator}2${line.separator}</string>
+        <concat fixlastline="true" filterbeforeconcat="true">
+          <filelist dir="${input}">
+            <file name="1"/>
+            <file name="2"/>
+          </filelist>
+          <filterchain>
+	    <tokenfilter>
+          <filetokenizer/>
+          <replaceregex pattern="(.*)" flags="s" replace="\1\1"/>
+	    </tokenfilter>
+	  </filterchain>
+        </concat>
+      </resourcesmatch>
+    </au:assertTrue>
+  </target>
+
   <target name="testIgnoreEmptyFalseFileIsCreated">
     <mkdir dir="${input}" />
     <mkdir dir="${output}" />