1. /*
  2. * Copyright 2003-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. *
  16. */
  17. package org.apache.tools.ant.taskdefs;
  18. import java.io.File;
  19. import java.io.Reader;
  20. import java.io.InputStream;
  21. import java.io.IOException;
  22. import java.io.PrintStream;
  23. import java.io.OutputStream;
  24. import java.io.StringReader;
  25. import java.io.BufferedReader;
  26. import java.io.FileInputStream;
  27. import java.io.PipedInputStream;
  28. import java.io.InputStreamReader;
  29. import java.io.PipedOutputStream;
  30. import java.io.OutputStreamWriter;
  31. import java.io.BufferedInputStream;
  32. import java.io.ByteArrayInputStream;
  33. import java.io.ByteArrayOutputStream;
  34. import java.io.FileNotFoundException;
  35. import java.util.Arrays;
  36. import java.util.Vector;
  37. import org.apache.tools.ant.Task;
  38. import org.apache.tools.ant.Project;
  39. import org.apache.tools.ant.BuildException;
  40. import org.apache.tools.ant.filters.util.ChainReaderHelper;
  41. import org.apache.tools.ant.util.StringUtils;
  42. import org.apache.tools.ant.util.TeeOutputStream;
  43. import org.apache.tools.ant.util.ReaderInputStream;
  44. import org.apache.tools.ant.util.LeadPipeInputStream;
  45. import org.apache.tools.ant.util.LazyFileOutputStream;
  46. import org.apache.tools.ant.util.OutputStreamFunneler;
  47. import org.apache.tools.ant.util.ConcatFileInputStream;
  48. import org.apache.tools.ant.util.KeepAliveOutputStream;
  49. /**
  50. * The Redirector class manages the setup and connection of
  51. * input and output redirection for an Ant task.
  52. *
  53. * @since Ant 1.6
  54. */
  55. public class Redirector {
  56. private static final String defaultEncoding
  57. = System.getProperty("file.encoding");
  58. private class PropertyOutputStream extends ByteArrayOutputStream {
  59. String property;
  60. boolean closed = false;
  61. PropertyOutputStream(String property) {
  62. super();
  63. this.property = property;
  64. }
  65. public void close() throws IOException {
  66. if (!closed && !(append && appendProperties)) {
  67. setPropertyFromBAOS(this, property);
  68. closed = true;
  69. }
  70. }
  71. }
  72. /**
  73. * The file(s) from which standard input is being taken.
  74. * If > 1, files' content will be concatenated in the order received.
  75. */
  76. private File[] input;
  77. /**
  78. * The file(s) receiving standard output. Will also receive standard error
  79. * unless standard error is redirected or logError is true.
  80. */
  81. private File[] out;
  82. /**
  83. * The file(s) to which standard error is being redirected
  84. */
  85. private File[] error;
  86. /**
  87. * Indicates if standard error should be logged to Ant's log system
  88. * rather than the output. This has no effect if standard error is
  89. * redirected to a file or property.
  90. */
  91. private boolean logError = false;
  92. /**
  93. * Buffer used to capture output for storage into a property
  94. */
  95. private PropertyOutputStream baos = null;
  96. /**
  97. * Buffer used to capture error output for storage into a property
  98. */
  99. private PropertyOutputStream errorBaos = null;
  100. /** The name of the property into which output is to be stored */
  101. private String outputProperty;
  102. /** The name of the property into which error output is to be stored */
  103. private String errorProperty;
  104. /** String from which input is taken */
  105. private String inputString;
  106. /** Flag which indicates if error and output files are to be appended. */
  107. private boolean append = false;
  108. /** Flag which indicates whether files should be created even when empty. */
  109. private boolean createEmptyFiles = true;
  110. /** The task for which this redirector is working */
  111. private Task managingTask;
  112. /** The stream for output data */
  113. private OutputStream outputStream = null;
  114. /** The stream for error output */
  115. private OutputStream errorStream = null;
  116. /** The stream for input */
  117. private InputStream inputStream = null;
  118. /** Stream which is used for line oriented output */
  119. private PrintStream outPrintStream = null;
  120. /** Stream which is used for line oriented error output */
  121. private PrintStream errorPrintStream = null;
  122. /** The output filter chains */
  123. private Vector outputFilterChains;
  124. /** The error filter chains */
  125. private Vector errorFilterChains;
  126. /** The input filter chains */
  127. private Vector inputFilterChains;
  128. /** The output encoding */
  129. private String outputEncoding = defaultEncoding;
  130. /** The error encoding */
  131. private String errorEncoding = defaultEncoding;
  132. /** The input encoding */
  133. private String inputEncoding = defaultEncoding;
  134. /** Whether to complete properties settings **/
  135. private boolean appendProperties = true;
  136. /** The thread group used for starting <code>StreamPumper</code> threads */
  137. private ThreadGroup threadGroup = new ThreadGroup("redirector");
  138. /**
  139. * Create a redirector instance for the given task
  140. *
  141. * @param managingTask the task for which the redirector is to work
  142. */
  143. public Redirector(Task managingTask) {
  144. this.managingTask = managingTask;
  145. }
  146. /**
  147. * Set the input to use for the task
  148. *
  149. * @param input the file from which input is read.
  150. */
  151. public void setInput(File input) {
  152. setInput((input == null) ? null : new File[] {input});
  153. }
  154. /**
  155. * Set the input to use for the task
  156. *
  157. * @param input the files from which input is read.
  158. */
  159. public synchronized void setInput(File[] input) {
  160. this.input = input;
  161. }
  162. /**
  163. * Set the string to use as input
  164. *
  165. * @param inputString the string which is used as the input source
  166. */
  167. public synchronized void setInputString(String inputString) {
  168. this.inputString = inputString;
  169. }
  170. /**
  171. * File the output of the process is redirected to. If error is not
  172. * redirected, it too will appear in the output
  173. *
  174. * @param out the file to which output stream is written
  175. */
  176. public void setOutput(File out) {
  177. setOutput((out == null) ? null : new File[] {out});
  178. }
  179. /**
  180. * Files the output of the process is redirected to. If error is not
  181. * redirected, it too will appear in the output
  182. *
  183. * @param out the files to which output stream is written
  184. */
  185. public synchronized void setOutput(File[] out) {
  186. this.out = out;
  187. }
  188. /**
  189. * Set the output encoding.
  190. *
  191. * @param outputEncoding <CODE>String</CODE>.
  192. */
  193. public synchronized void setOutputEncoding(String outputEncoding) {
  194. if (outputEncoding == null) {
  195. throw new IllegalArgumentException(
  196. "outputEncoding must not be null");
  197. } else {
  198. this.outputEncoding = outputEncoding;
  199. }
  200. }
  201. /**
  202. * Set the error encoding.
  203. *
  204. * @param errorEncoding <CODE>String</CODE>.
  205. */
  206. public synchronized void setErrorEncoding(String errorEncoding) {
  207. if (errorEncoding == null) {
  208. throw new IllegalArgumentException(
  209. "errorEncoding must not be null");
  210. } else {
  211. this.errorEncoding = errorEncoding;
  212. }
  213. }
  214. /**
  215. * Set the input encoding.
  216. *
  217. * @param inputEncoding <CODE>String</CODE>.
  218. */
  219. public synchronized void setInputEncoding(String inputEncoding) {
  220. if (inputEncoding == null) {
  221. throw new IllegalArgumentException(
  222. "inputEncoding must not be null");
  223. } else {
  224. this.inputEncoding = inputEncoding;
  225. }
  226. }
  227. /**
  228. * Controls whether error output of exec is logged. This is only useful
  229. * when output is being redirected and error output is desired in the
  230. * Ant log
  231. *
  232. * @param logError if true the standard error is sent to the Ant log system
  233. * and not sent to output.
  234. */
  235. public synchronized void setLogError(boolean logError) {
  236. this.logError = logError;
  237. }
  238. /**
  239. * This <CODE>Redirector</CODE>'s subordinate
  240. * <CODE>PropertyOutputStream</CODE>s will not set their respective
  241. * properties <CODE>while (appendProperties && append)</CODE>.
  242. *
  243. * @param appendProperties whether to append properties.
  244. */
  245. public synchronized void setAppendProperties(boolean appendProperties) {
  246. this.appendProperties = appendProperties;
  247. }
  248. /**
  249. * Set the file to which standard error is to be redirected.
  250. *
  251. * @param error the file to which error is to be written
  252. */
  253. public void setError(File error) {
  254. setError((error == null) ? null : new File[] {error});
  255. }
  256. /**
  257. * Set the files to which standard error is to be redirected.
  258. *
  259. * @param error the file to which error is to be written
  260. */
  261. public synchronized void setError(File[] error) {
  262. this.error = error;
  263. }
  264. /**
  265. * Property name whose value should be set to the output of
  266. * the process.
  267. *
  268. * @param outputProperty the name of the property to be set with the
  269. * task's output.
  270. */
  271. public synchronized void setOutputProperty(String outputProperty) {
  272. if (outputProperty == null
  273. || !(outputProperty.equals(this.outputProperty))) {
  274. this.outputProperty = outputProperty;
  275. baos = null;
  276. }
  277. }
  278. /**
  279. * Whether output should be appended to or overwrite an existing file.
  280. * Defaults to false.
  281. *
  282. * @param append if true output and error streams are appended to their
  283. * respective files, if specified.
  284. */
  285. public synchronized void setAppend(boolean append) {
  286. this.append = append;
  287. }
  288. /**
  289. * Whether output and error files should be created even when empty.
  290. * Defaults to true.
  291. * @param createEmptyFiles <CODE>boolean</CODE>.
  292. */
  293. public void setCreateEmptyFiles(boolean createEmptyFiles) {
  294. this.createEmptyFiles = createEmptyFiles;
  295. }
  296. /**
  297. * Property name whose value should be set to the error of
  298. * the process.
  299. *
  300. * @param errorProperty the name of the property to be set
  301. * with the error output.
  302. */
  303. public synchronized void setErrorProperty(String errorProperty) {
  304. if (errorProperty == null
  305. || !(errorProperty.equals(this.errorProperty))) {
  306. this.errorProperty = errorProperty;
  307. errorBaos = null;
  308. }
  309. }
  310. /**
  311. * Set the input <CODE>FilterChain</CODE>s.
  312. *
  313. * @param inputFilterChains <CODE>Vector</CODE> containing <CODE>FilterChain</CODE>.
  314. */
  315. public synchronized void setInputFilterChains(Vector inputFilterChains) {
  316. this.inputFilterChains = inputFilterChains;
  317. }
  318. /**
  319. * Set the output <CODE>FilterChain</CODE>s.
  320. *
  321. * @param outputFilterChains <CODE>Vector</CODE> containing <CODE>FilterChain</CODE>.
  322. */
  323. public void setOutputFilterChains(Vector outputFilterChains) {
  324. this.outputFilterChains = outputFilterChains;
  325. }
  326. /**
  327. * Set the error <CODE>FilterChain</CODE>s.
  328. *
  329. * @param errorFilterChains <CODE>Vector</CODE> containing <CODE>FilterChain</CODE>.
  330. */
  331. public void setErrorFilterChains(Vector errorFilterChains) {
  332. this.errorFilterChains = errorFilterChains;
  333. }
  334. /**
  335. * Set a property from a ByteArrayOutputStream
  336. *
  337. * @param baos contains the property value.
  338. * @param propertyName the property name.
  339. *
  340. * @exception IOException if the value cannot be read form the stream.
  341. */
  342. private void setPropertyFromBAOS(ByteArrayOutputStream baos,
  343. String propertyName) throws IOException {
  344. BufferedReader in
  345. = new BufferedReader(new StringReader(Execute.toString(baos)));
  346. String line = null;
  347. StringBuffer val = new StringBuffer();
  348. while ((line = in.readLine()) != null) {
  349. if (val.length() != 0) {
  350. val.append(StringUtils.LINE_SEP);
  351. }
  352. val.append(line);
  353. }
  354. managingTask.getProject().setNewProperty(propertyName, val.toString());
  355. }
  356. /**
  357. * Create the input, error and output streams based on the
  358. * configuration options.
  359. */
  360. public synchronized void createStreams() {
  361. if ((out == null || out.length == 0) && outputProperty == null) {
  362. outputStream = new LogOutputStream(managingTask, Project.MSG_INFO);
  363. } else {
  364. if (out != null && out.length > 0) {
  365. String logHead = new StringBuffer("Output ").append(
  366. ((append) ? "appended" : "redirected")).append(
  367. " to ").toString();
  368. outputStream = foldFiles(out, logHead, Project.MSG_VERBOSE);
  369. }
  370. if (outputProperty != null) {
  371. if (baos == null) {
  372. baos = new PropertyOutputStream(outputProperty);
  373. managingTask.log("Output redirected to property: "
  374. + outputProperty, Project.MSG_VERBOSE);
  375. }
  376. //shield it from being closed by a filtering StreamPumper
  377. OutputStream keepAliveOutput = new KeepAliveOutputStream(baos);
  378. if (outputStream == null) {
  379. outputStream = keepAliveOutput;
  380. } else {
  381. outputStream
  382. = new TeeOutputStream(outputStream, keepAliveOutput);
  383. }
  384. } else {
  385. baos = null;
  386. }
  387. errorStream = outputStream;
  388. }
  389. if (error != null && error.length > 0) {
  390. String logHead = new StringBuffer("Error ").append(
  391. ((append) ? "appended" : "redirected")).append(
  392. " to ").toString();
  393. errorStream = foldFiles(error, logHead, Project.MSG_VERBOSE);
  394. } else if (logError || errorStream == null) {
  395. errorStream = new LogOutputStream(managingTask, Project.MSG_WARN);
  396. } else { //must be errorStream == outputStream
  397. long funnelTimeout = 0L;
  398. OutputStreamFunneler funneler
  399. = new OutputStreamFunneler(outputStream, funnelTimeout);
  400. try {
  401. outputStream = funneler.getFunnelInstance();
  402. errorStream = funneler.getFunnelInstance();
  403. } catch (IOException eyeOhEx) {
  404. throw new BuildException(
  405. "error splitting output/error streams", eyeOhEx);
  406. }
  407. }
  408. if (errorProperty != null) {
  409. if (errorBaos == null) {
  410. errorBaos = new PropertyOutputStream(errorProperty);
  411. managingTask.log("Error redirected to property: " + errorProperty,
  412. Project.MSG_VERBOSE);
  413. }
  414. //shield it from being closed by a filtering StreamPumper
  415. OutputStream keepAliveError = new KeepAliveOutputStream(errorBaos);
  416. errorStream = (error == null || error.length == 0) ? keepAliveError
  417. : new TeeOutputStream(errorStream, keepAliveError);
  418. } else {
  419. errorBaos = null;
  420. }
  421. if ((outputFilterChains != null && outputFilterChains.size() > 0)
  422. || !(outputEncoding.equalsIgnoreCase(inputEncoding))) {
  423. try {
  424. LeadPipeInputStream snk = new LeadPipeInputStream();
  425. snk.setManagingTask(managingTask);
  426. InputStream outPumpIn = snk;
  427. Reader reader = new InputStreamReader(outPumpIn, inputEncoding);
  428. if (outputFilterChains != null && outputFilterChains.size() > 0) {
  429. ChainReaderHelper helper = new ChainReaderHelper();
  430. helper.setPrimaryReader(reader);
  431. helper.setFilterChains(outputFilterChains);
  432. reader = helper.getAssembledReader();
  433. }
  434. outPumpIn = new ReaderInputStream(reader, outputEncoding);
  435. Thread t = new Thread(threadGroup, new StreamPumper(
  436. outPumpIn, outputStream, true), "output pumper");
  437. t.setPriority(Thread.MAX_PRIORITY);
  438. outputStream = new PipedOutputStream(snk);
  439. t.start();
  440. } catch (IOException eyeOhEx) {
  441. throw new BuildException(
  442. "error setting up output stream", eyeOhEx);
  443. }
  444. }
  445. if ((errorFilterChains != null && errorFilterChains.size() > 0)
  446. || !(errorEncoding.equalsIgnoreCase(inputEncoding))) {
  447. try {
  448. LeadPipeInputStream snk = new LeadPipeInputStream();
  449. snk.setManagingTask(managingTask);
  450. InputStream errPumpIn = snk;
  451. Reader reader = new InputStreamReader(errPumpIn, inputEncoding);
  452. if (errorFilterChains != null && errorFilterChains.size() > 0) {
  453. ChainReaderHelper helper = new ChainReaderHelper();
  454. helper.setPrimaryReader(reader);
  455. helper.setFilterChains(errorFilterChains);
  456. reader = helper.getAssembledReader();
  457. }
  458. errPumpIn = new ReaderInputStream(reader, errorEncoding);
  459. Thread t = new Thread(threadGroup, new StreamPumper(
  460. errPumpIn, errorStream, true), "error pumper");
  461. t.setPriority(Thread.MAX_PRIORITY);
  462. errorStream = new PipedOutputStream(snk);
  463. t.start();
  464. } catch (IOException eyeOhEx) {
  465. throw new BuildException(
  466. "error setting up error stream", eyeOhEx);
  467. }
  468. }
  469. // if input files are specified, inputString is ignored;
  470. // classes that work with redirector attributes can enforce
  471. // whatever warnings are needed
  472. if (input != null && input.length > 0) {
  473. managingTask.log("Redirecting input from file"
  474. + ((input.length == 1) ? "" : "s"), Project.MSG_VERBOSE);
  475. try {
  476. inputStream = new ConcatFileInputStream(input);
  477. } catch (IOException eyeOhEx) {
  478. throw new BuildException(eyeOhEx);
  479. }
  480. ((ConcatFileInputStream)inputStream).setManagingTask(managingTask);
  481. } else if (inputString != null) {
  482. managingTask.log("Using input \"" + inputString + "\"",
  483. Project.MSG_VERBOSE);
  484. inputStream = new ByteArrayInputStream(inputString.getBytes());
  485. }
  486. if (inputStream != null
  487. && inputFilterChains != null && inputFilterChains.size() > 0) {
  488. ChainReaderHelper helper = new ChainReaderHelper();
  489. try {
  490. helper.setPrimaryReader(
  491. new InputStreamReader(inputStream, inputEncoding));
  492. } catch (IOException eyeOhEx) {
  493. throw new BuildException(
  494. "error setting up input stream", eyeOhEx);
  495. }
  496. helper.setFilterChains(inputFilterChains);
  497. inputStream = new ReaderInputStream(
  498. helper.getAssembledReader(), inputEncoding);
  499. }
  500. }
  501. /**
  502. * Create the StreamHandler to use with our Execute instance.
  503. *
  504. * @return the execute stream handler to manage the input, output and
  505. * error streams.
  506. *
  507. * @throws BuildException if the execute stream handler cannot be created.
  508. */
  509. public synchronized ExecuteStreamHandler createHandler()
  510. throws BuildException {
  511. createStreams();
  512. return new PumpStreamHandler(outputStream, errorStream, inputStream);
  513. }
  514. /**
  515. * Pass output sent to System.out to specified output.
  516. *
  517. * @param output the data to be output
  518. */
  519. protected synchronized void handleOutput(String output) {
  520. if (outPrintStream == null) {
  521. outPrintStream = new PrintStream(outputStream);
  522. }
  523. outPrintStream.print(output);
  524. }
  525. /**
  526. * Handle an input request
  527. *
  528. * @param buffer the buffer into which data is to be read.
  529. * @param offset the offset into the buffer at which data is stored.
  530. * @param length the amount of data to read
  531. *
  532. * @return the number of bytes read
  533. *
  534. * @exception IOException if the data cannot be read
  535. */
  536. protected synchronized int handleInput(byte[] buffer, int offset,
  537. int length) throws IOException {
  538. if (inputStream == null) {
  539. return managingTask.getProject().defaultInput(buffer, offset,
  540. length);
  541. } else {
  542. return inputStream.read(buffer, offset, length);
  543. }
  544. }
  545. /**
  546. * Process data due to a flush operation.
  547. *
  548. * @param output the data being flushed.
  549. */
  550. protected synchronized void handleFlush(String output) {
  551. if (outPrintStream == null) {
  552. outPrintStream = new PrintStream(outputStream);
  553. }
  554. outPrintStream.print(output);
  555. outPrintStream.flush();
  556. }
  557. /**
  558. * Process error output
  559. *
  560. * @param output the error output data.
  561. */
  562. protected synchronized void handleErrorOutput(String output) {
  563. if (errorPrintStream == null) {
  564. errorPrintStream = new PrintStream(errorStream);
  565. }
  566. errorPrintStream.print(output);
  567. }
  568. /**
  569. * Handle a flush operation on the error stream
  570. *
  571. * @param output the error information being flushed.
  572. */
  573. protected synchronized void handleErrorFlush(String output) {
  574. if (errorPrintStream == null) {
  575. errorPrintStream = new PrintStream(errorStream);
  576. }
  577. errorPrintStream.print(output);
  578. }
  579. /**
  580. * Get the output stream for the redirector
  581. *
  582. * @return the redirector's output stream or null if no output
  583. * has been configured
  584. */
  585. public synchronized OutputStream getOutputStream() {
  586. return outputStream;
  587. }
  588. /**
  589. * Get the error stream for the redirector
  590. *
  591. * @return the redirector's error stream or null if no output
  592. * has been configured
  593. */
  594. public synchronized OutputStream getErrorStream() {
  595. return errorStream;
  596. }
  597. /**
  598. * Get the input stream for the redirector
  599. *
  600. * @return the redirector's input stream or null if no output
  601. * has been configured
  602. */
  603. public synchronized InputStream getInputStream() {
  604. return inputStream;
  605. }
  606. /**
  607. * Complete redirection.
  608. *
  609. * This operation will close any streams and create any specified
  610. * property values.
  611. *
  612. * @throws IOException if the output properties cannot be read from their
  613. * output streams.
  614. */
  615. public synchronized void complete() throws IOException {
  616. System.out.flush();
  617. System.err.flush();
  618. if (inputStream != null) {
  619. inputStream.close();
  620. }
  621. outputStream.flush();
  622. outputStream.close();
  623. errorStream.flush();
  624. errorStream.close();
  625. //wait for the StreamPumpers to finish
  626. while (threadGroup.activeCount() > 0) {
  627. try {
  628. managingTask.log("waiting for " + threadGroup.activeCount()
  629. + " Threads:", Project.MSG_DEBUG);
  630. Thread[] thread = new Thread[threadGroup.activeCount()];
  631. threadGroup.enumerate(thread);
  632. for (int i = 0; i < thread.length && thread[i] != null; i++) {
  633. try {
  634. managingTask.log(thread[i].toString(), Project.MSG_DEBUG);
  635. } catch (NullPointerException enPeaEx) {
  636. }
  637. }
  638. Thread.sleep(1000);
  639. } catch (InterruptedException eyeEx) {
  640. }
  641. }
  642. setProperties();
  643. inputStream = null;
  644. outputStream = errorStream = outPrintStream = errorPrintStream = null;
  645. }
  646. /**
  647. * Notify the <CODE>Redirector</CODE> that it is now okay
  648. * to set any output and/or error properties.
  649. */
  650. public synchronized void setProperties() {
  651. if (baos != null) {
  652. try {
  653. baos.close();
  654. } catch (IOException eyeOhEx) {
  655. }
  656. }
  657. if (errorBaos != null) {
  658. try {
  659. errorBaos.close();
  660. } catch (IOException eyeOhEx) {
  661. }
  662. }
  663. }
  664. private OutputStream foldFiles(File[] file, String logHead, int loglevel) {
  665. OutputStream result
  666. = new LazyFileOutputStream(file[0], append, createEmptyFiles);
  667. managingTask.log(logHead + file[0], loglevel);
  668. char[] c = new char[logHead.length()];
  669. Arrays.fill(c, ' ');
  670. String indent = new String(c);
  671. for (int i = 1; i < file.length ; i++) {
  672. outputStream = new TeeOutputStream(outputStream,
  673. new LazyFileOutputStream(file[i], append, createEmptyFiles));
  674. managingTask.log(indent + file[i], loglevel);
  675. }
  676. return result;
  677. }
  678. }