net.sf.mmm.util.io.impl.DetectorStreamBufferImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mmm-util-io Show documentation
Show all versions of mmm-util-io Show documentation
Utilities for input/output and streaming.
The newest version!
/* Copyright (c) The m-m-m Team, Licensed under the Apache License, Version 2.0
* http://www.apache.org/licenses/LICENSE-2.0 */
package net.sf.mmm.util.io.impl;
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.NoSuchElementException;
import net.sf.mmm.util.exception.api.NlsIllegalArgumentException;
import net.sf.mmm.util.io.api.BufferExceedException;
import net.sf.mmm.util.io.api.ByteArray;
import net.sf.mmm.util.io.api.spi.DetectorStreamBuffer;
import net.sf.mmm.util.io.api.spi.DetectorStreamProcessor;
import net.sf.mmm.util.io.base.AbstractByteArray;
import net.sf.mmm.util.io.base.ByteArrayImpl;
import net.sf.mmm.util.pool.api.ByteArrayPool;
/**
* This is the implementation of the {@link DetectorStreamBuffer} interface.
* It is based on the idea that each {@link DetectorStreamProcessor} in the chain has its own
* {@link DetectorStreamBuffer} instance. Therefore it holds the according {@link DetectorStreamProcessor} building a
* pair of buffer+processor. Further it holds an instance of the predecessor and thereby represents the chain itself.
*
* @author Joerg Hohwiller (hohwille at users.sourceforge.net)
*/
public class DetectorStreamBufferImpl implements DetectorStreamBuffer {
/** The actual processor served by this buffer. */
private DetectorStreamProcessor processor;
/** The successor in the chain or {@code null} if this is the last. */
private DetectorStreamBufferImpl chainSuccessor;
private long streamPosition;
/** @see #seek(long, SeekMode) */
private long seekCount;
/** @see #seek(long, SeekMode) */
private SeekMode seekMode;
private ByteArray currentByteArray;
/** The current {@link ByteArray} to work on. */
private byte[] currentArray;
/** The start-index in {@link #currentArray}. */
private int currentArrayMin;
/** The {@link ByteArray#getCurrentIndex() index} in {@link #currentArray}. */
private int currentArrayIndex;
/**
* The {@link ByteArray#getMaximumIndex() maximum index} in {@link #currentArray}.
*/
private int currentArrayMax;
/**
* The {@link java.util.Queue} of available {@link ByteArray}s that have NOT yet been processed.
*/
private final LinkedList arrayQueue;
private final ByteArray currentArrayView;
/** The {@link ByteArrayPool}. */
private ByteArrayPool byteArrayPool;
/**
* The constructor.
*
* @param processor is the {@link DetectorStreamProcessor} served by this buffer.
* @param successor is the successor in the chain or {@code null} if this is the last buffer/processor pair in the
* chain.
* @param byteArrayPool is the {@link ByteArrayPool} to use.
*/
public DetectorStreamBufferImpl(DetectorStreamProcessor processor, DetectorStreamBufferImpl successor,
ByteArrayPool byteArrayPool) {
super();
this.arrayQueue = new LinkedList<>();
this.chainSuccessor = successor;
this.byteArrayPool = byteArrayPool;
this.processor = processor;
this.currentArrayView = new CurrentByteArray();
}
@Override
public long skip(long byteCount) {
seek(byteCount, SeekMode.SKIP);
return byteCount;
}
@Override
public long skip() {
if (this.currentArray == null) {
return 0;
}
int bytesAvailable = this.currentArrayMax - this.currentArrayIndex + 1;
if (this.chainSuccessor != null) {
this.chainSuccessor
.append(this.currentByteArray.createSubArray(this.currentArrayIndex, this.currentArrayMax));
}
release(this.currentByteArray);
this.currentArray = null;
Iterator arrayIterator = this.arrayQueue.iterator();
while (arrayIterator.hasNext()) {
ByteArray array = arrayIterator.next();
arrayIterator.remove();
bytesAvailable = bytesAvailable + array.getBytesAvailable();
if (this.chainSuccessor == null) {
release(array);
} else {
this.chainSuccessor.append(array);
}
}
return bytesAvailable;
}
@Override
public ByteArray getByteArray(int index) {
if (index == 0) {
return this.currentArrayView;
} else {
return this.arrayQueue.get(index - 1);
}
}
@Override
public int getByteArrayCount() {
int arrayCount = this.arrayQueue.size();
if (this.currentArray != null) {
arrayCount++;
}
return arrayCount;
}
@Override
public int getBytesAvailable() {
if (this.currentArray == null) {
return 0;
}
int bytesAvailable = this.currentArrayMax - this.currentArrayIndex + 1;
for (ByteArray array : this.arrayQueue) {
bytesAvailable = bytesAvailable + array.getBytesAvailable();
}
return bytesAvailable;
}
@Override
public boolean hasNext() {
if (this.currentArray == null) {
boolean okay = nextArray();
if (!okay) {
return false;
}
}
return true;
}
/**
* This method is called when a {@link ByteArray} is wiped out of the chain.
*
* @param byteArray is the array to release.
*/
protected void release(ByteArray byteArray) {
if (byteArray instanceof PooledByteArray) {
PooledByteArray pooledArray = (PooledByteArray) byteArray;
if (pooledArray.release()) {
this.byteArrayPool.release(byteArray.getBytes());
}
}
}
/**
* This method switches over to the next internal {@link #getByteArray(int) byte-array}.
*
* @return {@code true} if a new buffer is available, {@code false} if the buffer queue is empty.
*/
private boolean nextArray() {
if (this.currentArray != null) {
if ((this.currentArrayMin < this.currentArrayMax) && (this.chainSuccessor != null)
&& (this.seekMode != SeekMode.REMOVE)) {
ByteArray subArray = this.currentByteArray.createSubArray(this.currentArrayMin, this.currentArrayMax);
this.chainSuccessor.append(subArray);
}
release(this.currentByteArray);
}
if (this.arrayQueue.isEmpty()) {
this.currentArray = null;
return false;
} else {
ByteArray nextArray = this.arrayQueue.remove();
int offsetMin = 0;
int offsetIndex = 0;
while (this.seekCount > 0) {
long bytesAvailable = nextArray.getBytesAvailable();
if (this.seekCount >= bytesAvailable) {
this.seekCount = this.seekCount - bytesAvailable;
this.streamPosition = this.streamPosition + bytesAvailable;
if ((this.chainSuccessor != null) && (this.seekMode == SeekMode.SKIP)) {
this.chainSuccessor.append(nextArray);
} else {
release(this.currentByteArray);
}
if (this.arrayQueue.isEmpty()) {
this.currentArray = null;
return false;
}
nextArray = this.arrayQueue.remove();
} else {
offsetIndex = (int) this.seekCount;
if (this.seekMode == SeekMode.REMOVE) {
offsetMin = offsetIndex;
}
this.streamPosition = this.streamPosition + this.seekCount;
this.seekCount = 0;
this.seekMode = null;
// break;
}
}
this.currentByteArray = nextArray;
this.currentArray = nextArray.getBytes();
int currentIndex = nextArray.getCurrentIndex();
this.currentArrayMin = currentIndex + offsetMin;
this.currentArrayIndex = currentIndex + offsetIndex;
this.currentArrayMax = nextArray.getMaximumIndex();
if (this.currentArrayIndex > this.currentArrayMax) {
// array already empty...
return nextArray();
}
return true;
}
}
@Override
public byte next() throws NoSuchElementException {
if (this.currentArray == null) {
throw new NoSuchElementException();
}
byte result = this.currentArray[this.currentArrayIndex++];
this.streamPosition++;
if (this.currentArrayIndex > this.currentArrayMax) {
nextArray();
}
return result;
}
@Override
public byte peek() throws NoSuchElementException {
if (this.currentArray == null) {
throw new NoSuchElementException();
}
byte result = this.currentArray[this.currentArrayIndex];
return result;
}
@Override
public void insert(byte... data) {
insert(new ByteArrayImpl(data));
}
@Override
public void insert(ByteArray data) {
if (this.currentArray != null) {
int max = this.currentArrayIndex - 1;
if (this.currentArrayMin <= max) {
this.chainSuccessor.append(new ByteArrayImpl(this.currentArray, this.currentArrayMin, max));
}
this.currentArrayMin = this.currentArrayIndex;
}
this.chainSuccessor.append(data);
}
/**
* This method {@link #remove(long) removes} or {@link #skip(long) skips} the given number of bytes.
*
* @param byteCount is the number of bytes to seek.
* @param mode is the {@link SeekMode}.
*/
protected void seek(long byteCount, SeekMode mode) {
if (this.seekMode == null) {
this.seekMode = mode;
} else if (this.seekMode != mode) {
// TODO: add dedicated exception or constructor to
// IllegalStateException...
throw new NlsIllegalArgumentException("remove and skip can NOT be mixed!");
}
this.seekCount = this.seekCount + byteCount;
if (this.currentArray != null) {
// if removeCount was > 0 before, then currentArray had been null.
if (mode == SeekMode.REMOVE) {
if (this.currentArrayMin < this.currentArrayIndex) {
// there are bytes that have been consumed before remove was
// invoked...
ByteArray subArray = this.currentByteArray.createSubArray(this.currentArrayMin,
this.currentArrayIndex - 1);
this.chainSuccessor.append(subArray);
this.currentArrayMin = this.currentArrayIndex;
}
}
long currentBytesAvailable = this.currentArrayMax - this.currentArrayIndex + 1;
if (currentBytesAvailable > this.seekCount) {
this.currentArrayIndex = (int) (this.currentArrayIndex + this.seekCount);
this.seekCount = 0;
this.seekMode = null;
if (mode == SeekMode.REMOVE) {
this.currentArrayMin = this.currentArrayIndex;
}
} else {
this.seekCount = this.seekCount - currentBytesAvailable;
nextArray();
if (this.seekCount == 0) {
this.seekMode = null;
}
}
}
}
@Override
public void remove(long byteCount) {
seek(byteCount, SeekMode.REMOVE);
}
@Override
public long getStreamPosition() {
return this.streamPosition;
}
/**
* This method queues the given {@link ByteArray} at the end of this buffer.
*
* @param nextArray is the {@link ByteArray} to append.
*/
protected void append(ByteArray nextArray) {
this.arrayQueue.add(nextArray);
if (this.currentArray == null) {
nextArray();
}
}
/**
* @see DetectorStreamProcessor#process(DetectorStreamBuffer, Map, boolean)
*
* @param metadata is the {@link Map} with the meta-data.
* @param eos - {@code true} if the end of the stream has been reached and the given {@code buffer} has to be
* @throws IOException in case of an Input/Output error. Should only be used internally.
*/
public void process(Map metadata, boolean eos) throws IOException {
this.processor.process(this, metadata, eos);
if (this.chainSuccessor != null) {
this.chainSuccessor.process(metadata, eos);
}
}
@Override
public int fill(byte[] buffer, int offset, int length) {
if (offset >= buffer.length) {
throw new BufferExceedException(offset, buffer.length);
}
if ((length + offset) > buffer.length) {
throw new BufferExceedException(length, buffer.length - offset);
}
if (!hasNext()) {
// buffer is empty...
return -1;
}
int bufferIndex = offset;
int bytesLeft = length;
while (bytesLeft > 0) {
int count = this.currentArrayMax - this.currentArrayIndex + 1;
if (count > bytesLeft) {
count = bytesLeft;
bytesLeft = 0;
} else {
bytesLeft = bytesLeft - count;
}
System.arraycopy(this.currentArray, this.currentArrayIndex, buffer, bufferIndex, count);
bufferIndex = bufferIndex + count;
this.currentArrayIndex = this.currentArrayIndex + count;
if (this.currentArrayIndex > this.currentArrayMax) {
boolean bufferLeft = nextArray();
if (!bufferLeft) {
break;
}
}
}
return (length - bytesLeft);
}
/**
* This inner class is a view on the current {@link ByteArray}.
*
* @see DetectorStreamBufferImpl#getByteArray(int)
*/
protected class CurrentByteArray extends AbstractByteArray {
@Override
public byte[] getBytes() {
return DetectorStreamBufferImpl.this.currentArray;
}
@Override
public int getBytesAvailable() {
return DetectorStreamBufferImpl.this.currentArrayMax - DetectorStreamBufferImpl.this.currentArrayIndex + 1;
}
@Override
public int getMinimumIndex() {
return DetectorStreamBufferImpl.this.currentArrayIndex;
}
@Override
public int getCurrentIndex() {
return DetectorStreamBufferImpl.this.currentArrayIndex;
}
@Override
public int getMaximumIndex() {
return DetectorStreamBufferImpl.this.currentArrayMax;
}
}
/**
* Enum with available modes for a {@link DetectorStreamBufferImpl#seek(long, SeekMode) seek}.
*/
protected static enum SeekMode {
/** @see DetectorStreamBufferImpl#skip(long) */
SKIP,
/** @see DetectorStreamBufferImpl#remove(long) */
REMOVE
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy