From 55944515800de79dabb56e64fc16b8c72169bef3 Mon Sep 17 00:00:00 2001 From: Stefan Bodewig Date: Tue, 2 Apr 2002 14:03:05 +0000 Subject: [PATCH] New task Submitted by: Derek Slager git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@272165 13f79535-47bb-0310-9956-ffa450edef68 --- WHATSNEW | 3 +- docs/manual/CoreTasks/concat.html | 137 +++++ docs/manual/coretasklist.html | 1 + docs/manual/tasksoverview.html | 6 + src/etc/testcases/taskdefs/concat.xml | 34 ++ .../org/apache/tools/ant/taskdefs/Concat.java | 510 ++++++++++++++++++ .../tools/ant/taskdefs/defaults.properties | 1 + .../apache/tools/ant/taskdefs/ConcatTest.java | 164 ++++++ 8 files changed, 855 insertions(+), 1 deletion(-) create mode 100644 docs/manual/CoreTasks/concat.html create mode 100644 src/etc/testcases/taskdefs/concat.xml create mode 100644 src/main/org/apache/tools/ant/taskdefs/Concat.java create mode 100644 src/testcases/org/apache/tools/ant/taskdefs/ConcatTest.java diff --git a/WHATSNEW b/WHATSNEW index f06958b1d..e008f8d8f 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -116,7 +116,8 @@ Other changes: BZip2 alogrithm. * New tasks , , , , , - , , , , , , . + , , , , , , , + . * A new combined task, which replaces the old and tasks, has been added. The task, and diff --git a/docs/manual/CoreTasks/concat.html b/docs/manual/CoreTasks/concat.html new file mode 100644 index 000000000..8abb425af --- /dev/null +++ b/docs/manual/CoreTasks/concat.html @@ -0,0 +1,137 @@ + + + + + + Ant User Manual + + + + +

Concat

+ +

Description

+ +

+ 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. +

+ +

+ 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. +

+ +

Parameters

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
destfile + The destination file for the concatenated stream. + + No, the console will be used as the destination for the + stream in the absence of this attribute. +
append + Specifies whether or not the file specified by 'destfile' + should be overwritten. Defaults to "yes". + No
encoding + Specifies the encoding for the input files, used only when + concatenating files to the console. Please see http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html + for a list of possible values. Defaults to the platform's + default character encoding. + No
+ +

Parameters specified as nested elements

+ +

fileset

+ +

+ FileSets are used to + select files to be concatenated. Note that the order in which + the files selected from a fileset are concatenated is + not guaranteed. If this is an issue, use multiple + filesets or consider using filelists. +

+ +

filelist

+ +

+ FileLists are used to + select files to be concatenated. The file ordering in the + files attribute will be the same order in which the + files are concatenated. +

+ +

Examples

+ +

Cat a string to a file:

+ +
+  <cat tofile="README">Hello, World!</cat>
+      
+ +

Cat a series of files to the console:

+ +
+  <cat>
+    <fileset dir="messages" includes="*important*" />
+  </cat>
+      
+ +

Cat a single file, appending if the destination file exists:

+ +
+  <cat tofile="NOTES" append="true">
+    <filelist dir="notes" files="note.txt" />
+  </cat>
+      
+ +

Cat a series of files, overwriting if the destination file exists:

+ +
+  <cat tofile="${docbook.dir}/all-sections.xml">
+    <filelist dir="${docbook.dir}/sections" files="introduction.xml,overview.xml" />
+    <fileset dir="${docbook.dir}" includes="sections/*.xml" excludes="introduction.xml,overview.xml" />
+  </cat>
+      
+ +
+ +

+ Copyright © 2002 Apache Software Foundation. All + Rights Reserved. +

+ + + + diff --git a/docs/manual/coretasklist.html b/docs/manual/coretasklist.html index 3740bbf37..062fbcc7b 100644 --- a/docs/manual/coretasklist.html +++ b/docs/manual/coretasklist.html @@ -25,6 +25,7 @@ ChangeLog
Checksum
Chmod
+Concat
Condition
  Supported conditions
Copy
diff --git a/docs/manual/tasksoverview.html b/docs/manual/tasksoverview.html index b37a55ba5..d7ff51bb5 100644 --- a/docs/manual/tasksoverview.html +++ b/docs/manual/tasksoverview.html @@ -439,6 +439,12 @@ documentation.

chmod command.

+ + Concat +

Concatenates multiple files into a single one or to Ant's + logging system.

+ + Copy

Copies a file or Fileset to a new file or directory.

diff --git a/src/etc/testcases/taskdefs/concat.xml b/src/etc/testcases/taskdefs/concat.xml new file mode 100644 index 000000000..8a28ef602 --- /dev/null +++ b/src/etc/testcases/taskdefs/concat.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + Hello, ${world}! + + + + Hello, ${world}! + + + + + + + + + + + Hello, ${world}! + + + diff --git a/src/main/org/apache/tools/ant/taskdefs/Concat.java b/src/main/org/apache/tools/ant/taskdefs/Concat.java new file mode 100644 index 000000000..0ff07efa1 --- /dev/null +++ b/src/main/org/apache/tools/ant/taskdefs/Concat.java @@ -0,0 +1,510 @@ +/* + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2002 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Ant", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ +package org.apache.tools.ant.taskdefs; + +import org.apache.tools.ant.Task; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectHelper; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.BuildException; + +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.FileList; + +import org.apache.tools.ant.util.StringUtils; + +import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.InputStreamReader; +import java.io.StringReader; +import java.io.BufferedReader; +import java.io.IOException; + +import java.util.Vector; // 1.1 +import java.util.Enumeration; // 1.1 + +/** + * This class contains the 'concat' task, used to concatenate a series + * of files into a single stream. The destination of this stream may + * be the system console, or a file. The following is a sample + * invocation: + * + *
+ * <concat destfile="${build.dir}/index.xml"
+ *   append="false">
+ *
+ *   <fileset dir="${xml.root.dir}"
+ *     includes="*.xml" />
+ *
+ * </concat>
+ * 
+ * + * @author Derek Slager + */ +public class Concat extends Task { + + // Attributes. + + /** + * The destination of the stream. If null, the system + * console is used. + */ + private File destinationFile = null; + + /** + * If the destination file exists, should the stream be appended? + * Defaults to false. + */ + private boolean append = false; + + /** + * Stores the input file encoding. + */ + private String encoding = null; + + // 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 Vector fileSets = new Vector(); // 1.1 + + // Constructors. + + /** + * Public, no-argument constructor. Required by Ant. + */ + public Concat() {} + + // Attribute setters. + + /** + * Sets the destination file for the stream. + */ + public void setDestfile(File destinationFile) { + this.destinationFile = destinationFile; + } + + /** + * Sets the behavior when the destination file exists, if set to + * true the stream data will be appended to the + * existing file, otherwise the existing file will be + * overwritten. Defaults to false. + */ + public void setAppend(boolean append) { + this.append = append; + } + + /** + * Sets the encoding for the input files, used when displaying the + * data via the console. + */ + public void setEncoding(String encoding) { + this.encoding = encoding; + } + + // Nested element creators. + + /** + * Adds a set of files (nested fileset element). + */ + public void addFileset(FileSet set) { + fileSets.addElement(set); + } + + /** + * Adds a list of files (nested filelist element). + */ + public void addFilelist(FileList list) { + fileSets.addElement(list); + } + + /** + * This method adds text which appears in the 'concat' element. + */ + public void addText(String text) { + if (textBuffer == null) { + // Initialize to the size of the first text fragment, with + // the hopes that it's the only one. + textBuffer = new StringBuffer(text.length()); + } + + // Append the fragment -- we defer property replacement until + // later just in case we get a partial property in a fragment. + textBuffer.append(text); + } + + /** + * This method performs the concatenation. + */ + public void execute() + throws BuildException { + + // treat empty nested text as no text + sanitizeText(); + + // Sanity check our inputs. + if (fileSets.size() == 0 && textBuffer == null) { + // Nothing to concatenate! + throw new BuildException("At least one file " + + "must be provided, or " + + "some text."); + } + + // Iterate the FileSet collection, concatenating each file as + // it is encountered. + for (Enumeration e = fileSets.elements(); e.hasMoreElements(); ) { + + // Root directory for files. + File fileSetBase = null; + + // List of files. + String[] srcFiles = null; + + // Get the next file set, which could be a FileSet or a + // FileList instance. + Object next = e.nextElement(); + + if (next instanceof FileSet) { + + FileSet fileSet = (FileSet) next; + + // Get a directory scanner from the file set, which will + // determine the files from the set which need to be + // concatenated. + DirectoryScanner scanner = + fileSet.getDirectoryScanner(project); + + // Determine the root path. + fileSetBase = fileSet.getDir(project); + + // Get the list of files. + srcFiles = scanner.getIncludedFiles(); + + } else if (next instanceof FileList) { + + FileList fileList = (FileList) next; + + // Determine the root path. + fileSetBase = fileList.getDir(project); + + // Get the list of files. + srcFiles = fileList.getFiles(project); + + } + + // Concatenate the files. + catFiles(fileSetBase, srcFiles); + } + + // Now, cat the inline text, if applicable. + catText(); + + // Reset state to default. + append = false; + destinationFile = null; + encoding = null; + fileSets = new Vector(); + } + + /** + * This method concatenates a series of files to a single + * destination. + * + * @param base the base directory for the list of file names. + * + * @param files the names of the files to be concatenated, + * relative to the base. + */ + private void catFiles(File base, String[] files) { + + // First, create a list of absolute paths for the input files. + final int len = files.length; + String[] input = new String[len]; + for (int i = 0; i < len; 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); + continue; + } + + input[i] = current.getAbsolutePath(); + } + + // Next, perform the concatenation. + if (destinationFile == null) { + + // No destination file, dump to stdout via Ant's logging + // interface, which requires that we assume the input data + // is line-oriented. Generally, this is a safe assumption, + // as most users won't (intentionally) attempt to cat + // binary files to the console. + for (int i = 0; i < len; i++) { + + BufferedReader reader = null; + try { + if (encoding == null) { + // Use default encoding. + reader = new BufferedReader( + new FileReader(input[i]) + ); + } else { + // Use specified encoding. + reader = new BufferedReader( + new InputStreamReader( + new FileInputStream(input[i]), + encoding + ) + ); + } + + String line; + while ((line = reader.readLine()) != null) { + // Log the line, using WARN so it displays in + // 'quiet' mode. + log(line, Project.MSG_WARN); + } + + } catch (IOException ioe) { + throw new BuildException("Error while concatenating " + + "file.", ioe); + } finally { + // Close resources. + if (reader != null) { + try { + reader.close(); + } catch (Exception ignore) {} + } + } + } + + } else { + + // Use the provided file, making no assumptions about + // whether or not the file is character or line-oriented. + final int bufferSize = 1024; + OutputStream os = null; + try { + 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; + + } catch (IOException ioe) { + throw new BuildException("Unable to open destination " + + "file.", ioe); + } + + // Concatenate the file. + try { + + for (int i = 0; i < len; i++) { + + // Make sure input != output. + if (destinationFile.getAbsolutePath().equals(input[i])) { + log(destinationFile.getName() + ": input file is " + + "output file.", Project.MSG_WARN); + } + + InputStream is = null; + try { + is = new FileInputStream(input[i]); + byte[] buffer = new byte[bufferSize]; + while (true) { + int bytesRead = is.read(buffer); + if (bytesRead == -1) { // EOF + break; + } + + // Write the read data. + os.write(buffer, 0, bytesRead); + } + + os.flush(); + + } catch (IOException ioex) { + throw new BuildException("Error writing file.", ioex); + } finally { + if (is != null) { + try { + is.close(); + } catch (Exception ignore) {} + } + } + } + + } finally { + if (os != null) { + try { + os.close(); + } catch (Exception ignore) {} + } + } + } + } + + /** + * 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. + */ + private void catText() { + + // Check the buffer. + if (textBuffer == null) { + // No text to write. + return; + } + + String text = textBuffer.toString(); + + // If using filesets, disallow inline text. This is similar to + // using GNU 'cat' with file arguments -- stdin is simply + // ignored. + if (fileSets.size() > 0) { + throw new BuildException("Cannot include inline text " + + "when using filesets."); + } + + // Replace ${property} strings. + text = ProjectHelper.replaceProperties(project, text, + project.getProperties()); + + // 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); + } + } + + // 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); + } else { + writer.write(line); + writer.write(StringUtils.LINE_SEP); + writer.flush(); + } + } + + } catch (IOException ioe) { + throw new BuildException("Error while concatenating " + + "text.", ioe); + } finally { + // Close resources. + if (reader != null) { + try { + reader.close(); + } catch (Exception ignore) {} + } + + if (writer != null) { + try { + writer.close(); + } catch (Exception ignore) {} + } + } + } + + /** + * 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; + } + } + } + +} diff --git a/src/main/org/apache/tools/ant/taskdefs/defaults.properties b/src/main/org/apache/tools/ant/taskdefs/defaults.properties index 31b62a82b..0fd3af46f 100644 --- a/src/main/org/apache/tools/ant/taskdefs/defaults.properties +++ b/src/main/org/apache/tools/ant/taskdefs/defaults.properties @@ -65,6 +65,7 @@ basename=org.apache.tools.ant.taskdefs.Basename dirname=org.apache.tools.ant.taskdefs.Dirname changelog=org.apache.tools.ant.taskdefs.cvslib.ChangeLogTask buildnumber=org.apache.tools.ant.taskdefs.BuildNumber +concat=org.apache.tools.ant.taskdefs.Concat # optional tasks script=org.apache.tools.ant.taskdefs.optional.Script diff --git a/src/testcases/org/apache/tools/ant/taskdefs/ConcatTest.java b/src/testcases/org/apache/tools/ant/taskdefs/ConcatTest.java new file mode 100644 index 000000000..c8089de3f --- /dev/null +++ b/src/testcases/org/apache/tools/ant/taskdefs/ConcatTest.java @@ -0,0 +1,164 @@ +/* + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2002 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Ant", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ + +package org.apache.tools.ant.taskdefs; + +import org.apache.tools.ant.BuildFileTest; + +import java.io.File; + +/** + * A test class for the 'concat' task, used to concatenate a series of + * files into a single stream. + * + * @author Derek Slager + */ +public class ConcatTest + extends BuildFileTest { + + /** + * The name of the temporary file. + */ + private static final String tempFile = "concat.tmp"; + + /** + * The name of the temporary file. + */ + private static final String tempFile2 = "concat.tmp.2"; + + /** + * Required constructor. + */ + public ConcatTest(String name) { + super(name); + } + + /** + * Test set up, called by the unit test framework prior to each + * test. + */ + public void setUp() { + configureProject("src/etc/testcases/taskdefs/concat.xml"); + } + + /** + * Test tear down, called by the unit test framework prior to each + * 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(); + } + } + } + + /** + * Expect an exception when insufficient information is provided. + */ + public void test1() { + expectBuildException("test1", "Insufficient information."); + } + + /** + * Expect an exception when the destination file is invalid. + */ + public void test2() { + expectBuildException("test2", "Invalid destination file."); + } + + /** + * Cats the string 'Hello, World!' to a temporary file. + */ + public void test3() { + + File file = new File(getProjectDir(), tempFile); + if (file.exists()) { + file.delete(); + } + + executeTarget("test3"); + + assertTrue(file.exists()); + } + + /** + * Cats the file created in test3 three times. + */ + public void test4() { + + test3(); + + File file = new File(getProjectDir(), tempFile); + final long origSize = file.length(); + + executeTarget("test4"); + + File file2 = new File(getProjectDir(), tempFile2); + final long newSize = file2.length(); + + assertEquals(origSize * 3, newSize); + } + + /** + * Cats the string 'Hello, World!' to the console. + */ + public void test5() { + expectLog("test5", "Hello, World!"); + } + +}