/*****************************************************************************
* Copyright (c) 2006, 2007 g-Eclipse Consortium
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Initial development of the original code was made for the
* g-Eclipse project founded by European Union
* project number: FP6-IST-034327 http://www.geclipse.eu/
*
* Contributors:
* Mathias Stuempert - initial API and implementation
*****************************************************************************/
package eu.geclipse.core.filesystem.internal.filesystem;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubMonitor;
/**
* Implementation of an {@link InputStream} that caches the whole content
* of a slave stream. An indirect {@link ByteBuffer} is used for caching.
* Therefore only relatively small streams should be cached in order to
* avoid out of memory errors.
*/
public class CachedInputStream extends InputStream {
/**
* Size of the read buffer.
*/
private static final int BUFFER_SIZE = 1024 * 1024;
/**
* The slave stream, i.e. the stream to be cached.
*/
private InputStream slave;
/**
* The size of the cache, e.g. the file size.
*/
private int bufferSize;
/**
* The buffer used for caching.
*/
private ByteBuffer buffer;
/**
* Create a new <code>CachedInputStream</code> for the specified slave
* stream. The cache will have the specified size. The size could for
* instance be the size of the file represented by the input stream.
* In this case the whole file would be cached.
*
* Before any operation is done on this stream {@link #cache(IProgressMonitor)}
* has to be called in order to initialize the caching. Note that one has
* to call {@link #discard()} in order to free the cache. A simple {@link #close()}
* will not release the cache and system resources are not freed.
*
* @param slave The input stream to be cached.
* @param cacheSize The size of the cache.
*/
public CachedInputStream( final InputStream slave, final int cacheSize ) {
this.slave = slave;
this.bufferSize = cacheSize;
}
/* (non-Javadoc)
* @see java.io.InputStream#available()
*/
@Override
public int available() throws IOException {
int result = 0;
if ( this.buffer != null ) {
result = this.buffer.remaining();
}
return result;
}
/**
* Cache the slave stream to a local buffer.
*
* @param monitor An {@link IProgressMonitor} used to monitor the caching
* procedure.
* @throws IOException If an error occures while reading from the slave stream.
*/
public void cache( final IProgressMonitor monitor )
throws IOException {
SubMonitor sMonitor
= SubMonitor.convert(
monitor,
Messages.getString("CachedInputStream.caching_progress"), this.bufferSize / BUFFER_SIZE //$NON-NLS-1$
);
sMonitor.subTask( Messages.getString("CachedInputStream.allocating_progress") ); //$NON-NLS-1$
this.buffer = ByteBuffer.allocate( this.bufferSize );
int totalBytesRead = 0;
int currentBlockRead = 0;
try {
int bytesRead = 0;
byte[] readBuffer = new byte[ BUFFER_SIZE ];
for ( ; ; ) {
if ( sMonitor.isCanceled() ) {
break;
}
bytesRead = this.slave.read( readBuffer );
if ( bytesRead < 0 ) {
break;
}
this.buffer.put( readBuffer, 0, bytesRead );
totalBytesRead += bytesRead;
currentBlockRead += bytesRead;
if ( currentBlockRead >= BUFFER_SIZE ) {
int steps = currentBlockRead / BUFFER_SIZE;
currentBlockRead %= BUFFER_SIZE;
double fraction = 100. / this.bufferSize * totalBytesRead;
sMonitor.worked( steps );
sMonitor.subTask(
String.format( Messages.getString("CachedInputStream.progress_format"), //$NON-NLS-1$
Integer.valueOf( totalBytesRead / 1024 ),
Integer.valueOf( this.bufferSize / 1024 ),
Double.valueOf( fraction ) ) );
}
}
double fraction = 100. / this.bufferSize * totalBytesRead;
sMonitor.subTask(
String.format( Messages.getString("CachedInputStream.progress_format"), //$NON-NLS-1$
Integer.valueOf( totalBytesRead / 1024 ),
Integer.valueOf( this.bufferSize / 1024 ),
Double.valueOf( fraction ) ) );
} finally {
this.slave.close();
this.buffer.rewind();
sMonitor.done();
}
}
/* (non-Javadoc)
* @see java.io.InputStream#close()
*/
@Override
public void close() throws IOException {
if ( this.buffer != null ) {
this.buffer.rewind();
}
}
/**
* Discard the cache and free all corresponding system resources.
*/
public void discard() {
this.buffer = null;
}
/* (non-Javadoc)
* @see java.io.InputStream#mark(int)
*/
@Override
public synchronized void mark( final int readlimit ) {
if ( this.buffer != null ) {
this.buffer.mark();
}
}
/* (non-Javadoc)
* @see java.io.InputStream#markSupported()
*/
@Override
public boolean markSupported() {
return true;
}
@Override
public int read() throws IOException {
int result = -1;
if ( ( this.buffer != null ) && this.buffer.hasRemaining() ) {
result = this.buffer.get();
}
return result;
}
/* (non-Javadoc)
* @see java.io.InputStream#read(byte[], int, int)
*/
@Override
public int read( final byte[] b, final int offset, final int length )
throws IOException {
int result = -1;
if ( this.buffer != null ) {
int remaining = this.buffer.remaining();
if ( remaining > 0 ) {
result = Math.min( remaining, length );
this.buffer.get( b, offset, result );
}
}
return result;
}
/* (non-Javadoc)
* @see java.io.InputStream#reset()
*/
@Override
public synchronized void reset() throws IOException {
if ( this.buffer != null ) {
this.buffer.reset();
}
}
/* (non-Javadoc)
* @see java.io.InputStream#skip(long)
*/
@Override
public long skip( final long n ) throws IOException {
int result = 0;
if ( this.buffer != null ) {
int remaining = this.buffer.remaining();
if ( remaining > 0 ) {
result = Math.min( remaining, ( int ) n );
int position = this.buffer.position();
this.buffer.position( position + result );
}
}
return result;
}
}