/* * 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.types; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Stack; import java.util.Vector; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; import org.apache.tools.ant.taskdefs.Redirector; /** * Element representation of a <code>Redirector</code>. * @since Ant 1.6.2 */ public class RedirectorElement extends DataType { /** * Whether the input mapper was set via <code>setOutput</code>. */ private boolean usingInput = false; /** * Whether the output mapper was set via <code>setOutput</code>. */ private boolean usingOutput = false; /** * Whether the error mapper was set via <code>setError</code>. */ private boolean usingError = false; /** * Indicates if standard error should be logged to Ant's log system * rather than the output. This has no effect if standard error is * redirected to a file or property. */ private Boolean logError; /** The name of the property into which output is to be stored */ private String outputProperty; /** The name of the property into which error output is to be stored */ private String errorProperty; /** String from which input is taken */ private String inputString; /** Flag which indicates if error and output files are to be appended. */ private Boolean append; /** Flag which indicates that output should be always sent to the log */ private Boolean alwaysLog; /** Flag which indicates whether files should be created even if empty. */ private Boolean createEmptyFiles; /** Input file mapper. */ private Mapper inputMapper; /** Output file mapper. */ private Mapper outputMapper; /** Error file mapper. */ private Mapper errorMapper; /** input filter chains. */ private Vector<FilterChain> inputFilterChains = new Vector<>(); /** output filter chains. */ private Vector<FilterChain> outputFilterChains = new Vector<>(); /** error filter chains. */ private Vector<FilterChain> errorFilterChains = new Vector<>(); /** The output encoding */ private String outputEncoding; /** The error encoding */ private String errorEncoding; /** The input encoding */ private String inputEncoding; /** whether to log the inputstring */ private Boolean logInputString; /** Is the output binary or can we safely split it into lines? */ private boolean outputIsBinary = false; /** * Add the input file mapper. * @param inputMapper <code>Mapper</code>. */ public void addConfiguredInputMapper(Mapper inputMapper) { if (isReference()) { throw noChildrenAllowed(); } if (this.inputMapper != null) { if (usingInput) { throw new BuildException("attribute \"input\"" + " cannot coexist with a nested <inputmapper>"); } else { throw new BuildException("Cannot have > 1 <inputmapper>"); } } setChecked(false); this.inputMapper = inputMapper; } /** * Add the output file mapper. * @param outputMapper <code>Mapper</code>. */ public void addConfiguredOutputMapper(Mapper outputMapper) { if (isReference()) { throw noChildrenAllowed(); } if (this.outputMapper != null) { if (usingOutput) { throw new BuildException("attribute \"output\"" + " cannot coexist with a nested <outputmapper>"); } else { throw new BuildException("Cannot have > 1 <outputmapper>"); } } setChecked(false); this.outputMapper = outputMapper; } /** * Add the error file mapper. * @param errorMapper <code>Mapper</code>. */ public void addConfiguredErrorMapper(Mapper errorMapper) { if (isReference()) { throw noChildrenAllowed(); } if (this.errorMapper != null) { if (usingError) { throw new BuildException("attribute \"error\"" + " cannot coexist with a nested <errormapper>"); } else { throw new BuildException("Cannot have > 1 <errormapper>"); } } setChecked(false); this.errorMapper = errorMapper; } /** * Make this instance in effect a reference to another instance. * * <p>You must not set another attribute or nest elements inside * this element if you make it a reference.</p> * @param r the reference to use. * @throws BuildException on error. */ public void setRefid(Reference r) throws BuildException { if (usingInput || usingOutput || usingError || inputString != null || logError != null || append != null || createEmptyFiles != null || inputEncoding != null || outputEncoding != null || errorEncoding != null || outputProperty != null || errorProperty != null || logInputString != null) { throw tooManyAttributes(); } super.setRefid(r); } /** * Set the input to use for the task. * @param input the file from which input is read. */ public void setInput(File input) { if (isReference()) { throw tooManyAttributes(); } if (inputString != null) { throw new BuildException("The \"input\" and \"inputstring\" " + "attributes cannot both be specified"); } usingInput = true; inputMapper = createMergeMapper(input); } /** * Set the string to use as input * @param inputString the string which is used as the input source */ public void setInputString(String inputString) { if (isReference()) { throw tooManyAttributes(); } if (usingInput) { throw new BuildException("The \"input\" and \"inputstring\" " + "attributes cannot both be specified"); } this.inputString = inputString; } /** * Set whether to include the value of the input string in log messages. * Defaults to true. * @param logInputString true or false. * @since Ant 1.7 */ public void setLogInputString(boolean logInputString) { if (isReference()) { throw tooManyAttributes(); } this.logInputString = logInputString ? Boolean.TRUE : Boolean.FALSE; } /** * File the output of the process is redirected to. If error is not * redirected, it too will appear in the output. * * @param out the file to which output stream is written. */ public void setOutput(File out) { if (isReference()) { throw tooManyAttributes(); } if (out == null) { throw new IllegalArgumentException("output file specified as null"); } usingOutput = true; outputMapper = createMergeMapper(out); } /** * Set the output encoding. * @param outputEncoding <code>String</code>. */ public void setOutputEncoding(String outputEncoding) { if (isReference()) { throw tooManyAttributes(); } this.outputEncoding = outputEncoding; } /** * Set the error encoding. * * @param errorEncoding <code>String</code>. */ public void setErrorEncoding(String errorEncoding) { if (isReference()) { throw tooManyAttributes(); } this.errorEncoding = errorEncoding; } /** * Set the input encoding. * @param inputEncoding <code>String</code>. */ public void setInputEncoding(String inputEncoding) { if (isReference()) { throw tooManyAttributes(); } this.inputEncoding = inputEncoding; } /** * Controls whether error output of exec is logged. This is only useful * when output is being redirected and error output is desired in the * Ant log. * @param logError if true the standard error is sent to the Ant log system * and not sent to output. */ public void setLogError(boolean logError) { if (isReference()) { throw tooManyAttributes(); } this.logError = ((logError) ? Boolean.TRUE : Boolean.FALSE); } /** * Set the file to which standard error is to be redirected. * @param error the file to which error is to be written. */ public void setError(File error) { if (isReference()) { throw tooManyAttributes(); } if (error == null) { throw new IllegalArgumentException("error file specified as null"); } usingError = true; errorMapper = createMergeMapper(error); } /** * Property name whose value should be set to the output of * the process. * @param outputProperty the name of the property to be set with the * task's output. */ public void setOutputProperty(String outputProperty) { if (isReference()) { throw tooManyAttributes(); } this.outputProperty = outputProperty; } /** * Whether output should be appended to or overwrite an existing file. * Defaults to false. * @param append if true output and error streams are appended to their * respective files, if specified. */ public void setAppend(boolean append) { if (isReference()) { throw tooManyAttributes(); } this.append = ((append) ? Boolean.TRUE : Boolean.FALSE); } /** * If true, (error and non-error) output will be "teed", redirected * as specified while being sent to Ant's logging mechanism as if no * redirection had taken place. Defaults to false. * @param alwaysLog <code>boolean</code> * @since Ant 1.6.3 */ public void setAlwaysLog(boolean alwaysLog) { if (isReference()) { throw tooManyAttributes(); } this.alwaysLog = ((alwaysLog) ? Boolean.TRUE : Boolean.FALSE); } /** * Whether output and error files should be created even when empty. * Defaults to true. * @param createEmptyFiles <code>boolean</code>. */ public void setCreateEmptyFiles(boolean createEmptyFiles) { if (isReference()) { throw tooManyAttributes(); } this.createEmptyFiles = ((createEmptyFiles) ? Boolean.TRUE : Boolean.FALSE); } /** * Property name whose value should be set to the error of * the process. * @param errorProperty the name of the property to be set * with the error output. */ public void setErrorProperty(String errorProperty) { if (isReference()) { throw tooManyAttributes(); } this.errorProperty = errorProperty; } /** * Create a nested input <code>FilterChain</code>. * @return <code>FilterChain</code>. */ public FilterChain createInputFilterChain() { if (isReference()) { throw noChildrenAllowed(); } FilterChain result = new FilterChain(); result.setProject(getProject()); inputFilterChains.add(result); setChecked(false); return result; } /** * Create a nested output <code>FilterChain</code>. * @return <code>FilterChain</code>. */ public FilterChain createOutputFilterChain() { if (isReference()) { throw noChildrenAllowed(); } FilterChain result = new FilterChain(); result.setProject(getProject()); outputFilterChains.add(result); setChecked(false); return result; } /** * Create a nested error <code>FilterChain</code>. * @return <code>FilterChain</code>. */ public FilterChain createErrorFilterChain() { if (isReference()) { throw noChildrenAllowed(); } FilterChain result = new FilterChain(); result.setProject(getProject()); errorFilterChains.add(result); setChecked(false); return result; } /** * Whether to consider the output created by the process binary. * * <p>Binary output will not be split into lines which may make * error and normal output look mixed up when they get written to * the same stream.</p> * @since 1.9.4 */ public void setBinaryOutput(boolean b) { outputIsBinary = b; } /** * Configure the specified <code>Redirector</code>. * @param redirector <code>Redirector</code>. */ public void configure(Redirector redirector) { configure(redirector, null); } /** * Configure the specified <code>Redirector</code> * for the specified sourcefile. * @param redirector <code>Redirector</code>. * @param sourcefile <code>String</code>. */ public void configure(Redirector redirector, String sourcefile) { if (isReference()) { getRef().configure(redirector, sourcefile); return; } dieOnCircularReference(); if (alwaysLog != null) { redirector.setAlwaysLog(alwaysLog.booleanValue()); } if (logError != null) { redirector.setLogError(logError.booleanValue()); } if (append != null) { redirector.setAppend(append.booleanValue()); } if (createEmptyFiles != null) { redirector.setCreateEmptyFiles(createEmptyFiles.booleanValue()); } if (outputProperty != null) { redirector.setOutputProperty(outputProperty); } if (errorProperty != null) { redirector.setErrorProperty(errorProperty); } if (inputString != null) { redirector.setInputString(inputString); } if (logInputString != null) { redirector.setLogInputString(logInputString.booleanValue()); } if (inputMapper != null) { String[] inputTargets = null; try { inputTargets = inputMapper.getImplementation().mapFileName(sourcefile); } catch (NullPointerException enPeaEx) { if (sourcefile != null) { throw enPeaEx; } } if (inputTargets != null && inputTargets.length > 0) { redirector.setInput(toFileArray(inputTargets)); } } if (outputMapper != null) { String[] outputTargets = null; try { outputTargets = outputMapper.getImplementation().mapFileName(sourcefile); } catch (NullPointerException enPeaEx) { if (sourcefile != null) { throw enPeaEx; } } if (outputTargets != null && outputTargets.length > 0) { redirector.setOutput(toFileArray(outputTargets)); } } if (errorMapper != null) { String[] errorTargets = null; try { errorTargets = errorMapper.getImplementation().mapFileName(sourcefile); } catch (NullPointerException enPeaEx) { if (sourcefile != null) { throw enPeaEx; } } if (errorTargets != null && errorTargets.length > 0) { redirector.setError(toFileArray(errorTargets)); } } if (!inputFilterChains.isEmpty()) { redirector.setInputFilterChains(inputFilterChains); } if (!outputFilterChains.isEmpty()) { redirector.setOutputFilterChains(outputFilterChains); } if (!errorFilterChains.isEmpty()) { redirector.setErrorFilterChains(errorFilterChains); } if (inputEncoding != null) { redirector.setInputEncoding(inputEncoding); } if (outputEncoding != null) { redirector.setOutputEncoding(outputEncoding); } if (errorEncoding != null) { redirector.setErrorEncoding(errorEncoding); } redirector.setBinaryOutput(outputIsBinary); } /** * Create a merge mapper pointing to the specified destination file. * @param destfile <code>File</code> * @return <code>Mapper</code>. */ protected Mapper createMergeMapper(File destfile) { Mapper result = new Mapper(getProject()); result.setClassname( org.apache.tools.ant.util.MergingMapper.class.getName()); result.setTo(destfile.getAbsolutePath()); return result; } /** * Return a <code>File[]</code> from the specified set of filenames. * @param name <code>String[]</code> * @return <code>File[]</code>. */ protected File[] toFileArray(String[] name) { if (name == null) { return null; } //remove any null elements ArrayList<File> list = new ArrayList<>(name.length); for (int i = 0; i < name.length; i++) { if (name[i] != null) { list.add(getProject().resolveFile(name[i])); } } return (File[]) (list.toArray(new File[list.size()])); } /** * Overrides the version of DataType to recurse on all DataType * child elements that may have been added. * @param stk the stack of data types to use (recursively). * @param p the project to use to dereference the references. * @throws BuildException on error. */ @Override protected void dieOnCircularReference(Stack<Object> stk, Project p) throws BuildException { if (isChecked()) { return; } if (isReference()) { super.dieOnCircularReference(stk, p); } else { Mapper[] m = new Mapper[] {inputMapper, outputMapper, errorMapper}; for (int i = 0; i < m.length; i++) { if (m[i] != null) { stk.push(m[i]); m[i].dieOnCircularReference(stk, p); stk.pop(); } } final List<? extends List<FilterChain>> filterChainLists = Arrays .<List<FilterChain>> asList(inputFilterChains, outputFilterChains, errorFilterChains); for (List<FilterChain> filterChains : filterChainLists) { if (filterChains != null) { for (FilterChain fc : filterChains) { pushAndInvokeCircularReferenceCheck(fc, stk, p); } } } setChecked(true); } } /** * Perform the check for circular references, returning the * referenced RedirectorElement. * @return the referenced RedirectorElement. */ private RedirectorElement getRef() { return (RedirectorElement) getCheckedRef(); } }