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]