/* * 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.File; import java.io.Reader; import java.io.Writer; import java.io.FileReader; import java.io.InputStream; import java.io.IOException; import java.io.StringReader; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.InputStreamReader; import java.util.Arrays; 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; import org.apache.tools.ant.ProjectComponent; import org.apache.tools.ant.filters.util.ChainReaderHelper; import org.apache.tools.ant.types.Path; import org.apache.tools.ant.types.FileSet; import org.apache.tools.ant.types.FileList; import org.apache.tools.ant.types.FilterChain; 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.Restrict; import org.apache.tools.ant.types.resources.Resources; import org.apache.tools.ant.types.resources.StringResource; 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.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: * *
* <concat destfile="${build.dir}/index.xml" * append="false"> * * <fileset dir="${xml.root.dir}" * includes="*.xml" /> * * </concat> ** */ 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
cbuf
.
* @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 MultiReadernull
, the system
* console is used.
*/
private Resource dest;
/**
* Whether or not the stream should be appended if the destination file
* exists.
* Defaults to false
.
*/
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 Vectortrue
the task will append the stream data an
* {@link Appendable} resource; otherwise existing content will be
* overwritten. Defaults to false
.
* @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.
*
* Defaults to false
* * @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 *false
the destination file will always be created.
* Defaults to true
.
* @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 VectorDepending on the XML parser, addText may have been called * for "ignorable whitespace" as well.
*/ 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(); } }