From 88dcebf815f27d9b16735745605342963870010e Mon Sep 17 00:00:00 2001
From: Stefan Bodewig
Concatenates a file, or a series of files, to a single file or
the console. The destination file will be created if it does
- not exist, though the the append attribute may be
- used to alter this behavior.
+ not exist.
+ Paths and/or
FileSets and/or FileLists are used to
select which files are to be concatenated. There is no
- singular 'file' attribute to specify a single file to cat -- a
- fileset or filelist must also be used in these cases.
+ singular 'file' attribute to specify a single file to cat.
since Ant 1.6.
+ This is a Path. This is
+ used to select file files to be concatenated. Note that
+ if a file can only appear once in a path. If this is
+ an issue consider using multiple paths.
+ Parameters
@@ -51,7 +50,17 @@
append
Specifies whether or not the file specified by 'destfile'
- should be overwritten. Defaults to "no".
+ should be appended. Defaults to "no".
+
+ No
+
+
+
@@ -71,6 +80,15 @@
force
+
+ Specifies whether or not the file specified by 'destfile'
+ should be written to even if it is newer than all source files.
+ since Ant 1.6.
+ Defaults to "yes".
No
Parameters specified as nested elements
+ path
+ fileset
@@ -81,7 +99,6 @@
not guaranteed. If this is an issue, use multiple
filesets or consider using filelists.
@@ -90,6 +107,47 @@ files attribute will be the same order in which the files are concatenated.
+since Ant 1.6.
+The concat task supports nested + FilterChains.
+ +since Ant 1.6.
+Used to prepend or postpend text into the concatenated stream.
+The text may be in-line or be in a file.
+Attribute | +Description | +Required | +
filtering | ++ Whether to filter the text provided by this sub element, + default is "yes". + | No | +
file | +A file to place at the head or tail of the + concatenated text. + | No | +
trim | +Whether to trim the value, default is "no" | +No | +
trimleading | ++ Whether to trim leading white space on each line, default is "no" + | +No | +
Concatenate a series of files, overwriting if the - destination file exists:
+Concatenate a series of files, update the destination + file only if is older that all the source files:
- <concat destfile="${docbook.dir}/all-sections.xml"> + <concat destfile="${docbook.dir}/all-sections.xml" + force="no"> <filelist dir="${docbook.dir}/sections" files="introduction.xml,overview.xml"/> <fileset dir="${docbook.dir}" @@ -128,10 +187,40 @@ </concat>+
Concatenate a series of files, expanding ant properties
++ <concat destfile="${build.dir}/subs"> + <path> + <fileset dir="${src.dir}" includes="*.xml"/> + <pathelement location="build.xml"/> + </path> + <filterchain> + <expandproperties/> + </filterchain> + </concat> ++ +
Filter the lines containing project from build.xml and output + them to report.output, prepending with a header
++ <concat destfile="${build.dir}/report.output"> + <header filtering="no" trimleading="yes"> + Lines that contain project + ========================== + </header> + <path path="build.xml"/> + <filterchain> + <linecontains> + <contains value="project"/> + </linecontains> + </filterchain> + </concat> ++
- Copyright © 2002 Apache Software Foundation. All + Copyright © 2002-2003 Apache Software Foundation. All Rights Reserved.
diff --git a/src/etc/testcases/taskdefs/concat.xml b/src/etc/testcases/taskdefs/concat.xml index 63876e2c6..8de1728c0 100644 --- a/src/etc/testcases/taskdefs/concat.xml +++ b/src/etc/testcases/taskdefs/concat.xml @@ -7,6 +7,12 @@base
.
- */
- private void catFiles(File base, String[] files) {
-
- // First, create a list of absolute paths for the input files.
- Vector inputFileNames = new Vector();
- for (int i = 0; i < files.length; i++) {
-
- File current = new File(base, files[i]);
-
- // Make sure the file exists. This will rarely fail when
- // using file sets, but it could be rather common when
- // using file lists.
- if (!current.exists()) {
- // File does not exist, log an error and continue.
- log("File " + current + " does not exist.",
- Project.MSG_ERR);
+ private void checkAddFiles(File base, String[] filenames) {
+ for (int i = 0; i < filenames.length; ++i) {
+ File file = new File(base, filenames[i]);
+ if (!file.exists()) {
+ log("File " + file + " does not exist.", Project.MSG_ERR);
continue;
}
-
- inputFileNames.addElement(current.getAbsolutePath());
+ if (destinationFile != null
+ && fileUtils.fileNameEquals(destinationFile, file)) {
+ throw new BuildException("Input file \""
+ + file + "\" "
+ + "is the same as the output file.");
+ }
+ sourceFiles.addElement(file);
}
+ }
+
+ /** perform the concatenation */
+ private void cat() {
+ OutputStream os = null;
+ Reader reader = null;
+ char[] buffer = new char[8192];
- final int len = inputFileNames.size();
- if (len == 0) {
- log("Warning: Could not find any of the files specified " +
- "in concat task.", Project.MSG_WARN);
- return;
- }
+ try {
- String[] input = new String[len];
- inputFileNames.copyInto(input);
+ if (destinationFile == null) {
+ // Log using WARN so it displays in 'quiet' mode.
+ os = new LogOutputStream(this, Project.MSG_WARN);
+ } else {
+ // ensure that the parent dir of dest file exists
+ File parent = fileUtils.getParentFile(destinationFile);
+ if (!parent.exists()) {
+ parent.mkdirs();
+ }
- // Next, perform the concatenation.
- if (encoding == null) {
- OutputStream os = null;
- InputStream is = null;
+ os = new FileOutputStream(destinationFile.getAbsolutePath(),
+ append);
+ }
- try {
+ PrintWriter writer = null;
+ if (encoding == null) {
+ writer = new PrintWriter(
+ new BufferedWriter(
+ new OutputStreamWriter(os)));
+ } else {
+ writer = new PrintWriter(
+ new BufferedWriter(
+ new OutputStreamWriter(os, encoding)));
+ }
- if (destinationFile == null) {
- // Log using WARN so it displays in 'quiet' mode.
- os = new LogOutputStream(this, Project.MSG_WARN);
+
+ if (header != null) {
+ if (header.getFiltering()) {
+ concatenate(
+ buffer, writer, new StringReader(header.getValue()));
} else {
- os =
- new FileOutputStream(destinationFile.getAbsolutePath(),
- append);
-
- // This flag should only be recognized for the first
- // file. In the context of a single 'cat', we always
- // want to append.
- append = true;
+ writer.print(header.getValue());
}
+ }
+
+ if (textBuffer != null) {
+ reader = new StringReader(
+ getProject().replaceProperties(textBuffer.toString()));
+ } else {
+ reader = new MultiReader();
+ }
- for (int i = 0; i < len; i++) {
-
- // Make sure input != output.
- if (destinationFile != null &&
- destinationFile.getAbsolutePath().equals(input[i])) {
- throw new BuildException("Input file \""
- + destinationFile.getName()
- + "\" is the same as the output file.");
- }
+ concatenate(buffer, writer, reader);
- is = new FileInputStream(input[i]);
- byte[] buffer = new byte[8192];
- while (true) {
- int bytesRead = is.read(buffer);
- if (bytesRead == -1) { // EOF
- break;
- }
-
- // Write the read data.
- os.write(buffer, 0, bytesRead);
- }
- os.flush();
- is.close();
- is = null;
- }
- } catch (IOException ioex) {
- throw new BuildException("Error while concatenating: "
- + ioex.getMessage(), ioex);
- } finally {
- if (is != null) {
- try {
- is.close();
- } catch (Exception ignore) {}
- }
- if (os != null) {
- try {
- os.close();
- } catch (Exception ignore) {}
+ if (footer != null) {
+ if (footer.getFiltering()) {
+ concatenate(
+ buffer, writer, new StringReader(footer.getValue()));
+ } else {
+ writer.print(footer.getValue());
}
}
- } else { // user specified encoding
+ writer.flush();
+ os.flush();
- Writer out = null;
- BufferedReader in = null;
+ } catch (IOException ioex) {
+ throw new BuildException("Error while concatenating: "
+ + ioex.getMessage(), ioex);
+ } finally {
+ if (reader != null) {
+ try {reader.close();} catch (IOException ignore) {}
+ }
+ if (os != null) {
+ try {os.close();} catch (IOException ignore) {}
+ }
+ }
+ }
- try {
- if (destinationFile == null) {
- // Log using WARN so it displays in 'quiet' mode.
- out = new OutputStreamWriter(
- new LogOutputStream(this, Project.MSG_WARN));
- } else {
- out = new OutputStreamWriter(
- new FileOutputStream(destinationFile
- .getAbsolutePath(),
- append),
- encoding);
-
- // This flag should only be recognized for the first
- // file. In the context of a single 'cat', we always
- // want to append.
- append = true;
- }
- for (int i = 0; i < len; i++) {
- in = new BufferedReader(
- new InputStreamReader(new FileInputStream(input[i]),
- encoding));
-
- String line;
- char[] buffer = new char[4096];
- while (true) {
- int charsRead = in.read(buffer);
- if (charsRead == -1) { // EOF
- break;
- }
-
- // Write the read data.
- out.write(buffer, 0, charsRead);
- }
- out.flush();
- in.close();
- in = null;
- }
- } catch (IOException ioe) {
- throw new BuildException("Error while concatenating: "
- + ioe.getMessage(), ioe);
- } finally {
- // Close resources.
- if (in != null) {
- try {
- in.close();
- } catch (Exception ignore) {}
- }
- if (out != null) {
- try {
- out.close();
- } catch (Exception ignore) {}
- }
+ /** Concatenate a single reader to the writer using buffer */
+ private void concatenate(char[] buffer, Writer writer, Reader in)
+ throws IOException {
+ if (filterChains != null) {
+ ChainReaderHelper helper = new ChainReaderHelper();
+ helper.setBufferSize(8192);
+ helper.setPrimaryReader(in);
+ helper.setFilterChains(filterChains);
+ helper.setProject(getProject());
+ in = new BufferedReader(helper.getAssembledReader());
+ }
+
+ while (true) {
+ int nRead = in.read(buffer, 0, buffer.length);
+ if (nRead == -1) {
+ break;
}
+ writer.write(buffer, 0, nRead);
}
+
+ writer.flush();
}
/**
- * This method concatenates the text which was added inside the
- * 'concat' tags. If the text between the tags consists only of
- * whitespace characters, it is ignored.
+ * Treat empty nested text as no text.
+ *
+ * Depending on the XML parser, addText may have been called + * for "ignorable whitespace" as well.
*/ - private void catText() { + private void sanitizeText() { + if (textBuffer != null) { + if (textBuffer.toString().trim().length() == 0) { + textBuffer = null; + } + } + } - // Check the buffer. - if (textBuffer == null) { - // No text to write. - return; + /** + * sub element points to a file or contains text + */ + public static class TextElement { + private String value; + private boolean trimLeading = false; + private boolean trim = false; + private boolean filtering = true; + + /** + * whether to filter the text in this element + * or not. + * + * @param filtering true if the text should be filtered. + * the default value is true. + */ + public void setFiltering(boolean filtering) { + this.filtering = filtering; + } + + /** return the filtering attribute */ + private boolean getFiltering() { + return filtering; + } + + /** + * set the text using a file + * @param file the file to use + * @throws BuildException if the file does not exist, or cannot be + * read + */ + public void setFile(File file) { + // non-existing files are not allowed + if (!file.exists()) { + throw new BuildException("File " + file + " does not exist."); + } + + BufferedReader reader = null; + try { + reader = new BufferedReader(new FileReader(file)); + value = fileUtils.readFully(reader); + } catch (IOException ex) { + throw new BuildException(ex); + } finally { + if (reader != null) { + try {reader.close();} catch (Throwable t) {} + } + } } - String text = textBuffer.toString(); + /** + * set the text using inline + */ + public void addText(String value) { + if (value.trim().length() == 0) { + return; + } + this.value = value; + } - // Replace ${property} strings. - text = getProject().replaceProperties(text); + /** + * s:^\s*:: on each line of input + * @param strip if true do the trim + */ + public void setTrimLeading(boolean strip) { + this.trimLeading = strip; + } - // Set up a writer if necessary. - FileWriter writer = null; - if (destinationFile != null) { - try { - writer = new FileWriter(destinationFile.getAbsolutePath(), - append); - } catch (IOException ioe) { - throw new BuildException("Error creating destination " + - "file.", ioe); + /** + * whether to call text.trim() + */ + public void setTrim(boolean trim) { + this.trim = trim; + } + + /** + * return the text, after possible trimming + */ + public String getValue() { + if (value == null) { + value = ""; + } + if (value.trim().length() == 0) { + value = ""; } + if (trimLeading) { + char[] current = value.toCharArray(); + StringBuffer b = new StringBuffer(current.length); + boolean startOfLine = true; + int pos = 0; + while (pos < current.length) { + char ch = current[pos++]; + if (startOfLine) { + if (ch == ' ' || ch == '\t') { + continue; + } + startOfLine = false; + } + b.append(ch); + if (ch == '\n' || ch == '\r') { + startOfLine = true; + } + } + value = b.toString(); + } + if (trim) { + value = value.trim(); + } + return value; } + } - // Reads the text, line by line. - BufferedReader reader = null; - try { - reader = new BufferedReader(new StringReader(text)); - - String line; - while ((line = reader.readLine()) != null) { - if (destinationFile == null) { - // Log the line, using WARN so it displays in - // 'quiet' mode. - log(line, Project.MSG_WARN); + /** + * This class reads from each of the source files in turn. + * The concatentated result can then be filtered as + * a single stream. + */ + private class MultiReader extends Reader { + private int pos = 0; + private Reader reader = null; + + private Reader getReader() throws IOException { + if (reader == null) { + if (encoding == null) { + reader = new BufferedReader( + new FileReader((File) sourceFiles.elementAt(pos))); } else { - writer.write(line); - writer.write(StringUtils.LINE_SEP); - writer.flush(); - } + // invoke the zoo of io readers + reader = new BufferedReader( + new InputStreamReader( + new FileInputStream( + (File) sourceFiles.elementAt(pos)), + encoding)); + } } + return reader; + } - } catch (IOException ioe) { - throw new BuildException("Error while concatenating " + - "text.", ioe); - } finally { - // Close resources. - if (reader != null) { - try { + /** + * Read a character from the current reader object. Advance + * to the next if the reader is finished. + * @return the character read, -1 for EOF on the last reader. + * @exception IOException - possiblly thrown by the read for a reader + * object. + */ + public int read() throws IOException { + while (pos < sourceFiles.size()) { + int ch = getReader().read(); + if (ch == -1) { reader.close(); - } catch (Exception ignore) {} + reader = null; + } else { + return ch; + } + pos++; } + return -1; + } - if (writer != null) { - try { - writer.close(); - } catch (Exception ignore) {} + /** + * Read into the buffercbuf
.
+ * @param cbuf The array to be read into.
+ * @param off The offset.
+ * @param len The length to read.
+ * @exception IOException - possiblely thrown by the reads to the
+ * reader objects.
+ */
+ public int read(char cbuf[], int off, int len)
+ throws IOException {
+ int amountRead = 0;
+ int iOff = off;
+ while (pos < sourceFiles.size()) {
+ int nRead = getReader().read(cbuf, off, len);
+ if (nRead == -1 || nRead == 0) {
+ reader.close();
+ reader = null;
+ pos++;
+ } else {
+ len -= nRead;
+ off += nRead;
+ amountRead += nRead;
+ if (len == 0) {
+ return amountRead;
+ }
+ }
+ }
+ if (amountRead == 0) {
+ return -1;
+ } else {
+ return amountRead;
}
}
- }
- /**
- * Treat empty nested text as no text.
- *
- * Depending on the XML parser, addText may have been called - * for "ignorable whitespace" as well.
- */ - private void sanitizeText() { - if (textBuffer != null) { - if (textBuffer.toString().trim().length() == 0) { - textBuffer = null; + public void close() throws IOException { + if (reader != null) { + reader.close(); } } } + } -} diff --git a/src/testcases/org/apache/tools/ant/taskdefs/ConcatTest.java b/src/testcases/org/apache/tools/ant/taskdefs/ConcatTest.java index 7d5025198..7344dd46c 100644 --- a/src/testcases/org/apache/tools/ant/taskdefs/ConcatTest.java +++ b/src/testcases/org/apache/tools/ant/taskdefs/ConcatTest.java @@ -1,7 +1,7 @@ /* * The Apache Software License, Version 1.1 * - * Copyright (c) 2002 The Apache Software Foundation. All rights + * Copyright (c) 2002-2003 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without @@ -97,14 +97,7 @@ public class ConcatTest * test. */ public void tearDown() { - // Remove temporary files. - String[] rm = new String[] { tempFile, tempFile2 }; - for (int i = 0; i < rm.length; i++) { - File f = new File(getProjectDir(), rm[i]); - if (f.exists()) { - f.delete(); - } - } + executeTarget("cleanup"); } /** @@ -140,7 +133,6 @@ public class ConcatTest * Cats the file created in test3 three times. */ public void test4() { - test3(); File file = new File(getProjectDir(), tempFile); @@ -174,4 +166,80 @@ public class ConcatTest public void testConcatNoNewlineEncoding() { expectLog("testConcatNoNewlineEncoding", "ab"); } + + public void testPath() { + test3(); + + File file = new File(getProjectDir(), tempFile); + final long origSize = file.length(); + + executeTarget("testPath"); + + File file2 = new File(getProjectDir(), tempFile2); + final long newSize = file2.length(); + + assertEquals(origSize, newSize); + + } + public void testAppend() { + test3(); + + File file = new File(getProjectDir(), tempFile); + final long origSize = file.length(); + + executeTarget("testAppend"); + + File file2 = new File(getProjectDir(), tempFile2); + final long newSize = file2.length(); + + assertEquals(origSize*2, newSize); + + } + + public void testFilter() { + executeTarget("testfilter"); + assertTrue(getLog().indexOf("REPLACED") > -1); + } + + public void testNoOverwrite() { + executeTarget("testnooverwrite"); + File file2 = new File(getProjectDir(), tempFile2); + long size = file2.length(); + assertEquals(size, 0); + } + + public void testheaderfooter() { + test3(); + expectLog("testheaderfooter", "headerHello, World!footer"); + } + + public void testfileheader() { + test3(); + expectLog("testfileheader", "Hello, World!Hello, World!"); + } + + /** + * Expect an exception when attempting to cat an file to itself + */ + public void testsame() { + expectBuildException("samefile", "output file same as input"); + } + + /** + * Check if filter inline works + */ + public void testfilterinline() { + executeTarget("testfilterinline"); + assertTrue(getLog().indexOf("REPLACED") > -1); + } + + /** + * Check if multireader works + */ + public void testmultireader() { + executeTarget("testmultireader"); + assertTrue(getLog().indexOf("Bye") > -1); + assertTrue(getLog().indexOf("Hello") == -1); + } + }