diff --git a/WHATSNEW b/WHATSNEW index fd75a5c68..d01fb160e 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -137,6 +137,9 @@ Changes that could break older environments: Fixed bugs: ----------- + * Got rid of deadlock between in in, out and err in the Redirector. + Bugzilla Report 44544. + * Caused by AssertionError no longer filtered. Bugzilla report 45631. * would sometimes recreate JARs unnecessarily. Bugzilla report 45902. diff --git a/src/main/org/apache/tools/ant/taskdefs/Redirector.java b/src/main/org/apache/tools/ant/taskdefs/Redirector.java index 0c70a9ec8..8d68556ed 100644 --- a/src/main/org/apache/tools/ant/taskdefs/Redirector.java +++ b/src/main/org/apache/tools/ant/taskdefs/Redirector.java @@ -47,19 +47,20 @@ import org.apache.tools.ant.util.ConcatFileInputStream; import org.apache.tools.ant.util.KeepAliveOutputStream; /** - * The Redirector class manages the setup and connection of - * input and output redirection for an Ant project component. - * + * 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 static final String DEFAULT_ENCODING = System + .getProperty("file.encoding"); private class PropertyOutputStream extends ByteArrayOutputStream { private String property; + private boolean closed = false; PropertyOutputStream(String property) { @@ -68,16 +69,18 @@ public class Redirector { } public void close() throws IOException { - if (!closed && !(append && appendProperties)) { - setPropertyFromBAOS(this, property); - closed = true; + 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. + * The file(s) from which standard input is being taken. If > 1, files' + * content will be concatenated in the order received. */ private File[] input; @@ -93,10 +96,10 @@ public class Redirector { 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. - */ + * 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; /** @@ -119,13 +122,19 @@ public class Redirector { private String inputString; /** Flag which indicates if error and output files are to be appended. */ - private boolean append = false; + private boolean appendOut = false; + + private boolean appendErr = false; /** Flag which indicates that output should be always sent to the log */ - private boolean alwaysLog = false; + private boolean alwaysLogOut = false; + + private boolean alwaysLogErr = false; /** Flag which indicates whether files should be created even when empty. */ - private boolean createEmptyFiles = true; + private boolean createEmptyFilesOut = true; + + private boolean createEmptyFilesErr = true; /** The task for which this redirector is working */ private ProjectComponent managingTask; @@ -172,10 +181,20 @@ public class Redirector { /** whether to log the inputstring */ private boolean logInputString = true; + /** Mutex for in */ + private Object inMutex = new Object(); + + /** Mutex for out */ + private Object outMutex = new Object(); + + /** Mutex for err */ + private Object errMutex = new Object(); + /** * Create a redirector instance for the given task - * - * @param managingTask the task for which the redirector is to work + * + * @param managingTask + * the task for which the redirector is to work */ public Redirector(Task managingTask) { this((ProjectComponent) managingTask); @@ -183,9 +202,9 @@ public class Redirector { /** * Create a redirector instance for the given task - * - * @param managingTask the project component for which the - * redirector is to work + * + * @param managingTask + * the project component for which the redirector is to work * @since Ant 1.6.3 */ public Redirector(ProjectComponent managingTask) { @@ -194,35 +213,48 @@ public class Redirector { /** * Set the input to use for the task - * - * @param input the file from which input is read. + * + * @param input + * the file from which input is read. */ public void setInput(File input) { - setInput((input == null) ? null : new 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 synchronized void setInput(File[] input) { - this.input = input; + * + * @param input + * the files from which input is read. + */ + public void setInput(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 + * + * @param inputString + * the string which is used as the input source */ - public synchronized void setInputString(String inputString) { - this.inputString = inputString; + public void setInputString(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. + * + * @param logInputString + * true or false. * @since Ant 1.7 */ public void setLogInputString(boolean logInputString) { @@ -231,218 +263,280 @@ public class Redirector { /** * Set a stream to use as input. - * - * @param inputStream the stream from which input will be read + * + * @param inputStream + * the stream from which input will be read * @since Ant 1.6.3 */ - /*public*/ void setInputStream(InputStream inputStream) { - this.inputStream = inputStream; + /* public */void setInputStream(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 + * + * @param out + * the file to which output stream is written */ public void setOutput(File out) { - setOutput((out == null) ? null : new 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 synchronized void setOutput(File[] out) { - this.out = out; + * + * @param out + * the files to which output stream is written + */ + public void setOutput(File[] out) { + synchronized (outMutex) { + if (out == null) { + this.out = null; + } else { + this.out = out.clone(); + } + } } /** * Set the output encoding. - * - * @param outputEncoding String. + * + * @param outputEncoding + * String. */ - public synchronized void setOutputEncoding(String outputEncoding) { + public void setOutputEncoding(String outputEncoding) { if (outputEncoding == null) { throw new IllegalArgumentException( - "outputEncoding must not be null"); - } else { + "outputEncoding must not be null"); + } + synchronized (outMutex) { this.outputEncoding = outputEncoding; } } /** * Set the error encoding. - * - * @param errorEncoding String. + * + * @param errorEncoding + * String. */ - public synchronized void setErrorEncoding(String errorEncoding) { + public void setErrorEncoding(String errorEncoding) { if (errorEncoding == null) { - throw new IllegalArgumentException( - "errorEncoding must not be null"); - } else { + throw new IllegalArgumentException("errorEncoding must not be null"); + } + synchronized (errMutex) { this.errorEncoding = errorEncoding; } } /** * Set the input encoding. - * - * @param inputEncoding String. + * + * @param inputEncoding + * String. */ - public synchronized void setInputEncoding(String inputEncoding) { + public void setInputEncoding(String inputEncoding) { if (inputEncoding == null) { - throw new IllegalArgumentException( - "inputEncoding must not be null"); - } else { + 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. + * 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 synchronized void setLogError(boolean logError) { - this.logError = logError; + public void setLogError(boolean logError) { + synchronized (errMutex) { + this.logError = logError; + } } /** * This Redirector's subordinate * PropertyOutputStreams will not set their respective * properties while (appendProperties && append). - * - * @param appendProperties whether to append properties. + * + * @param appendProperties + * whether to append properties. */ - public synchronized void setAppendProperties(boolean appendProperties) { - this.appendProperties = appendProperties; + public void setAppendProperties(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 + * + * @param error + * the file to which error is to be written */ public void setError(File error) { - setError((error == null) ? null : new 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 synchronized void setError(File[] error) { - this.error = error; + * + * @param error + * the file to which error is to be written + */ + public void setError(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. + * 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 synchronized void setOutputProperty(String outputProperty) { + public void setOutputProperty(String outputProperty) { if (outputProperty == null - || !(outputProperty.equals(this.outputProperty))) { - this.outputProperty = outputProperty; - baos = 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 synchronized void setAppend(boolean append) { - this.append = append; + * + * @param append + * if true output and error streams are appended to their + * respective files, if specified. + */ + public void setAppend(boolean append) { + synchronized (outMutex) { + appendOut = append; + } + synchronized (errMutex) { + appendErr = append; + } } /** - * 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 boolean + * 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 + * boolean * @since Ant 1.6.3 */ - public synchronized void setAlwaysLog(boolean alwaysLog) { - this.alwaysLog = alwaysLog; + public void setAlwaysLog(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 boolean. + * + * @param createEmptyFiles + * boolean. */ - public synchronized void setCreateEmptyFiles(boolean createEmptyFiles) { - this.createEmptyFiles = createEmptyFiles; + public void setCreateEmptyFiles(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. + * 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 synchronized void setErrorProperty(String errorProperty) { - if (errorProperty == null - || !(errorProperty.equals(this.errorProperty))) { - this.errorProperty = errorProperty; - errorBaos = null; + public void setErrorProperty(String errorProperty) { + synchronized (errMutex) { + if (errorProperty == null + || !(errorProperty.equals(this.errorProperty))) { + this.errorProperty = errorProperty; + errorBaos = null; + } } } /** * Set the input FilterChains. - * - * @param inputFilterChains Vector containing FilterChain. + * + * @param inputFilterChains + * Vector containing FilterChain. */ - public synchronized void setInputFilterChains(Vector inputFilterChains) { - this.inputFilterChains = inputFilterChains; + public void setInputFilterChains(Vector inputFilterChains) { + synchronized (inMutex) { + this.inputFilterChains = inputFilterChains; + } } /** * Set the output FilterChains. - * - * @param outputFilterChains Vector containing FilterChain. + * + * @param outputFilterChains + * Vector containing FilterChain. */ - public synchronized void setOutputFilterChains(Vector outputFilterChains) { - this.outputFilterChains = outputFilterChains; + public void setOutputFilterChains(Vector outputFilterChains) { + synchronized (outMutex) { + this.outputFilterChains = outputFilterChains; + } } /** * Set the error FilterChains. - * - * @param errorFilterChains Vector containing FilterChain. + * + * @param errorFilterChains + * Vector containing FilterChain. */ - public synchronized void setErrorFilterChains(Vector errorFilterChains) { - this.errorFilterChains = errorFilterChains; + public void setErrorFilterChains(Vector errorFilterChains) { + synchronized (errMutex) { + this.errorFilterChains = errorFilterChains; + } } /** * Set a property from a ByteArrayOutputStream - * - * @param baos contains the property value. - * @param propertyName the property name. - * - * @exception IOException if the value cannot be read form the stream. + * + * @param baos + * contains the property value. + * @param propertyName + * the property name. + * + * @exception IOException + * if the value cannot be read form the stream. */ private void setPropertyFromBAOS(ByteArrayOutputStream baos, - String propertyName) throws IOException { + String propertyName) throws IOException { - BufferedReader in - = new BufferedReader(new StringReader(Execute.toString(baos))); + BufferedReader in = new BufferedReader(new StringReader(Execute + .toString(baos))); String line = null; StringBuffer val = new StringBuffer(); while ((line = in.readLine()) != null) { @@ -455,121 +549,138 @@ public class Redirector { } /** - * Create the input, error and output streams based on the - * configuration options. + * Create the input, error and output streams based on the configuration + * options. */ - public synchronized void createStreams() { - outStreams(); - errorStreams(); - if (alwaysLog || outputStream == null) { - OutputStream outputLog - = new LogOutputStream(managingTask, Project.MSG_INFO); - outputStream = (outputStream == null) - ? outputLog : new TeeOutputStream(outputLog, outputStream); - } - if (alwaysLog || errorStream == null) { - OutputStream errorLog - = new LogOutputStream(managingTask, Project.MSG_WARN); - errorStream = (errorStream == null) - ? errorLog : new TeeOutputStream(errorLog, errorStream); - } - if ((outputFilterChains != null && outputFilterChains.size() > 0) - || !(outputEncoding.equalsIgnoreCase(inputEncoding))) { - try { - LeadPipeInputStream snk = new LeadPipeInputStream(); - snk.setManagingComponent(managingTask); + public void createStreams() { - InputStream outPumpIn = snk; + synchronized (outMutex) { + outStreams(); + if (alwaysLogOut || outputStream == null) { + OutputStream outputLog = new LogOutputStream(managingTask, + Project.MSG_INFO); + outputStream = (outputStream == null) ? outputLog + : new TeeOutputStream(outputLog, outputStream); + } - Reader reader = new InputStreamReader(outPumpIn, inputEncoding); + if ((outputFilterChains != null && outputFilterChains.size() > 0) + || !(outputEncoding.equalsIgnoreCase(inputEncoding))) { + try { + LeadPipeInputStream snk = new LeadPipeInputStream(); + snk.setManagingComponent(managingTask); - if (outputFilterChains != null && outputFilterChains.size() > 0) { - ChainReaderHelper helper = new ChainReaderHelper(); - helper.setProject(managingTask.getProject()); - helper.setPrimaryReader(reader); - helper.setFilterChains(outputFilterChains); - reader = helper.getAssembledReader(); - } - outPumpIn = new ReaderInputStream(reader, outputEncoding); + InputStream outPumpIn = snk; - Thread t = new Thread(threadGroup, new StreamPumper( - outPumpIn, outputStream, true), "output pumper"); - t.setPriority(Thread.MAX_PRIORITY); - outputStream = new PipedOutputStream(snk); - t.start(); - } catch (IOException eyeOhEx) { - throw new BuildException( - "error setting up output stream", eyeOhEx); + Reader reader = new InputStreamReader(outPumpIn, + inputEncoding); + + if (outputFilterChains != null + && outputFilterChains.size() > 0) { + ChainReaderHelper helper = new ChainReaderHelper(); + helper.setProject(managingTask.getProject()); + helper.setPrimaryReader(reader); + helper.setFilterChains(outputFilterChains); + reader = helper.getAssembledReader(); + } + outPumpIn = new ReaderInputStream(reader, outputEncoding); + + Thread t = new Thread(threadGroup, new StreamPumper( + outPumpIn, outputStream, true), "output pumper"); + t.setPriority(Thread.MAX_PRIORITY); + outputStream = new PipedOutputStream(snk); + t.start(); + } catch (IOException eyeOhEx) { + throw new BuildException("error setting up output stream", + eyeOhEx); + } } } - if ((errorFilterChains != null && errorFilterChains.size() > 0) - || !(errorEncoding.equalsIgnoreCase(inputEncoding))) { - try { - LeadPipeInputStream snk = new LeadPipeInputStream(); - snk.setManagingComponent(managingTask); + synchronized (errMutex) { + errorStreams(); + if (alwaysLogErr || errorStream == null) { + OutputStream errorLog = new LogOutputStream(managingTask, + Project.MSG_WARN); + errorStream = (errorStream == null) ? errorLog + : new TeeOutputStream(errorLog, errorStream); + } - InputStream errPumpIn = snk; + if ((errorFilterChains != null && errorFilterChains.size() > 0) + || !(errorEncoding.equalsIgnoreCase(inputEncoding))) { + try { + LeadPipeInputStream snk = new LeadPipeInputStream(); + snk.setManagingComponent(managingTask); - Reader reader = new InputStreamReader(errPumpIn, inputEncoding); + InputStream errPumpIn = snk; - if (errorFilterChains != null && errorFilterChains.size() > 0) { - ChainReaderHelper helper = new ChainReaderHelper(); - helper.setProject(managingTask.getProject()); - helper.setPrimaryReader(reader); - helper.setFilterChains(errorFilterChains); - reader = helper.getAssembledReader(); - } - errPumpIn = new ReaderInputStream(reader, errorEncoding); + Reader reader = new InputStreamReader(errPumpIn, + inputEncoding); - Thread t = new Thread(threadGroup, new StreamPumper( - errPumpIn, errorStream, true), "error pumper"); - t.setPriority(Thread.MAX_PRIORITY); - errorStream = new PipedOutputStream(snk); - t.start(); - } catch (IOException eyeOhEx) { - throw new BuildException( - "error setting up error stream", eyeOhEx); + if (errorFilterChains != null + && errorFilterChains.size() > 0) { + ChainReaderHelper helper = new ChainReaderHelper(); + helper.setProject(managingTask.getProject()); + helper.setPrimaryReader(reader); + helper.setFilterChains(errorFilterChains); + reader = helper.getAssembledReader(); + } + errPumpIn = new ReaderInputStream(reader, errorEncoding); + + Thread t = new Thread(threadGroup, new StreamPumper( + errPumpIn, errorStream, true), "error pumper"); + t.setPriority(Thread.MAX_PRIORITY); + errorStream = new PipedOutputStream(snk); + t.start(); + } catch (IOException eyeOhEx) { + throw new BuildException("error setting up error stream", + eyeOhEx); + } } } - // 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 (IOException eyeOhEx) { - throw new BuildException(eyeOhEx); - } - ((ConcatFileInputStream) inputStream).setManagingComponent(managingTask); - } else if (inputString != null) { - StringBuffer buf = new StringBuffer("Using input "); - if (logInputString) { - buf.append('"').append(inputString).append('"'); - } else { - buf.append("string"); + 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 (IOException eyeOhEx) { + throw new BuildException(eyeOhEx); + } + ((ConcatFileInputStream) inputStream) + .setManagingComponent(managingTask); + } else if (inputString != null) { + 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()); } - managingTask.log(buf.toString(), Project.MSG_VERBOSE); - inputStream = new ByteArrayInputStream(inputString.getBytes()); - } - if (inputStream != null - && inputFilterChains != null && inputFilterChains.size() > 0) { - ChainReaderHelper helper = new ChainReaderHelper(); - helper.setProject(managingTask.getProject()); - try { - helper.setPrimaryReader( - new InputStreamReader(inputStream, inputEncoding)); - } catch (IOException eyeOhEx) { - throw new BuildException( - "error setting up input stream", eyeOhEx); + if (inputStream != null && inputFilterChains != null + && inputFilterChains.size() > 0) { + ChainReaderHelper helper = new ChainReaderHelper(); + helper.setProject(managingTask.getProject()); + try { + helper.setPrimaryReader(new InputStreamReader(inputStream, + inputEncoding)); + } catch (IOException eyeOhEx) { + throw new BuildException("error setting up input stream", + eyeOhEx); + } + helper.setFilterChains(inputFilterChains); + inputStream = new ReaderInputStream( + helper.getAssembledReader(), inputEncoding); } - helper.setFilterChains(inputFilterChains); - inputStream = new ReaderInputStream( - helper.getAssembledReader(), inputEncoding); } } @@ -577,20 +688,21 @@ public class Redirector { private void outStreams() { if (out != null && out.length > 0) { String logHead = new StringBuffer("Output ").append( - ((append) ? "appended" : "redirected")).append( - " to ").toString(); - outputStream = foldFiles(out, logHead, Project.MSG_VERBOSE); + ((appendOut) ? "appended" : "redirected")).append(" to ") + .toString(); + 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); + + outputProperty, Project.MSG_VERBOSE); } - //shield it from being closed by a filtering StreamPumper + // shield it from being closed by a filtering StreamPumper OutputStream keepAliveOutput = new KeepAliveOutputStream(baos); outputStream = (outputStream == null) ? keepAliveOutput - : new TeeOutputStream(outputStream, keepAliveOutput); + : new TeeOutputStream(outputStream, keepAliveOutput); } else { baos = null; } @@ -599,31 +711,32 @@ public class Redirector { private void errorStreams() { if (error != null && error.length > 0) { String logHead = new StringBuffer("Error ").append( - ((append) ? "appended" : "redirected")).append( - " to ").toString(); - errorStream = foldFiles(error, logHead, Project.MSG_VERBOSE); + ((appendErr) ? "appended" : "redirected")).append(" to ") + .toString(); + errorStream = foldFiles(error, logHead, Project.MSG_VERBOSE, + appendErr, createEmptyFilesErr); } else if (!(logError || outputStream == null)) { long funnelTimeout = 0L; - OutputStreamFunneler funneler - = new OutputStreamFunneler(outputStream, funnelTimeout); + OutputStreamFunneler funneler = new OutputStreamFunneler( + outputStream, funnelTimeout); try { outputStream = funneler.getFunnelInstance(); errorStream = funneler.getFunnelInstance(); } catch (IOException eyeOhEx) { throw new BuildException( - "error splitting output/error streams", eyeOhEx); + "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); + managingTask.log("Error redirected to property: " + + errorProperty, Project.MSG_VERBOSE); } - //shield it from being closed by a filtering StreamPumper + // shield it from being closed by a filtering StreamPumper OutputStream keepAliveError = new KeepAliveOutputStream(errorBaos); errorStream = (error == null || error.length == 0) ? keepAliveError - : new TeeOutputStream(errorStream, keepAliveError); + : new TeeOutputStream(errorStream, keepAliveError); } else { errorBaos = null; } @@ -631,198 +744,245 @@ public class Redirector { /** * 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 synchronized ExecuteStreamHandler createHandler() - throws BuildException { + * + * @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(); - return new PumpStreamHandler(outputStream, errorStream, inputStream); + return new PumpStreamHandler(getOutputStream(), getErrorStream(), + getInputStream()); + } /** * Pass output sent to System.out to specified output. - * - * @param output the data to be output - */ - protected synchronized void handleOutput(String output) { - if (outPrintStream == null) { - outPrintStream = new PrintStream(outputStream); + * + * @param output + * the data to be output + */ + protected void handleOutput(String output) { + synchronized (outMutex) { + if (outPrintStream == null) { + outPrintStream = new PrintStream(outputStream); + } + outPrintStream.print(output); } - 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 - * + * + * @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 synchronized int handleInput(byte[] buffer, int offset, - int length) throws IOException { - if (inputStream == null) { - return managingTask.getProject().defaultInput(buffer, offset, - length); - } else { + * + * @exception IOException + * if the data cannot be read + */ + protected int handleInput(byte[] buffer, int offset, 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 synchronized void handleFlush(String output) { - if (outPrintStream == null) { - outPrintStream = new PrintStream(outputStream); + * + * @param output + * the data being flushed. + */ + protected void handleFlush(String output) { + synchronized (outMutex) { + if (outPrintStream == null) { + outPrintStream = new PrintStream(outputStream); + } + outPrintStream.print(output); + outPrintStream.flush(); } - outPrintStream.print(output); - outPrintStream.flush(); } /** * Process error output - * - * @param output the error output data. - */ - protected synchronized void handleErrorOutput(String output) { - if (errorPrintStream == null) { - errorPrintStream = new PrintStream(errorStream); + * + * @param output + * the error output data. + */ + protected void handleErrorOutput(String output) { + synchronized (errMutex) { + if (errorPrintStream == null) { + errorPrintStream = new PrintStream(errorStream); + } + errorPrintStream.print(output); } - errorPrintStream.print(output); } /** * Handle a flush operation on the error stream - * - * @param output the error information being flushed. - */ - protected synchronized void handleErrorFlush(String output) { - if (errorPrintStream == null) { - errorPrintStream = new PrintStream(errorStream); + * + * @param output + * the error information being flushed. + */ + protected void handleErrorFlush(String output) { + synchronized (errMutex) { + if (errorPrintStream == null) { + errorPrintStream = new PrintStream(errorStream); + } + errorPrintStream.print(output); } - errorPrintStream.print(output); } /** * Get the output stream for the redirector - * - * @return the redirector's output stream or null if no output - * has been configured + * + * @return the redirector's output stream or null if no output has been + * configured */ - public synchronized OutputStream getOutputStream() { - return outputStream; + 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 + * + * @return the redirector's error stream or null if no output has been + * configured */ - public synchronized OutputStream getErrorStream() { - return errorStream; + 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 + * + * @return the redirector's input stream or null if no output has been + * configured */ - public synchronized InputStream getInputStream() { - return inputStream; + 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 synchronized void complete() throws IOException { + * + * 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(); - if (inputStream != null) { - inputStream.close(); + synchronized (inMutex) { + if (inputStream != null) { + inputStream.close(); + } } - outputStream.flush(); - outputStream.close(); + synchronized (outMutex) { + outputStream.flush(); + outputStream.close(); + } - errorStream.flush(); - errorStream.close(); + synchronized (errMutex) { + errorStream.flush(); + errorStream.close(); + } - //wait for the StreamPumpers to finish - while (threadGroup.activeCount() > 0) { - try { - managingTask.log("waiting for " + threadGroup.activeCount() - + " Threads:", Project.MSG_DEBUG); - 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 (NullPointerException enPeaEx) { - // Ignore exception + // wait for the StreamPumpers to finish + synchronized (this) { + while (threadGroup.activeCount() > 0) { + try { + managingTask.log("waiting for " + threadGroup.activeCount() + + " Threads:", Project.MSG_DEBUG); + 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 (NullPointerException enPeaEx) { + // Ignore exception + } + } + wait(STREAMPUMPER_WAIT_INTERVAL); + } catch (InterruptedException eyeEx) { + Thread[] thread = new Thread[threadGroup.activeCount()]; + threadGroup.enumerate(thread); + for (int i = 0; i < thread.length && thread[i] != null; i++) { + thread[i].interrupt(); } - } - wait(STREAMPUMPER_WAIT_INTERVAL); - } catch (InterruptedException eyeEx) { - Thread[] thread = new Thread[threadGroup.activeCount()]; - threadGroup.enumerate(thread); - for (int i = 0; i < thread.length && thread[i] != null; i++) { - thread[i].interrupt(); } } } setProperties(); - inputStream = null; - outputStream = null; - errorStream = null; - outPrintStream = null; - errorPrintStream = null; - } + synchronized (inMutex) { + inputStream = null; + } + synchronized (outMutex) { + outputStream = null; + outPrintStream = null; + } + synchronized (errMutex) { + errorStream = null; + errorPrintStream = null; + } + } /** - * Notify the Redirector that it is now okay - * to set any output and/or error properties. + * Notify the Redirector that it is now okay to set any output + * and/or error properties. */ - public synchronized void setProperties() { - if (baos != null) { - try { - baos.close(); - } catch (IOException eyeOhEx) { - // Ignore exception + public void setProperties() { + synchronized (outMutex) { + if (baos != null) { + try { + baos.close(); + } catch (IOException eyeOhEx) { + // Ignore exception + } } } - if (errorBaos != null) { - try { - errorBaos.close(); - } catch (IOException eyeOhEx) { - // Ignore exception + synchronized (errMutex) { + if (errorBaos != null) { + try { + errorBaos.close(); + } catch (IOException eyeOhEx) { + // Ignore exception + } } } } - private OutputStream foldFiles(File[] file, String logHead, int loglevel) { - OutputStream result - = new LazyFileOutputStream(file[0], append, createEmptyFiles); + private OutputStream foldFiles(File[] file, String logHead, int loglevel, + boolean append, boolean createEmptyFiles) { + OutputStream result = new LazyFileOutputStream(file[0], append, + createEmptyFiles); managingTask.log(logHead + file[0], loglevel); char[] c = new char[logHead.length()]; @@ -831,7 +991,7 @@ public class Redirector { for (int i = 1; i < file.length; i++) { outputStream = new TeeOutputStream(outputStream, - new LazyFileOutputStream(file[i], append, createEmptyFiles)); + new LazyFileOutputStream(file[i], append, createEmptyFiles)); managingTask.log(indent + file[i], loglevel); } return result;