blob: 6694c3f07acde39000dd06eb140172c4ced4a82e [file] [log] [blame]
/*
* 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
*
* http://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.util;
import java.io.IOException;
import java.io.OutputStream;
/**
* Manages a set of <code>OutputStream</code>s to
* write to a single underlying stream, which is
* closed only when the last &quot;funnel&quot;
* has been closed.
*/
public class OutputStreamFunneler {
/**
* Default timeout.
* @see #setTimeout(long)
*/
public static final long DEFAULT_TIMEOUT_MILLIS = 1000;
private final class Funnel extends OutputStream {
private boolean closed = false;
private Funnel() {
synchronized (OutputStreamFunneler.this) {
++count;
}
}
public void flush() throws IOException {
synchronized (OutputStreamFunneler.this) {
dieIfClosed();
out.flush();
}
}
public void write(int b) throws IOException {
synchronized (OutputStreamFunneler.this) {
dieIfClosed();
out.write(b);
}
}
public void write(byte[] b) throws IOException {
synchronized (OutputStreamFunneler.this) {
dieIfClosed();
out.write(b);
}
}
public void write(byte[] b, int off, int len) throws IOException {
synchronized (OutputStreamFunneler.this) {
dieIfClosed();
out.write(b, off, len);
}
}
public void close() throws IOException {
release(this);
}
}
private OutputStream out;
private int count = 0;
private boolean closed;
private long timeoutMillis;
/**
* Create a new <code>OutputStreamFunneler</code> for
* the specified <code>OutputStream</code>.
* @param out <code>OutputStream</code>.
*/
public OutputStreamFunneler(OutputStream out) {
this(out, DEFAULT_TIMEOUT_MILLIS);
}
/**
* Create a new <code>OutputStreamFunneler</code> for
* the specified <code>OutputStream</code>, with the
* specified timeout value.
* @param out <code>OutputStream</code>.
* @param timeoutMillis <code>long</code>.
* @see #setTimeout(long)
*/
public OutputStreamFunneler(OutputStream out, long timeoutMillis) {
if (out == null) {
throw new IllegalArgumentException(
"OutputStreamFunneler.<init>: out == null");
}
this.out = out;
this.closed = false; //as far as we know
setTimeout(timeoutMillis);
}
/**
* Set the timeout for this <code>OutputStreamFunneler</code>.
* This is the maximum time that may elapse between the closure
* of the last &quot;funnel&quot; and the next call to
* <code>getOutputStream()</code> without closing the
* underlying stream.
* @param timeoutMillis <code>long</code> timeout value.
*/
public synchronized void setTimeout(long timeoutMillis) {
this.timeoutMillis = timeoutMillis;
}
/**
* Get a &quot;funnel&quot; <code>OutputStream</code> instance to
* write to this <code>OutputStreamFunneler</code>'s underlying
* <code>OutputStream</code>.
* @return <code>OutputStream</code>.
* @throws IOException if unable to create the funnel.
*/
public synchronized OutputStream getFunnelInstance()
throws IOException {
dieIfClosed();
try {
return new Funnel();
} finally {
notifyAll();
}
}
private synchronized void release(Funnel funnel) throws IOException {
//ignore release of an already-closed funnel
if (!funnel.closed) {
try {
if (timeoutMillis > 0) {
try {
wait(timeoutMillis);
} catch (InterruptedException eyeEx) {
//ignore
}
}
if (--count == 0) {
close();
}
} finally {
funnel.closed = true;
}
}
}
private synchronized void close() throws IOException {
try {
dieIfClosed();
out.close();
} finally {
closed = true;
}
}
private synchronized void dieIfClosed() throws IOException {
if (closed) {
throw new IOException("The funneled OutputStream has been closed.");
}
}
}