/* This code is part of Freenet. It is distributed under the GNU General
* Public License, version 2 (or at your option any later version). See
* http://www.gnu.org/ for further details of the GPL. */
package freenet.support.io;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import freenet.client.async.ClientContext;
import freenet.support.LogThresholdCallback;
import freenet.support.ListUtils;
import freenet.support.Logger;
import freenet.support.Logger.LogLevel;
import freenet.support.api.Bucket;
/**
* A wrapper for a read-only bucket providing for multiple readers. The data is
* only freed when all of the readers have freed it.
* @author toad
*/
public class MultiReaderBucket implements Serializable {
private static final long serialVersionUID = 1L;
private final Bucket bucket;
// Assume there will be relatively few readers
private ArrayList<Bucket> readers;
private boolean closed;
private static volatile boolean logMINOR;
static {
Logger.registerLogThresholdCallback(new LogThresholdCallback() {
@Override
public void shouldUpdate() {
logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
}
});
}
public MultiReaderBucket(Bucket underlying) {
bucket = underlying;
}
protected MultiReaderBucket() {
// For serialization.
bucket = null;
}
/** Get a reader bucket */
public Bucket getReaderBucket() {
synchronized(this) {
if(closed) return null;
Bucket d = new ReaderBucket();
if (readers == null)
readers = new ArrayList<Bucket>(1);
readers.add(d);
if(logMINOR)
Logger.minor(this, "getReaderBucket() returning "+d+" for "+this+" for "+bucket);
return d;
}
}
class ReaderBucket implements Bucket, Serializable {
private static final long serialVersionUID = 1L;
private boolean freed;
@Override
public void free() {
if(logMINOR)
Logger.minor(this, "ReaderBucket "+this+" for "+MultiReaderBucket.this+" free()ing for "+bucket);
synchronized(MultiReaderBucket.this) {
if(freed) return;
freed = true;
ListUtils.removeBySwapLast(readers, this);
if(!readers.isEmpty()) return;
readers = null;
if(closed) return;
closed = true;
}
bucket.free();
}
@Override
public InputStream getInputStream() throws IOException {
synchronized(MultiReaderBucket.this) {
if(freed || closed) {
throw new IOException("Already freed");
}
}
return new ReaderBucketInputStream(true);
}
@Override
public InputStream getInputStreamUnbuffered() throws IOException {
synchronized(MultiReaderBucket.this) {
if(freed || closed) {
throw new IOException("Already freed");
}
}
return new ReaderBucketInputStream(false);
}
private class ReaderBucketInputStream extends InputStream {
InputStream is;
ReaderBucketInputStream(boolean buffer) throws IOException {
is = buffer ? bucket.getInputStream() : bucket.getInputStreamUnbuffered();
}
@Override
public final int read() throws IOException {
synchronized(MultiReaderBucket.this) {
if(freed || closed) throw new IOException("Already closed");
}
return is.read();
}
@Override
public final int read(byte[] data, int offset, int length) throws IOException {
synchronized(MultiReaderBucket.this) {
if(freed || closed) throw new IOException("Already closed");
}
return is.read(data, offset, length);
}
@Override
public final int read(byte[] data) throws IOException {
synchronized(MultiReaderBucket.this) {
if(freed || closed) throw new IOException("Already closed");
}
return is.read(data);
}
@Override
public final void close() throws IOException {
is.close();
}
@Override
public final int available() throws IOException {
return is.available();
}
}
@Override
public String getName() {
return bucket.getName();
}
@Override
public OutputStream getOutputStream() throws IOException {
throw new IOException("Read only");
}
@Override
public OutputStream getOutputStreamUnbuffered() throws IOException {
throw new IOException("Read only");
}
@Override
public boolean isReadOnly() {
return true;
}
@Override
public void setReadOnly() {
// Already read only
}
@Override
public long size() {
return bucket.size();
}
@Override
protected void finalize() throws Throwable {
free();
super.finalize();
}
@Override
public Bucket createShadow() {
return null;
}
@Override
public void onResume(ClientContext context) throws ResumeFailedException {
throw new UnsupportedOperationException(); // Not persistent.
}
@Override
public void storeTo(DataOutputStream dos) throws IOException {
throw new UnsupportedOperationException();
}
}
}