
com.gc.iotools.stream.is.ChunkInputStream Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of easystream Show documentation
Show all versions of easystream Show documentation
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 com.gc.iotools.stream.base.EasyStreamConstants;
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
* @version $Id: ChunkInputStream.java 576 2015-03-28 00:03:33Z gcontini $
*/
public final class ChunkInputStream extends InputStream {
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 InputStream. 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 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()}
* @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.
* @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 java.io.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;
}
private void findStartMarker() throws IOException {
if (!this.copyToOuter && this.automaticFetch) {
// if no start marker set copy
this.copyToOuter = moveToNextStartMarker();
}
}
/**
* {@inheritDoc}.
*/
@Override
public synchronized void mark(final int readlimit) {
this.wrappedIs.mark(readlimit);
}
/**
* {@inheritDoc}.
*/
@Override
public boolean markSupported() {
return this.wrappedIs.markSupported();
}
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[EasyStreamConstants.SKIP_BUFFER_SIZE
+ this.start.length];
do {
this.wrappedIs.mark(EasyStreamConstants.SKIP_BUFFER_SIZE
+ this.start.length);
n = StreamUtils.tryReadFully(this.wrappedIs, buffer, 0,
EasyStreamConstants.SKIP_BUFFER_SIZE
+ this.start.length);
if (n > 0) {
final int pos = ArrayTools.indexOf(
ArrayTools.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;
}
/**
* {@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);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy