| /* |
| * 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 |
| * |
| * https://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.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.OutputStream; |
| import java.io.PipedOutputStream; |
| import java.io.PrintStream; |
| import java.io.Reader; |
| import java.io.StringReader; |
| import java.util.Arrays; |
| import java.util.Vector; |
| import java.util.stream.Collectors; |
| |
| 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.FilterChain; |
| import org.apache.tools.ant.util.ConcatFileInputStream; |
| import org.apache.tools.ant.util.FileUtils; |
| import org.apache.tools.ant.util.KeepAliveOutputStream; |
| import org.apache.tools.ant.util.LazyFileOutputStream; |
| import org.apache.tools.ant.util.LeadPipeInputStream; |
| import org.apache.tools.ant.util.LineOrientedOutputStreamRedirector; |
| import org.apache.tools.ant.util.NullOutputStream; |
| import org.apache.tools.ant.util.OutputStreamFunneler; |
| import org.apache.tools.ant.util.ReaderInputStream; |
| import org.apache.tools.ant.util.TeeOutputStream; |
| |
| /** |
| * The Redirector class manages the setup and connection of input and output |
| * redirection for an Ant project component. |
| * |
| * @since Ant 1.6 |
| */ |
| public class Redirector { |
| private static final int STREAMPUMPER_WAIT_INTERVAL = 1000; |
| |
| private static final String DEFAULT_ENCODING = System |
| .getProperty("file.encoding"); |
| |
| private class PropertyOutputStream extends ByteArrayOutputStream { |
| private final String property; |
| |
| private boolean closed = false; |
| |
| PropertyOutputStream(final String property) { |
| super(); |
| this.property = property; |
| } |
| |
| @Override |
| public void close() throws IOException { |
| synchronized (outMutex) { |
| if (!closed && !(appendOut && appendProperties)) { |
| setPropertyFromBAOS(this, property); |
| closed = true; |
| } |
| } |
| } |
| } |
| |
| /** |
| * The file(s) from which standard input is being taken. If > 1, files' |
| * content will be concatenated in the order received. |
| */ |
| private File[] input; |
| |
| /** |
| * The file(s) receiving standard output. Will also receive standard error |
| * unless standard error is redirected or logError is true. |
| */ |
| private File[] out; |
| |
| /** |
| * The file(s) to which standard error is being redirected |
| */ |
| private File[] error; |
| |
| /** |
| * 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 = false; |
| |
| /** |
| * Buffer used to capture output for storage into a property |
| */ |
| private PropertyOutputStream baos = null; |
| |
| /** |
| * Buffer used to capture error output for storage into a property |
| */ |
| private PropertyOutputStream errorBaos = null; |
| |
| /** 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 appendOut = false; |
| |
| private boolean appendErr = false; |
| |
| /** Flag which indicates that output should be always sent to the log */ |
| private boolean alwaysLogOut = false; |
| |
| private boolean alwaysLogErr = false; |
| |
| /** Flag which indicates whether files should be created even when empty. */ |
| private boolean createEmptyFilesOut = true; |
| |
| private boolean createEmptyFilesErr = true; |
| |
| /** The task for which this redirector is working */ |
| private final ProjectComponent managingTask; |
| |
| /** The stream for output data */ |
| private OutputStream outputStream = null; |
| |
| /** The stream for error output */ |
| private OutputStream errorStream = null; |
| |
| /** The stream for input */ |
| private InputStream inputStream = null; |
| |
| /** Stream which is used for line oriented output */ |
| private PrintStream outPrintStream = null; |
| |
| /** Stream which is used for line oriented error output */ |
| private PrintStream errorPrintStream = null; |
| |
| /** The output filter chains */ |
| private Vector<FilterChain> outputFilterChains; |
| |
| /** The error filter chains */ |
| private Vector<FilterChain> errorFilterChains; |
| |
| /** The input filter chains */ |
| private Vector<FilterChain> inputFilterChains; |
| |
| /** The output encoding */ |
| private String outputEncoding = DEFAULT_ENCODING; |
| |
| /** The error encoding */ |
| private String errorEncoding = DEFAULT_ENCODING; |
| |
| /** The input encoding */ |
| private String inputEncoding = DEFAULT_ENCODING; |
| |
| /** Whether to complete properties settings **/ |
| private boolean appendProperties = true; |
| |
| /** The thread group used for starting <code>StreamPumper</code> threads */ |
| private final ThreadGroup threadGroup = new ThreadGroup("redirector"); |
| |
| /** whether to log the inputstring */ |
| private boolean logInputString = true; |
| |
| /** Mutex for in */ |
| private final Object inMutex = new Object(); |
| |
| /** Mutex for out */ |
| private final Object outMutex = new Object(); |
| |
| /** Mutex for err */ |
| private final Object errMutex = new Object(); |
| |
| /** Is the output binary or can we safely split it into lines? */ |
| private boolean outputIsBinary = false; |
| |
| /** Flag which indicates if error and output files are to be discarded. */ |
| private boolean discardOut = false; |
| |
| private boolean discardErr = false; |
| |
| /** |
| * Create a redirector instance for the given task |
| * |
| * @param managingTask |
| * the task for which the redirector is to work |
| */ |
| public Redirector(final Task managingTask) { |
| this((ProjectComponent) managingTask); |
| } |
| |
| /** |
| * Create a redirector instance for the given task |
| * |
| * @param managingTask |
| * the project component for which the redirector is to work |
| * @since Ant 1.6.3 |
| */ |
| public Redirector(final ProjectComponent managingTask) { |
| this.managingTask = managingTask; |
| } |
| |
| /** |
| * Set the input to use for the task |
| * |
| * @param input |
| * the file from which input is read. |
| */ |
| public void setInput(final File input) { |
| setInput((input == null) ? null : new File[] {input}); |
| } |
| |
| /** |
| * Set the input to use for the task |
| * |
| * @param input |
| * the files from which input is read. |
| */ |
| public void setInput(final File[] input) { |
| synchronized (inMutex) { |
| if (input == null) { |
| this.input = null; |
| } else { |
| this.input = input.clone(); |
| } |
| } |
| } |
| |
| /** |
| * Set the string to use as input |
| * |
| * @param inputString |
| * the string which is used as the input source |
| */ |
| public void setInputString(final String inputString) { |
| synchronized (inMutex) { |
| 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(final boolean logInputString) { |
| this.logInputString = logInputString; |
| } |
| |
| /** |
| * Set a stream to use as input. |
| * |
| * @param inputStream |
| * the stream from which input will be read |
| * @since Ant 1.6.3 |
| */ |
| /* public */void setInputStream(final InputStream inputStream) { |
| synchronized (inMutex) { |
| this.inputStream = inputStream; |
| } |
| } |
| |
| /** |
| * 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(final File out) { |
| setOutput((out == null) ? null : new File[] {out}); |
| } |
| |
| /** |
| * Files the output of the process is redirected to. If error is not |
| * redirected, it too will appear in the output |
| * |
| * @param out |
| * the files to which output stream is written |
| */ |
| public void setOutput(final File[] out) { |
| synchronized (outMutex) { |
| if (out == null) { |
| this.out = null; |
| } else { |
| this.out = out.clone(); |
| } |
| } |
| } |
| |
| /** |
| * Set the output encoding. |
| * |
| * @param outputEncoding |
| * <code>String</code>. |
| */ |
| public void setOutputEncoding(final String outputEncoding) { |
| if (outputEncoding == null) { |
| throw new IllegalArgumentException( |
| "outputEncoding must not be null"); |
| } |
| synchronized (outMutex) { |
| this.outputEncoding = outputEncoding; |
| } |
| } |
| |
| /** |
| * Set the error encoding. |
| * |
| * @param errorEncoding |
| * <code>String</code>. |
| */ |
| public void setErrorEncoding(final String errorEncoding) { |
| if (errorEncoding == null) { |
| throw new IllegalArgumentException("errorEncoding must not be null"); |
| } |
| synchronized (errMutex) { |
| this.errorEncoding = errorEncoding; |
| } |
| } |
| |
| /** |
| * Set the input encoding. |
| * |
| * @param inputEncoding |
| * <code>String</code>. |
| */ |
| public void setInputEncoding(final String inputEncoding) { |
| if (inputEncoding == null) { |
| throw new IllegalArgumentException("inputEncoding must not be null"); |
| } |
| synchronized (inMutex) { |
| 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(final boolean logError) { |
| synchronized (errMutex) { |
| this.logError = logError; |
| } |
| } |
| |
| /** |
| * This <code>Redirector</code>'s subordinate |
| * <code>PropertyOutputStream</code>s will not set their respective |
| * properties <code>while (appendProperties && append)</code>. |
| * |
| * @param appendProperties |
| * whether to append properties. |
| */ |
| public void setAppendProperties(final boolean appendProperties) { |
| synchronized (outMutex) { |
| this.appendProperties = appendProperties; |
| } |
| } |
| |
| /** |
| * 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(final File error) { |
| setError((error == null) ? null : new File[] {error}); |
| } |
| |
| /** |
| * Set the files to which standard error is to be redirected. |
| * |
| * @param error |
| * the file to which error is to be written |
| */ |
| public void setError(final File[] error) { |
| synchronized (errMutex) { |
| if (error == null) { |
| this.error = null; |
| } else { |
| this.error = error.clone(); |
| } |
| } |
| } |
| |
| /** |
| * 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(final String outputProperty) { |
| if (outputProperty == null |
| || !(outputProperty.equals(this.outputProperty))) { |
| synchronized (outMutex) { |
| this.outputProperty = outputProperty; |
| baos = null; |
| } |
| } |
| } |
| |
| /** |
| * 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(final boolean append) { |
| synchronized (outMutex) { |
| appendOut = append; |
| } |
| synchronized (errMutex) { |
| appendErr = append; |
| } |
| } |
| |
| /** |
| * Whether output should be discarded. |
| * |
| * <p>Defaults to false.</p> |
| * |
| * @param discard |
| * if true output streams are discarded. |
| * |
| * @since Ant 1.10.10 |
| * @see #setDiscardError |
| */ |
| public void setDiscardOutput(final boolean discard) { |
| synchronized (outMutex) { |
| discardOut = discard; |
| } |
| } |
| |
| /** |
| * Whether error output should be discarded. |
| * |
| * <p>Defaults to false.</p> |
| * |
| * @param discard |
| * if true error streams are discarded. |
| * |
| * @since Ant 1.10.10 |
| * @see #setDiscardOutput |
| */ |
| public void setDiscardError(final boolean discard) { |
| synchronized (errMutex) { |
| discardErr = discard; |
| } |
| } |
| |
| /** |
| * 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(final boolean alwaysLog) { |
| synchronized (outMutex) { |
| alwaysLogOut = alwaysLog; |
| } |
| synchronized (errMutex) { |
| alwaysLogErr = alwaysLog; |
| } |
| } |
| |
| /** |
| * Whether output and error files should be created even when empty. |
| * Defaults to true. |
| * |
| * @param createEmptyFiles |
| * <code>boolean</code>. |
| */ |
| public void setCreateEmptyFiles(final boolean createEmptyFiles) { |
| synchronized (outMutex) { |
| createEmptyFilesOut = createEmptyFiles; |
| } |
| synchronized (outMutex) { |
| createEmptyFilesErr = createEmptyFiles; |
| } |
| } |
| |
| /** |
| * 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(final String errorProperty) { |
| synchronized (errMutex) { |
| if (errorProperty == null |
| || !(errorProperty.equals(this.errorProperty))) { |
| this.errorProperty = errorProperty; |
| errorBaos = null; |
| } |
| } |
| } |
| |
| /** |
| * Set the input <code>FilterChain</code>s. |
| * |
| * @param inputFilterChains |
| * <code>Vector</code> containing <code>FilterChain</code>. |
| */ |
| public void setInputFilterChains(final Vector<FilterChain> inputFilterChains) { |
| synchronized (inMutex) { |
| this.inputFilterChains = inputFilterChains; |
| } |
| } |
| |
| /** |
| * Set the output <code>FilterChain</code>s. |
| * |
| * @param outputFilterChains |
| * <code>Vector</code> containing <code>FilterChain</code>. |
| */ |
| public void setOutputFilterChains(final Vector<FilterChain> outputFilterChains) { |
| synchronized (outMutex) { |
| this.outputFilterChains = outputFilterChains; |
| } |
| } |
| |
| /** |
| * Set the error <code>FilterChain</code>s. |
| * |
| * @param errorFilterChains |
| * <code>Vector</code> containing <code>FilterChain</code>. |
| */ |
| public void setErrorFilterChains(final Vector<FilterChain> errorFilterChains) { |
| synchronized (errMutex) { |
| this.errorFilterChains = errorFilterChains; |
| } |
| } |
| |
| /** |
| * 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> |
| * |
| * @param b boolean |
| * @since 1.9.4 |
| */ |
| public void setBinaryOutput(final boolean b) { |
| outputIsBinary = b; |
| } |
| |
| /** |
| * Set a property from a ByteArrayOutputStream |
| * |
| * @param baos |
| * contains the property value. |
| * @param propertyName |
| * the property name. |
| */ |
| private void setPropertyFromBAOS(final ByteArrayOutputStream baos, |
| final String propertyName) { |
| final BufferedReader in = new BufferedReader(new StringReader(Execute.toString(baos))); |
| managingTask.getProject().setNewProperty(propertyName, |
| in.lines().collect(Collectors.joining(System.lineSeparator()))); |
| } |
| |
| /** |
| * Create the input, error and output streams based on the configuration |
| * options. |
| */ |
| public void createStreams() { |
| |
| synchronized (outMutex) { |
| outStreams(); |
| if (alwaysLogOut || outputStream == null) { |
| final OutputStream outputLog = new LogOutputStream(managingTask, |
| Project.MSG_INFO); |
| outputStream = (outputStream == null) ? outputLog |
| : new TeeOutputStream(outputLog, outputStream); |
| } |
| |
| if ((outputFilterChains != null && outputFilterChains.size() > 0) |
| || !outputEncoding.equalsIgnoreCase(inputEncoding)) { |
| try { |
| final LeadPipeInputStream snk = new LeadPipeInputStream(); |
| snk.setManagingComponent(managingTask); |
| |
| InputStream outPumpIn = snk; |
| |
| Reader reader = new InputStreamReader(outPumpIn, |
| inputEncoding); |
| |
| if (outputFilterChains != null |
| && outputFilterChains.size() > 0) { |
| final ChainReaderHelper helper = new ChainReaderHelper(); |
| helper.setProject(managingTask.getProject()); |
| helper.setPrimaryReader(reader); |
| helper.setFilterChains(outputFilterChains); |
| reader = helper.getAssembledReader(); |
| } |
| outPumpIn = new ReaderInputStream(reader, outputEncoding); |
| |
| final Thread t = new Thread(threadGroup, new StreamPumper( |
| outPumpIn, outputStream, true), "output pumper"); |
| t.setPriority(Thread.MAX_PRIORITY); |
| outputStream = new PipedOutputStream(snk); |
| t.start(); |
| } catch (final IOException eyeOhEx) { |
| throw new BuildException("error setting up output stream", |
| eyeOhEx); |
| } |
| } |
| } |
| |
| synchronized (errMutex) { |
| errorStreams(); |
| if (alwaysLogErr || errorStream == null) { |
| final OutputStream errorLog = new LogOutputStream(managingTask, |
| Project.MSG_WARN); |
| errorStream = (errorStream == null) ? errorLog |
| : new TeeOutputStream(errorLog, errorStream); |
| } |
| |
| if ((errorFilterChains != null && errorFilterChains.size() > 0) |
| || !errorEncoding.equalsIgnoreCase(inputEncoding)) { |
| try { |
| final LeadPipeInputStream snk = new LeadPipeInputStream(); |
| snk.setManagingComponent(managingTask); |
| |
| InputStream errPumpIn = snk; |
| |
| Reader reader = new InputStreamReader(errPumpIn, |
| inputEncoding); |
| |
| if (errorFilterChains != null |
| && errorFilterChains.size() > 0) { |
| final ChainReaderHelper helper = new ChainReaderHelper(); |
| helper.setProject(managingTask.getProject()); |
| helper.setPrimaryReader(reader); |
| helper.setFilterChains(errorFilterChains); |
| reader = helper.getAssembledReader(); |
| } |
| errPumpIn = new ReaderInputStream(reader, errorEncoding); |
| |
| final Thread t = new Thread(threadGroup, new StreamPumper( |
| errPumpIn, errorStream, true), "error pumper"); |
| t.setPriority(Thread.MAX_PRIORITY); |
| errorStream = new PipedOutputStream(snk); |
| t.start(); |
| } catch (final IOException eyeOhEx) { |
| throw new BuildException("error setting up error stream", |
| eyeOhEx); |
| } |
| } |
| } |
| |
| synchronized (inMutex) { |
| // if input files are specified, inputString and inputStream are |
| // ignored; |
| // classes that work with redirector attributes can enforce |
| // whatever warnings are needed |
| if (input != null && input.length > 0) { |
| managingTask |
| .log("Redirecting input from file" |
| + ((input.length == 1) ? "" : "s"), |
| Project.MSG_VERBOSE); |
| try { |
| inputStream = new ConcatFileInputStream(input); |
| } catch (final IOException eyeOhEx) { |
| throw new BuildException(eyeOhEx); |
| } |
| ((ConcatFileInputStream) inputStream).setManagingComponent(managingTask); |
| } else if (inputString != null) { |
| final StringBuffer buf = new StringBuffer("Using input "); |
| if (logInputString) { |
| buf.append('"').append(inputString).append('"'); |
| } else { |
| buf.append("string"); |
| } |
| managingTask.log(buf.toString(), Project.MSG_VERBOSE); |
| inputStream = new ByteArrayInputStream(inputString.getBytes()); |
| } |
| |
| if (inputStream != null && inputFilterChains != null |
| && inputFilterChains.size() > 0) { |
| final ChainReaderHelper helper = new ChainReaderHelper(); |
| helper.setProject(managingTask.getProject()); |
| try { |
| helper.setPrimaryReader(new InputStreamReader(inputStream, |
| inputEncoding)); |
| } catch (final IOException eyeOhEx) { |
| throw new BuildException("error setting up input stream", |
| eyeOhEx); |
| } |
| helper.setFilterChains(inputFilterChains); |
| inputStream = new ReaderInputStream( |
| helper.getAssembledReader(), inputEncoding); |
| } |
| } |
| } |
| |
| /** outStreams */ |
| private void outStreams() { |
| final boolean haveOutputFiles = out != null && out.length > 0; |
| if (discardOut) { |
| if (haveOutputFiles || outputProperty != null) { |
| throw new BuildException("Cant discard output when output or outputProperty" |
| + " are set"); |
| } |
| managingTask.log("Discarding output", Project.MSG_VERBOSE); |
| outputStream = NullOutputStream.INSTANCE; |
| return; |
| } |
| if (haveOutputFiles) { |
| final String logHead = "Output " |
| + ((appendOut) ? "appended" : "redirected") + " to "; |
| outputStream = foldFiles(out, logHead, Project.MSG_VERBOSE, |
| appendOut, createEmptyFilesOut); |
| } |
| if (outputProperty != null) { |
| if (baos == null) { |
| baos = new PropertyOutputStream(outputProperty); |
| managingTask.log("Output redirected to property: " |
| + outputProperty, Project.MSG_VERBOSE); |
| } |
| // shield it from being closed by a filtering StreamPumper |
| final OutputStream keepAliveOutput = new KeepAliveOutputStream(baos); |
| outputStream = (outputStream == null) ? keepAliveOutput |
| : new TeeOutputStream(outputStream, keepAliveOutput); |
| } else { |
| baos = null; |
| } |
| } |
| |
| private void errorStreams() { |
| final boolean haveErrorFiles = error != null && error.length > 0; |
| if (discardErr) { |
| if (haveErrorFiles || errorProperty != null || logError) { |
| throw new BuildException("Cant discard error output when error, errorProperty" |
| + " or logError are set"); |
| } |
| managingTask.log("Discarding error output", Project.MSG_VERBOSE); |
| errorStream = NullOutputStream.INSTANCE; |
| return; |
| } |
| if (haveErrorFiles) { |
| final String logHead = "Error " |
| + ((appendErr) ? "appended" : "redirected") + " to "; |
| errorStream = foldFiles(error, logHead, Project.MSG_VERBOSE, |
| appendErr, createEmptyFilesErr); |
| } else if (!(logError || outputStream == null) && errorProperty == null) { |
| final long funnelTimeout = 0L; |
| final OutputStreamFunneler funneler = new OutputStreamFunneler( |
| outputStream, funnelTimeout); |
| try { |
| outputStream = funneler.getFunnelInstance(); |
| errorStream = funneler.getFunnelInstance(); |
| if (!outputIsBinary) { |
| outputStream = new LineOrientedOutputStreamRedirector(outputStream); |
| errorStream = new LineOrientedOutputStreamRedirector(errorStream); |
| } |
| } catch (final IOException eyeOhEx) { |
| throw new BuildException( |
| "error splitting output/error streams", eyeOhEx); |
| } |
| } |
| if (errorProperty != null) { |
| if (errorBaos == null) { |
| errorBaos = new PropertyOutputStream(errorProperty); |
| managingTask.log("Error redirected to property: " |
| + errorProperty, Project.MSG_VERBOSE); |
| } |
| // shield it from being closed by a filtering StreamPumper |
| final OutputStream keepAliveError = new KeepAliveOutputStream(errorBaos); |
| errorStream = (error == null || error.length == 0) ? keepAliveError |
| : new TeeOutputStream(errorStream, keepAliveError); |
| } else { |
| errorBaos = null; |
| } |
| } |
| |
| /** |
| * Create the StreamHandler to use with our Execute instance. |
| * |
| * @return the execute stream handler to manage the input, output and error |
| * streams. |
| * |
| * @throws BuildException |
| * if the execute stream handler cannot be created. |
| */ |
| public ExecuteStreamHandler createHandler() throws BuildException { |
| createStreams(); |
| final boolean nonBlockingRead = input == null && inputString == null; |
| return new PumpStreamHandler(getOutputStream(), getErrorStream(), |
| getInputStream(), nonBlockingRead); |
| } |
| |
| /** |
| * Pass output sent to System.out to specified output. |
| * |
| * @param output |
| * the data to be output |
| */ |
| protected void handleOutput(final String output) { |
| synchronized (outMutex) { |
| if (outPrintStream == null) { |
| outPrintStream = new PrintStream(outputStream); |
| } |
| outPrintStream.print(output); |
| } |
| } |
| |
| /** |
| * Handle an input request |
| * |
| * @param buffer |
| * the buffer into which data is to be read. |
| * @param offset |
| * the offset into the buffer at which data is stored. |
| * @param length |
| * the amount of data to read |
| * |
| * @return the number of bytes read |
| * |
| * @exception IOException |
| * if the data cannot be read |
| */ |
| protected int handleInput(final byte[] buffer, final int offset, final int length) |
| throws IOException { |
| synchronized (inMutex) { |
| if (inputStream == null) { |
| return managingTask.getProject().defaultInput(buffer, offset, |
| length); |
| } |
| return inputStream.read(buffer, offset, length); |
| |
| } |
| } |
| |
| /** |
| * Process data due to a flush operation. |
| * |
| * @param output |
| * the data being flushed. |
| */ |
| protected void handleFlush(final String output) { |
| synchronized (outMutex) { |
| if (outPrintStream == null) { |
| outPrintStream = new PrintStream(outputStream); |
| } |
| outPrintStream.print(output); |
| outPrintStream.flush(); |
| } |
| } |
| |
| /** |
| * Process error output |
| * |
| * @param output |
| * the error output data. |
| */ |
| protected void handleErrorOutput(final String output) { |
| synchronized (errMutex) { |
| if (errorPrintStream == null) { |
| errorPrintStream = new PrintStream(errorStream); |
| } |
| errorPrintStream.print(output); |
| } |
| } |
| |
| /** |
| * Handle a flush operation on the error stream |
| * |
| * @param output |
| * the error information being flushed. |
| */ |
| protected void handleErrorFlush(final String output) { |
| synchronized (errMutex) { |
| if (errorPrintStream == null) { |
| errorPrintStream = new PrintStream(errorStream); |
| } |
| errorPrintStream.print(output); |
| errorPrintStream.flush(); |
| } |
| } |
| |
| /** |
| * Get the output stream for the redirector |
| * |
| * @return the redirector's output stream or null if no output has been |
| * configured |
| */ |
| public OutputStream getOutputStream() { |
| synchronized (outMutex) { |
| return outputStream; |
| } |
| } |
| |
| /** |
| * Get the error stream for the redirector |
| * |
| * @return the redirector's error stream or null if no output has been |
| * configured |
| */ |
| public OutputStream getErrorStream() { |
| synchronized (errMutex) { |
| return errorStream; |
| } |
| } |
| |
| /** |
| * Get the input stream for the redirector |
| * |
| * @return the redirector's input stream or null if no output has been |
| * configured |
| */ |
| public InputStream getInputStream() { |
| synchronized (inMutex) { |
| return inputStream; |
| } |
| } |
| |
| /** |
| * Complete redirection. |
| * |
| * This operation will close any streams and create any specified property |
| * values. |
| * |
| * @throws IOException |
| * if the output properties cannot be read from their output |
| * streams. |
| */ |
| public void complete() throws IOException { |
| System.out.flush(); |
| System.err.flush(); |
| |
| synchronized (inMutex) { |
| if (inputStream != null) { |
| inputStream.close(); |
| } |
| } |
| |
| synchronized (outMutex) { |
| outputStream.flush(); |
| outputStream.close(); |
| } |
| |
| synchronized (errMutex) { |
| errorStream.flush(); |
| errorStream.close(); |
| } |
| |
| // wait for the StreamPumpers to finish |
| synchronized (this) { |
| while (threadGroup.activeCount() > 0) { |
| try { |
| managingTask.log("waiting for " + threadGroup.activeCount() |
| + " Threads:", Project.MSG_DEBUG); |
| final Thread[] thread = new Thread[threadGroup.activeCount()]; |
| threadGroup.enumerate(thread); |
| for (int i = 0; i < thread.length && thread[i] != null; i++) { |
| try { |
| managingTask.log(thread[i].toString(), |
| Project.MSG_DEBUG); |
| } catch (final NullPointerException enPeaEx) { |
| // Ignore exception |
| } |
| } |
| wait(STREAMPUMPER_WAIT_INTERVAL); |
| } catch (final InterruptedException eyeEx) { |
| final Thread[] thread = new Thread[threadGroup.activeCount()]; |
| threadGroup.enumerate(thread); |
| for (int i = 0; i < thread.length && thread[i] != null; i++) { |
| thread[i].interrupt(); |
| } |
| } |
| } |
| } |
| |
| setProperties(); |
| |
| synchronized (inMutex) { |
| inputStream = null; |
| } |
| synchronized (outMutex) { |
| outputStream = null; |
| outPrintStream = null; |
| } |
| synchronized (errMutex) { |
| errorStream = null; |
| errorPrintStream = null; |
| } |
| } |
| |
| /** |
| * Notify the <code>Redirector</code> that it is now okay to set any output |
| * and/or error properties. |
| */ |
| public void setProperties() { |
| synchronized (outMutex) { |
| FileUtils.close(baos); |
| } |
| synchronized (errMutex) { |
| FileUtils.close(errorBaos); |
| } |
| } |
| |
| private OutputStream foldFiles(final File[] file, final String logHead, final int loglevel, |
| final boolean append, final boolean createEmptyFiles) { |
| final OutputStream result = new LazyFileOutputStream(file[0], append, |
| createEmptyFiles); |
| |
| managingTask.log(logHead + file[0], loglevel); |
| final char[] c = new char[logHead.length()]; |
| Arrays.fill(c, ' '); |
| final String indent = new String(c); |
| |
| for (int i = 1; i < file.length; i++) { |
| outputStream = new TeeOutputStream(outputStream, |
| new LazyFileOutputStream(file[i], append, createEmptyFiles)); |
| managingTask.log(indent + file[i], loglevel); |
| } |
| return result; |
| } |
| } |