
com.gc.iotools.stream.is.ChunkInputStream Maven / Gradle / Ivy
package com.gc.iotools.stream.is;
/*
* Copyright (c) 2008,2009 Davide Simonetti.
* This source code is released under the BSD Software License.
*/
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.apache.commons.lang.ArrayUtils;
import com.gc.iotools.stream.utils.ArrayTools;
import com.gc.iotools.stream.utils.StreamUtils;
/**
*
* This class is useful when you have an InputStream
and you want
* to filter some parts of it basing on its content without reading it into
* memory.
*
*
* Basically it strips the initial bytes of the stream until a sequence of bytes
* equal to startMarker
is found.
*
* When a sequence of bytes equals to endMarker
is found the stream
* hide the bytes until a new startMarker
is found.
*
*
* The result is that only the bytes between startMarker
and
* endMarker
are shown to the outer stream through the
* read()
methods.
*
* Example:
*
*
*
* InputStream is = new ByteArrayInputStream("aa start bbb stopfff".getBytes());
* ChunckInputStream chunkIs = new ChunkInputStream("rt".getBytes(), "stop"
* .getBytes(), is);
* byte[] bytes = IOUtils.toByteArray(chunkIs);
* //here bytes contains " bbb "
*
*
*
* The class has three operational modes. They can be selected invoking the
* constructor with four parameters. These two modes affect how this class
* handles multiple chunks in a file.
*
*
* automaticFetch=true
(default). After an
* endMarker
is found the stream automatically moves to the next
* startMarker
if found. Usage pattern is shown in the example
* above.
* automaticFetch=false
Each chunk need to be fetched invoking
* explicitly a {@link #fetchNextChunk()}
methods. The stream is
* initially in an EOF state and {@link #fetchNextChunk()}
must be
* called at first. At this point all the bytes of the stream are shown until an
* endMarker
is found. At this point the ChunkInputStream is in an
* EOF state until a {@link #fetchNextChunk()}
is invoked.
* automaticFetch=false
and startMarker=null
It is
* similar to the previous case. It can be used to the src stream on the
* endMarker
s
*
*
* Example of automaticFetch=false
mode:
*
*
*
* InputStream is = new ByteArrayInputStream("aa start bbb stopfff".getBytes());
* ChunckInputStream chunkIs = new ChunkInputStream(is, "rt".getBytes(), "stop"
* .getBytes(), false, false);
* while (chunkIs.moveToNextChunk()) {
* byte[] bytes = IOUtils.toByteArray(chunkIs);
* //here bytes contains " bbb "
* }
*
*
* @author dvd.smnt
* @since 1.0.8
*/
public final class ChunkInputStream extends InputStream {
private static final int SKIP_BUFFER_SIZE = 2048;
private final boolean automaticFetch;
private boolean copyToOuter = false;
private final boolean showMarkers;
private final byte[] start;
private final byte[] stop;
private final InputStream wrappedIs;
/**
* Constructs a ChunkInputStream
.
*
* @param src
* Source stream. Must not be null
.
* @param startMarker
* When this sequence of bytes is found in the src
* stream the bytes of the inner stream are shown until an
* endMarker
is found. If this parameter is set to
* null
the stream is initially in an EOF state
* until a fetchNextChunk()
is performed.
*
* @param stopMarker
* when this sequence of bytes is found in the src
* stream the bytes of the inner stream are hidden until a
* startMarker
is found. If this parameter is set to
* null
the stream is made available until the inner
* stream reaches an EOF.
*/
public ChunkInputStream(final InputStream src, final byte[] startMarker,
final byte[] stopMarker) {
this(src, startMarker, stopMarker, false,
((startMarker != null) && (startMarker.length > 0)));
}
/**
* Gets an instance of the ChunkInputStream. If
* startMarker!=null
the operating mode is set to
* automaticFetch=true
*
* @param src
* Source stream. Must not be null
.
* @param startMarker
* When this sequence of bytes is found in the src
* stream the bytes of the inner stream are shown until an
* endMarker
is found. If this parameter is set to
* null
the stream is initially in an EOF state
* until a fetchNextChunk()
is performed.
*
* @param stopMarker
* when this sequence of bytes is found in the src
* stream the bytes of the inner stream are hidden until a
* startMarker
is found. If this parameter is set to
* null
the stream is made available until the inner
* stream reaches an EOF.
* @param showMarkers
* if set to true
start and end markers are shown in
* the outer stream.
* @param automaticFetch
* enables automatic fetching of startMarker
s. If
* false
startMarker
s must be fetched
* manually invoking {@link #fetchNextChunk()}
*
* @see fetchNextChunk()
*/
public ChunkInputStream(final InputStream src, final byte[] startMarker,
final byte[] stopMarker, final boolean showMarkers,
final boolean automaticFetch) {
if (src == null) {
throw new IllegalArgumentException(
"Wrapped InputStrem can't be null");
}
this.start = (startMarker == null ? new byte[0] : startMarker);
this.stop = (stopMarker == null ? new byte[0] : stopMarker);
this.wrappedIs = new BufferedInputStream(src);
this.automaticFetch = automaticFetch;
if ((this.start.length == 0) && automaticFetch) {
throw new IllegalArgumentException(
"It's not possible to specify " + "a startMarker"
+ (startMarker == null ? "=null" : ".size=0")
+ " and" + " automaticFetch=[" + automaticFetch
+ "]");
}
this.showMarkers = showMarkers;
}
/**
* {@inheritDoc}.
*/
@Override
public int available() throws IOException {
findStartMarker();
return this.wrappedIs.available();
}
/**
* {@inheritDoc}.
*/
@Override
public void close() throws IOException {
this.wrappedIs.close();
}
/**
* This method must be called if automaticFetch=false
before
* the stream can be used and each time an endMarker has been found to
* proceed to next startMarker.
*
* @return true
if another chunk is available,
* false
otherwise.
* @throws IOException
* exception thrown if it is impossible to read from the inner
* stream for some unknown reason.
*/
public boolean fetchNextChunk() throws IOException {
if (this.automaticFetch) {
throw new IllegalStateException(
"this method shouldn't be called when automaticFetch ["
+ this.automaticFetch + "]");
}
this.copyToOuter = moveToNextStartMarker();
return this.copyToOuter;
}
/**
* {@inheritDoc}.
*/
@Override
public synchronized void mark(final int readlimit) {
this.wrappedIs.mark(readlimit);
}
/**
* {@inheritDoc}.
*/
@Override
public boolean markSupported() {
return this.wrappedIs.markSupported();
}
/**
* {@inheritDoc}.
*/
@Override
public int read() throws IOException {
final byte[] buf = new byte[1];
final int rd = read(buf);
int result = buf[0];
if (rd < 0) {
result = rd;
}
return result;
}
/**
* {@inheritDoc}.
*/
@Override
public int read(final byte[] b) throws IOException {
return this.read(b, 0, b.length);
}
/**
* {@inheritDoc}.
*/
@Override
public int read(final byte[] b, final int off, final int len)
throws IOException {
if ((off | len | (off + len) | (b.length - (off + len))) < 0) {
throw new IndexOutOfBoundsException("b.length[" + b.length
+ "] offset[" + off + "] length[" + len + "");
} else if (len == 0) {
return 0;
}
findStartMarker();
int ret;
if (this.copyToOuter) {
if (this.stop.length > 0) {
final int readSize = len - off + this.stop.length;
final byte[] tmpBuffer = new byte[readSize];
this.wrappedIs.mark(readSize);
ret = StreamUtils.tryReadFully(this.wrappedIs, tmpBuffer, 0,
readSize);
this.wrappedIs.reset();
if (ret != -1) {
final int position = ArrayTools.indexOf(tmpBuffer,
this.stop);
if (position == -1) {
// stop marker not found
ret = Math.min(ret, len - off);
this.wrappedIs.skip(ret);
System.arraycopy(tmpBuffer, 0, b, off, ret);
} else if (position == 0) {
this.wrappedIs.skip(this.stop.length);
this.copyToOuter = false;
ret = this.read(b, off, len);
} else {
// position >0
ret = position;
final int bytesToSkip = position + this.stop.length;
this.wrappedIs.skip(bytesToSkip);
System.arraycopy(tmpBuffer, 0, b, off, position);
this.copyToOuter = false;
}
}
} else {
ret = this.wrappedIs.read(b, off, len);
}
} else {
ret = -1;
}
return ret;
}
/**
* {@inheritDoc}.
*/
@Override
public synchronized void reset() throws IOException {
this.wrappedIs.reset();
}
/**
* {@inheritDoc}.
*/
@Override
public long skip(final long n) throws IOException {
findStartMarker();
return super.skip(n);
}
private void findStartMarker() throws IOException {
if (!this.copyToOuter && this.automaticFetch) {
// if no start marker set copy
this.copyToOuter = moveToNextStartMarker();
}
}
private boolean moveToNextStartMarker() throws IOException {
boolean found;
if (this.start.length == 0) {
this.wrappedIs.mark(2);
// if EOF stop.
found = (this.wrappedIs.read() >= 0);
this.wrappedIs.reset();
} else {
int n;
found = false;
final byte[] buffer = new byte[ChunkInputStream.SKIP_BUFFER_SIZE
+ this.start.length];
do {
this.wrappedIs.mark(ChunkInputStream.SKIP_BUFFER_SIZE
+ this.start.length);
n = StreamUtils.tryReadFully(this.wrappedIs, buffer, 0,
SKIP_BUFFER_SIZE + this.start.length);
if (n > 0) {
final int pos = ArrayTools.indexOf(ArrayUtils.subarray(
buffer, 0, n), this.start);
if (pos >= 0) {
// found
found = true;
this.wrappedIs.reset();
final int skip = pos
+ (this.showMarkers ? 0 : this.start.length);
this.wrappedIs.skip(skip);
} else {
// not found
if (n - this.start.length > 0) {
this.wrappedIs.reset();
this.wrappedIs.skip(n - this.start.length);
}
}
}
} while (!found && (n >= 0));
}
return found;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy