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 "ignorable whitespace" 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 "ignorable whitespace" 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]