1. /*
  2. * Copyright 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.util;
  18. import java.io.IOException;
  19. import java.io.OutputStream;
  20. import java.io.FilterOutputStream;
  21. /**
  22. * Manages a set of <CODE>OutputStream</CODE>s to
  23. * write to a single underlying stream, which is
  24. * closed only when the last "funnel"
  25. * has been closed.
  26. */
  27. public class OutputStreamFunneler {
  28. /**
  29. * Default timeout.
  30. * @see #setTimeout()
  31. */
  32. public static final long DEFAULT_TIMEOUT_MILLIS = 1000;
  33. private class Funnel extends OutputStream {
  34. private boolean closed = false;
  35. private Funnel() {
  36. synchronized (OutputStreamFunneler.this) {
  37. ++count;
  38. }
  39. }
  40. public void flush() throws IOException {
  41. synchronized (OutputStreamFunneler.this) {
  42. dieIfClosed();
  43. out.flush();
  44. }
  45. }
  46. public void write(int b) throws IOException {
  47. synchronized (OutputStreamFunneler.this) {
  48. dieIfClosed();
  49. out.write(b);
  50. }
  51. }
  52. public void write(byte[] b) throws IOException {
  53. synchronized (OutputStreamFunneler.this) {
  54. dieIfClosed();
  55. out.write(b);
  56. }
  57. }
  58. public void write(byte[] b, int off, int len) throws IOException {
  59. synchronized (OutputStreamFunneler.this) {
  60. dieIfClosed();
  61. out.write(b, off, len);
  62. }
  63. }
  64. public void close() throws IOException {
  65. release(this);
  66. }
  67. }
  68. private OutputStream out;
  69. private int count = 0;
  70. private boolean closed;
  71. private long timeoutMillis;
  72. /**
  73. * Create a new <CODE>OutputStreamFunneler</CODE> for
  74. * the specified <CODE>OutputStream</CODE>.
  75. * @param out <CODE>OutputStream</CODE>.
  76. */
  77. public OutputStreamFunneler(OutputStream out) {
  78. this(out, DEFAULT_TIMEOUT_MILLIS);
  79. }
  80. /**
  81. * Create a new <CODE>OutputStreamFunneler</CODE> for
  82. * the specified <CODE>OutputStream</CODE>, with the
  83. * specified timeout value.
  84. * @param out <CODE>OutputStream</CODE>.
  85. * @param timeoutMillis <CODE>long</CODE>.
  86. * @see #setTimeout()
  87. */
  88. public OutputStreamFunneler(OutputStream out, long timeoutMillis) {
  89. if (out == null) {
  90. throw new IllegalArgumentException(
  91. "OutputStreamFunneler.<init>: out == null");
  92. }
  93. this.out = out;
  94. this.closed = false; //as far as we know
  95. setTimeout(timeoutMillis);
  96. }
  97. /**
  98. * Set the timeout for this <CODE>OutputStreamFunneler</CODE>.
  99. * This is the maximum time that may elapse between the closure
  100. * of the last "funnel" and the next call to
  101. * <CODE>getOutputStream()</CODE> without closing the
  102. * underlying stream.
  103. * @param timeoutMillis <CODE>long</CODE> timeout value.
  104. */
  105. public synchronized void setTimeout(long timeoutMillis) {
  106. this.timeoutMillis = timeoutMillis;
  107. }
  108. /**
  109. * Get a "funnel" <CODE>OutputStream</CODE> instance to
  110. * write to this <CODE>OutputStreamFunneler</CODE>'s underlying
  111. * <CODE>OutputStream</CODE>.
  112. * @return <code>OutputStream</code>.
  113. */
  114. public synchronized OutputStream getFunnelInstance()
  115. throws IOException {
  116. dieIfClosed();
  117. try {
  118. return new Funnel();
  119. } finally {
  120. notifyAll();
  121. }
  122. }
  123. private synchronized void release(Funnel funnel) throws IOException {
  124. //ignore release of an already-closed funnel
  125. if (!funnel.closed) {
  126. try {
  127. if (timeoutMillis > 0) {
  128. try {
  129. wait(timeoutMillis);
  130. } catch (InterruptedException eyeEx) {
  131. //ignore
  132. }
  133. }
  134. if (--count == 0) {
  135. close();
  136. }
  137. } finally {
  138. funnel.closed = true;
  139. }
  140. }
  141. }
  142. private synchronized void close() throws IOException {
  143. try {
  144. dieIfClosed();
  145. out.close();
  146. } finally {
  147. closed = true;
  148. }
  149. }
  150. private synchronized void dieIfClosed() throws IOException {
  151. if (closed) {
  152. throw new IOException("The funneled OutputStream has been closed.");
  153. }
  154. }
  155. }