diff options
Diffstat (limited to 'framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/Concat.java')
-rw-r--r-- | framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/Concat.java | 955 |
1 files changed, 955 insertions, 0 deletions
diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/Concat.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/Concat.java new file mode 100644 index 00000000..1338f2a4 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/Concat.java @@ -0,0 +1,955 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.tools.ant.taskdefs; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringReader; +import java.io.Writer; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.Vector; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectComponent; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.filters.util.ChainReaderHelper; +import org.apache.tools.ant.types.FileList; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.FilterChain; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Resource; +import org.apache.tools.ant.types.ResourceCollection; +import org.apache.tools.ant.types.resources.FileResource; +import org.apache.tools.ant.types.resources.Intersect; +import org.apache.tools.ant.types.resources.LogOutputResource; +import org.apache.tools.ant.types.resources.Resources; +import org.apache.tools.ant.types.resources.Restrict; +import org.apache.tools.ant.types.resources.StringResource; +import org.apache.tools.ant.types.resources.selectors.Exists; +import org.apache.tools.ant.types.resources.selectors.Not; +import org.apache.tools.ant.types.resources.selectors.ResourceSelector; +import org.apache.tools.ant.types.selectors.SelectorUtils; +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.ResourceUtils; +import org.apache.tools.ant.util.StringUtils; + +/** + * 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: + * + * <pre> + * <concat destfile="${build.dir}/index.xml" + * append="false"> + * + * <fileset dir="${xml.root.dir}" + * includes="*.xml" /> + * + * </concat> + * </pre> + * + */ +public class Concat extends Task implements ResourceCollection { + + // The size of buffers to be used + private static final int BUFFER_SIZE = 8192; + + private static final FileUtils FILE_UTILS = FileUtils.getFileUtils(); + + private static final ResourceSelector EXISTS = new Exists(); + private static final ResourceSelector NOT_EXISTS = new Not(EXISTS); + + /** + * 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.safeReadFully(reader); + } catch (IOException ex) { + throw new BuildException(ex); + } finally { + FileUtils.close(reader); + } + } + + /** + * set the text using inline + * @param value the text to place inline + */ + public void addText(String value) { + this.value += getProject().replaceProperties(value); + } + + /** + * s:^\s*:: on each line of input + * @param strip if true do the trim + */ + public void setTrimLeading(boolean strip) { + this.trimLeading = strip; + } + + /** + * whether to call text.trim() + * @param trim if true trim the text + */ + 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; + } + } + + private interface ReaderFactory<S> { + Reader getReader(S s) throws IOException; + } + + /** + * This class reads from each of the source files in turn. + * The concatentated result can then be filtered as + * a single stream. + */ + private final class MultiReader<S> extends Reader { + private Reader reader = null; + private int lastPos = 0; + private char[] lastChars = new char[eolString.length()]; + private boolean needAddSeparator = false; + private Iterator<S> readerSources; + private ReaderFactory<S> factory; + + private MultiReader(Iterator<S> readerSources, ReaderFactory<S> factory) { + this.readerSources = readerSources; + this.factory = factory; + } + + private Reader getReader() throws IOException { + if (reader == null && readerSources.hasNext()) { + reader = factory.getReader(readerSources.next()); + Arrays.fill(lastChars, (char) 0); + } + return reader; + } + + private void nextReader() throws IOException { + close(); + reader = null; + } + + /** + * 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) { + if (lastPos >= eolString.length()) { + lastPos = 0; + needAddSeparator = false; + } else { + return eolString.charAt(lastPos++); + } + } + while (getReader() != null) { + int ch = getReader().read(); + if (ch == -1) { + nextReader(); + if (isFixLastLine() && isMissingEndOfLine()) { + needAddSeparator = true; + lastPos = 1; + return eolString.charAt(0); + } + } else { + addLastChar((char) ch); + return ch; + } + } + 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 (isFixLastLine() && isMissingEndOfLine()) { + needAddSeparator = true; + lastPos = 0; + } + } else { + if (isFixLastLine()) { + for (int i = nRead; + i > (nRead - lastChars.length); + --i) { + if (i <= 0) { + break; + } + addLastChar(cbuf[off + i - 1]); + } + } + len -= nRead; + off += nRead; + amountRead += nRead; + 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 boolean isFixLastLine() { + return fixLastLine && textBuffer == null; + } + } + + private final 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<Resource>(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<Reader>(Arrays.asList(readers).iterator(), + identityReaderFactory); + } + return outputEncoding == null ? new ReaderInputStream(rdr) + : new ReaderInputStream(rdr, outputEncoding); + } + public String getName() { + return resourceName == null + ? "concat (" + String.valueOf(c) + ")" : resourceName; + } + } + + // Attributes. + + /** + * The destination of the stream. If <code>null</code>, the system + * console is used. + */ + private Resource dest; + + /** + * 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 Resources rc; + + /** for filtering the concatenated */ + private Vector<FilterChain> filterChains; + /** ignore dates on input files */ + private boolean forceOverwrite = true; + /** overwrite read-only files */ + private boolean force = false; + /** String to place at the start of the concatenated stream */ + private TextElement footer; + /** String to place at the end of the concatenated 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; + /** whether to not create dest if no source files are + * available */ + private boolean ignoreEmpty = true; + /** exposed resource name */ + private String resourceName; + + private ReaderFactory<Resource> resourceReaderFactory = new ReaderFactory<Resource>() { + public Reader getReader(Resource o) throws IOException { + InputStream is = o.getInputStream(); + return new BufferedReader(encoding == null + ? new InputStreamReader(is) + : new InputStreamReader(is, encoding)); + } + }; + + private ReaderFactory<Reader> identityReaderFactory = new ReaderFactory<Reader>() { + public Reader getReader(Reader o) { + return o; + } + }; + + /** + * Construct a new Concat task. + */ + public Concat() { + reset(); + } + + /** + * Reset state to default. + */ + public void reset() { + append = false; + forceOverwrite = true; + dest = 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; + ignoreEmpty = true; + force = false; + } + + // Attribute setters. + + /** + * Sets the destination file, or uses the console if not specified. + * @param destinationFile the destination file + */ + public void setDestfile(File destinationFile) { + setDest(new FileResource(destinationFile)); + } + + /** + * Set the resource to write to. + * @param dest the Resource to write to. + * @since Ant 1.8 + */ + public void setDest(Resource dest) { + this.dest = dest; + } + + /** + * Sets the behavior when the destination exists. If set to + * <code>true</code> the task will append the stream data an + * {@link Appendable} resource; otherwise existing content will be + * overwritten. Defaults to <code>false</code>. + * @param append if true append output. + */ + 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 forceOverwrite if true always overwrite, otherwise only + * overwrite if the output file is older any of the + * input files. + * @since Ant 1.6 + * @deprecated use #setOverwrite instead + */ + public void setForce(boolean forceOverwrite) { + this.forceOverwrite = forceOverwrite; + } + + /** + * Force overwrite existing destination file + * @param forceOverwrite if true always overwrite, otherwise only + * overwrite if the output file is older any of the + * input files. + * @since Ant 1.8.2 + */ + public void setOverwrite(boolean forceOverwrite) { + setForce(forceOverwrite); + } + + /** + * Whether read-only destinations will be overwritten. + * + * <p>Defaults to false</p> + * + * @since Ant 1.8.2 + */ + public void setForceReadOnly(boolean f) { + force = f; + } + + /** + * Sets the behavior when no source resource files are available. If set to + * <code>false</code> the destination file will always be created. + * Defaults to <code>true</code>. + * @param ignoreEmpty if false honour destinationfile creation. + * @since Ant 1.8.0 + */ + public void setIgnoreEmpty(boolean ignoreEmpty) { + this.ignoreEmpty = ignoreEmpty; + } + + /** + * Set the name that will be reported by the exposed {@link Resource}. + * @param resourceName to set + * @since Ant 1.8.3 + */ + public void setResourceName(String resourceName) { + this.resourceName = resourceName; + } + + // 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. + * @param set the set of files + */ + public void addFileset(FileSet set) { + add(set); + } + + /** + * List of files to concatenate. + * @param list the list of files + */ + public void addFilelist(FileList list) { + add(list); + } + + /** + * Add an arbitrary ResourceCollection. + * @param c the ResourceCollection to add. + * @since Ant 1.7 + */ + public void add(ResourceCollection c) { + synchronized (this) { + if (rc == null) { + rc = new Resources(); + rc.setProject(getProject()); + rc.setCache(true); + } + } + rc.add(c); + } + + /** + * Adds a FilterChain. + * @param filterChain a filterchain to filter the concatenated input + * @since Ant 1.6 + */ + public void addFilterChain(FilterChain filterChain) { + if (filterChains == null) { + filterChains = new Vector<FilterChain>(); + } + filterChains.addElement(filterChain); + } + + /** + * This method adds text which appears in the 'concat' element. + * @param text the text to be concated. + */ + 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); + } + + /** + * Add a header to the concatenated output + * @param headerToAdd the header + * @since Ant 1.6 + */ + public void addHeader(TextElement headerToAdd) { + this.header = headerToAdd; + } + + /** + * Add a footer to the concatenated output + * @param footerToAdd the footer + * @since Ant 1.6 + */ + public void addFooter(TextElement footerToAdd) { + this.footer = footerToAdd; + } + + /** + * Append line.separator to files that do not end + * with a line.separator, default false. + * @param fixLastLine if true make sure each input file has + * new line on the concatenated stream + * @since Ant 1.6 + */ + public void setFixLastLine(boolean fixLastLine) { + this.fixLastLine = fixLastLine; + } + + /** + * Specify the end of line to find and to add if + * not present at end of each input file. This attribute + * is used in conjunction with fixlastline. + * @param crlf the type of new line to add - + * cr, mac, lf, unix, crlf, or dos + * @since Ant 1.6 + */ + public void setEol(FixCRLF.CrLf crlf) { + String s = crlf.getValue(); + if (s.equals("cr") || s.equals("mac")) { + eolString = "\r"; + } else if (s.equals("lf") || s.equals("unix")) { + eolString = "\n"; + } else if (s.equals("crlf") || s.equals("dos")) { + eolString = "\r\n"; + } + } + + /** + * Set the output writer. This is to allow + * concat to be used as a nested element. + * @param outputWriter the output writer. + * @since Ant 1.6 + */ + public void setWriter(Writer outputWriter) { + this.outputWriter = outputWriter; + } + + /** + * Set the binary attribute. If true, concat will concatenate the files + * byte for byte. This mode does not allow any filtering or other + * modifications to the input streams. The default value is false. + * @since Ant 1.6.2 + * @param binary if true, enable binary mode. + */ + public void setBinary(boolean binary) { + this.binary = binary; + } + + /** + * Execute the concat task. + */ + public void execute() { + validate(); + if (binary && dest == null) { + throw new BuildException( + "dest|destfile attribute is required for binary concatenation"); + } + ResourceCollection c = getResources(); + if (isUpToDate(c)) { + log(dest + " is up-to-date.", Project.MSG_VERBOSE); + return; + } + if (c.size() == 0 && ignoreEmpty) { + return; + } + try { + //most of these are defaulted because the concat-as-a-resource code hijacks a lot: + ResourceUtils.copyResource(new ConcatResource(c), dest == null + ? new LogOutputResource(this, Project.MSG_WARN) + : dest, + null, null, true, false, append, null, + null, getProject(), force); + } catch (IOException e) { + throw new BuildException("error concatenating content to " + dest, e); + } + } + + /** + * Implement ResourceCollection. + * @return Iterator<Resource>. + */ + public Iterator<Resource> iterator() { + validate(); + return Collections.<Resource>singletonList(new ConcatResource(getResources())).iterator(); + } + + /** + * Implement ResourceCollection. + * @return 1. + */ + public int size() { + return 1; + } + + /** + * 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 (encoding != null || outputEncoding != null) { + throw new BuildException( + "Setting 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 (dest != null && outputWriter != null) { + throw new BuildException( + "Cannot specify both a destination resource 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."); + } + } + + /** + * Get the resources to concatenate. + */ + private ResourceCollection getResources() { + if (rc == null) { + return new StringResource(getProject(), textBuffer.toString()); + } + if (dest != null) { + Intersect checkDestNotInSources = new Intersect(); + checkDestNotInSources.setProject(getProject()); + checkDestNotInSources.add(rc); + checkDestNotInSources.add(dest); + if (checkDestNotInSources.size() > 0) { + throw new BuildException("Destination resource " + dest + + " was specified as an input resource."); + } + } + Restrict noexistRc = new Restrict(); + noexistRc.add(NOT_EXISTS); + noexistRc.add(rc); + for (Resource r : noexistRc) { + log(r + " does not exist.", Project.MSG_ERR); + } + Restrict result = new Restrict(); + result.add(EXISTS); + result.add(rc); + return result; + } + + private boolean isUpToDate(ResourceCollection c) { + if (dest == null || forceOverwrite) { + return false; + } + for (Resource r : c) { + if (SelectorUtils.isOutOfDate(r, dest, FILE_UTILS.getFileTimestampGranularity())) { + return false; + } + } + return true; + } + + /** + * 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 && "".equals(textBuffer.toString().trim())) { + textBuffer = null; + } + } + + 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(); + } + +} |