All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.gc.iotools.stream.is.RandomAccessInputStream Maven / Gradle / Ivy

Go to download

EasyStream is a small set of utilities for dealing with streams (InputStreams and OutputStreams). The aim is to ease the use of pipes when they're required. Main features are: * "Convert" an OutputStream to an InputStream. * Count the number of bytes read or wrote to a given stream. * While reading the data from an InputStream copy it to a supplied OutputStream. * Read the content of an InputStream multiple times or seek to a definite position

The newest version!
package com.gc.iotools.stream.is;

/*
 * Copyright (c) 2008, 2015 Gabriele Contini. This source code is released
 * under the BSD License.
 */
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;

import com.gc.iotools.stream.base.AbstractInputStreamWrapper;
import com.gc.iotools.stream.store.SeekableStore;
import com.gc.iotools.stream.store.Store;
import com.gc.iotools.stream.store.ThresholdStore;

/**
 * 

* A RandomAccessInputStream adds functionality to another input * stream-namely, the ability to buffer the input, allowing it to be read * multiple times, and to support the mark and reset * methods. *

*

* When the RandomAccessInputStream is created, an internal * {@linkplain Store} is created. As bytes from the stream are read or skipped, * the internal store is refilled as necessary from the source * input stream. The implementation of store can be changed to fit * the application needs: cache on disk rather than in memory. The default * store implementation caches 64K in memory and then write the * content on disk. *

*

* It also adds the functionality of marking an InputStream without * specifying a mark length, thus allowing a reset after an * indefinite length of bytes has been read. Check the {@link #mark(int))} * javadoc for details. *

*

* Internally it uses a {@link RandomAccessFile} to cache and seek the data. * Since it must be able to random seek it can't be (easily) buffered * internally. External programs should wrap this class with a * {@link BufferedInputStream} to improve performances (especially if * int read() method is called). *

* * @author gabriele.contini * @see Store * @since 1.2.0 * @version $Id: RandomAccessInputStream.java 527 2014-02-24 19:29:50Z * $ */ public class RandomAccessInputStream extends AbstractInputStreamWrapper { /** * Default size for passing from memory allocation to disk allocation for * the buffer. */ public static final int DEFAULT_DISK_TRHESHOLD = 32768 * 2; protected long markLimit = 0; /** * Position in the stream when the mark() was issued. */ protected long markPosition = 0; /** * Position of read cursor in the RandomAccessInputStream. */ protected long randomAccessIsPosition = 0; /** * Position of reading in the source stream. */ protected long sourcePosition = 0; /** * Store where data is kept. */ private Store store; /** *

* Constructor for RandomAccessInputStream. *

* * @param source * The underlying input stream. */ public RandomAccessInputStream(final InputStream source) { this(source, DEFAULT_DISK_TRHESHOLD); } /** *

* Creates a RandomAccessInputStream with the specified * treshold, and saves its argument, the input stream source, * for later use. *

*

* When data read under threshold size treshold is kept into * memory. Over this size it is placed on disk. *

* * @see ThresholdStore * @param source * The underlying input stream. * @param threshold * Maximum number of bytes to keep into memory. */ public RandomAccessInputStream(final InputStream source, final int threshold) { super(source); this.store = new ThresholdStore(threshold); } /** *

* Constructor for RandomAccessInputStream. *

* * @param source * The underlying input stream. * @param store * a {@link com.gc.iotools.stream.store.SeekableStore} object. */ public RandomAccessInputStream(final InputStream source, final SeekableStore store) { super(source); if (store == null) { throw new IllegalArgumentException("store can't be null."); } this.store = store; } /** {@inheritDoc} */ @Override public int available() throws IOException { return (int) Math.min(this.sourcePosition - this.randomAccessIsPosition + this.source.available(), Integer.MAX_VALUE); } /** {@inheritDoc} */ @Override protected void closeOnce() throws IOException { this.store.cleanup(); this.source.close(); } /** * Return the underlying store where the cache of data is kept. * * @return The underlying store that caches data. */ public Store getStore() { return this.store; } /** {@inheritDoc} */ @Override protected int innerRead(final byte[] b, final int off, final int len) throws IOException { int n; if (this.sourcePosition == this.randomAccessIsPosition) { // source and external same position so read from source. n = super.source.read(b, off, len); if (n > 0) { this.sourcePosition += n; this.randomAccessIsPosition += n; this.store.put(b, off, n); } } else if (this.randomAccessIsPosition < this.sourcePosition) { // resetIS has been called. Read from buffer;n final int efflen = (int) Math.min(len, this.sourcePosition - this.randomAccessIsPosition); n = this.store.get(b, off, efflen); if (n <= 0) { throw new IllegalStateException( "Problem reading from buffer. Expecting bytes [" + efflen + "] but buffer is empty."); } this.randomAccessIsPosition += n; } else { /* * shouldn't be here. refactor throw exception * randomAccessIsPosition > sourcePosition. A reset() was called on * the StorageBufInputStream. just read from source don't buffer. */ // final int efflen = (int) Math.min(len, // this.randomAccessIsPosition - this.sourcePosition); // n = this.source.read(b, off, efflen); // this.sourcePosition += Math.max(n, 0); throw new IllegalStateException("randomAccessIsPosition[" + this.randomAccessIsPosition + "] > sourcePosition[" + this.sourcePosition + "]"); } return n; } /** * {@inheritDoc} * *

* Marks the current position in this input stream. A subsequent call to the * {@linkplain #reset()} method repositions this stream at the last marked * position so that subsequent reads re-read the same bytes. *

*

* This method extends the original behavior of the class * InputStream allowing to use indefinite marking. *

    *
  • readLimit> 0 The readLimit arguments * tells this input stream to allow that many bytes to be read before the * mark position gets invalidated.
  • *
  • readLimit == 0 Invalidate the all the current marks and * clean up the temporary files.
  • *
  • readLimit < 0 Set up an indefinite mark: reset can * be invoked regardless on how many bytes have been read.
  • *
*

* * @see RandomAccessInputStream#reset() * @see java.io.InputStream#reset() */ @Override public synchronized void mark(final int readLimit) { this.markLimit = readLimit; this.markPosition = this.randomAccessIsPosition; } /** * {@inheritDoc} * * Overrides the markSupported() method of the * InputStream class. * * @see InputStream#markSupported(); */ @Override public boolean markSupported() { return true; } /** * {@inheritDoc} * *

* Repositions this stream to the position at the time the mark * method was last called on this input stream. *

*

* After invoking mark it can be invoked multiple times and it * always reset the stream at the previously marked position. *

* * @exception IOException * if this stream has not been marked or if the mark has been * invalidated. * @see RandomAccessInputStream#mark(int) * @see java.io.InputStream#reset() */ @Override public synchronized void reset() throws IOException { if ((this.markLimit < 0) || (this.randomAccessIsPosition - this.markPosition <= this.markLimit)) { this.randomAccessIsPosition = this.markPosition; ((SeekableStore) this.store).seek(this.markPosition); } else { throw new IOException("Reset to an invalid mark."); } } /** *

* Reposition the read pointer in the stream. Next read will start from the * position passed as argument. *

* * @param position * a long indicating the position in the stream we want to go. * @throws java.io.IOException * throw if any IOException occurs reading the underlying stream * or if it is attempted a seek to a position greater to the * effective number of bytes in the stream. */ public void seek(final long position) throws IOException { if (position < 0) { throw new IllegalArgumentException("Seek to negative position [" + position + "]"); } if (!(this.store instanceof SeekableStore)) { throw new IllegalStateException("Seek was called but the store[" + this.store + "] is not an instance of [" + SeekableStore.class + "]"); } final long startingPos = this.randomAccessIsPosition; final long len = position - this.randomAccessIsPosition; if (len > 0) { final long n = skip(len); if (n < len) { throw new IOException("Requested seek to [" + position + "] but the stream is only [" + (this.randomAccessIsPosition) + "] bytes long. skipped[" + n + "] startingPos[" + startingPos + "]."); } } else if (len < 0) { // if len==0 already at the right place. Do Nothing. this.randomAccessIsPosition = position; ((SeekableStore) this.store).seek(position); } } /** *

* Setter for the field store. *

* * @param store * a {@link com.gc.iotools.stream.store.Store} object. */ public void setStore(final Store store) { this.store = store; } /** * {@inheritDoc} * * Provides a String representation of the state of the stream for debugging * purposes. */ @Override public String toString() { return this.getClass().getSimpleName() + "[randomAccPos=" + this.randomAccessIsPosition + ",srcPos=" + this.sourcePosition + ", store=" + this.store + "]"; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy