001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: package org.apache.tools.ant.util;
020:
021: import java.io.IOException;
022: import java.io.OutputStream;
023:
024: /**
025: * Manages a set of <code>OutputStream</code>s to
026: * write to a single underlying stream, which is
027: * closed only when the last "funnel"
028: * has been closed.
029: */
030: public class OutputStreamFunneler {
031:
032: /**
033: * Default timeout.
034: * @see #setTimeout(long)
035: */
036: public static final long DEFAULT_TIMEOUT_MILLIS = 1000;
037:
038: private final class Funnel extends OutputStream {
039: private boolean closed = false;
040:
041: private Funnel() {
042: synchronized (OutputStreamFunneler.this ) {
043: ++count;
044: }
045: }
046:
047: public void flush() throws IOException {
048: synchronized (OutputStreamFunneler.this ) {
049: dieIfClosed();
050: out.flush();
051: }
052: }
053:
054: public void write(int b) throws IOException {
055: synchronized (OutputStreamFunneler.this ) {
056: dieIfClosed();
057: out.write(b);
058: }
059: }
060:
061: public void write(byte[] b) throws IOException {
062: synchronized (OutputStreamFunneler.this ) {
063: dieIfClosed();
064: out.write(b);
065: }
066: }
067:
068: public void write(byte[] b, int off, int len)
069: throws IOException {
070: synchronized (OutputStreamFunneler.this ) {
071: dieIfClosed();
072: out.write(b, off, len);
073: }
074: }
075:
076: public void close() throws IOException {
077: release(this );
078: }
079: }
080:
081: private OutputStream out;
082: private int count = 0;
083: private boolean closed;
084: private long timeoutMillis;
085:
086: /**
087: * Create a new <code>OutputStreamFunneler</code> for
088: * the specified <code>OutputStream</code>.
089: * @param out <code>OutputStream</code>.
090: */
091: public OutputStreamFunneler(OutputStream out) {
092: this (out, DEFAULT_TIMEOUT_MILLIS);
093: }
094:
095: /**
096: * Create a new <code>OutputStreamFunneler</code> for
097: * the specified <code>OutputStream</code>, with the
098: * specified timeout value.
099: * @param out <code>OutputStream</code>.
100: * @param timeoutMillis <code>long</code>.
101: * @see #setTimeout(long)
102: */
103: public OutputStreamFunneler(OutputStream out, long timeoutMillis) {
104: if (out == null) {
105: throw new IllegalArgumentException(
106: "OutputStreamFunneler.<init>: out == null");
107: }
108: this .out = out;
109: this .closed = false; //as far as we know
110: setTimeout(timeoutMillis);
111: }
112:
113: /**
114: * Set the timeout for this <code>OutputStreamFunneler</code>.
115: * This is the maximum time that may elapse between the closure
116: * of the last "funnel" and the next call to
117: * <code>getOutputStream()</code> without closing the
118: * underlying stream.
119: * @param timeoutMillis <code>long</code> timeout value.
120: */
121: public synchronized void setTimeout(long timeoutMillis) {
122: this .timeoutMillis = timeoutMillis;
123: }
124:
125: /**
126: * Get a "funnel" <code>OutputStream</code> instance to
127: * write to this <code>OutputStreamFunneler</code>'s underlying
128: * <code>OutputStream</code>.
129: * @return <code>OutputStream</code>.
130: * @throws IOException if unable to create the funnel.
131: */
132: public synchronized OutputStream getFunnelInstance()
133: throws IOException {
134: dieIfClosed();
135: try {
136: return new Funnel();
137: } finally {
138: notifyAll();
139: }
140: }
141:
142: private synchronized void release(Funnel funnel) throws IOException {
143: //ignore release of an already-closed funnel
144: if (!funnel.closed) {
145: try {
146: if (timeoutMillis > 0) {
147: try {
148: wait(timeoutMillis);
149: } catch (InterruptedException eyeEx) {
150: //ignore
151: }
152: }
153: if (--count == 0) {
154: close();
155: }
156: } finally {
157: funnel.closed = true;
158: }
159: }
160: }
161:
162: private synchronized void close() throws IOException {
163: try {
164: dieIfClosed();
165: out.close();
166: } finally {
167: closed = true;
168: }
169: }
170:
171: private synchronized void dieIfClosed() throws IOException {
172: if (closed) {
173: throw new IOException(
174: "The funneled OutputStream has been closed.");
175: }
176: }
177:
178: }
|