com.thelastcheck.io.base.InputStreamRecordReader Maven / Gradle / Ivy
The newest version!
/*******************************************************************************
* Copyright (c) 2009-2015 The Last Check, LLC, All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.thelastcheck.io.base;
import com.thelastcheck.commons.base.exception.InvalidDataException;
import com.thelastcheck.commons.buffer.ByteArray;
import com.thelastcheck.io.base.exception.InvalidFormatException;
import com.thelastcheck.io.base.exception.InvalidStandardLevelException;
import com.thelastcheck.io.base.exception.RecordReaderException;
import java.io.Closeable;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
public abstract class InputStreamRecordReader implements
Iterable, Closeable {
protected static final String US_ASCII = ByteArray.ASCII_CHARSET_NAME;
protected static final String EBCDIC = ByteArray.EBCDIC_CHARSET_NAME;
protected static final String END_OF_STREAM_ERROR =
"End of stream reached before finished processing expected data.";
private InputStream is;
private List filterList = new ArrayList();
private Record cachedRecord;
private int recordCount;
private long offset;
private long bytesReadForRecord;
private boolean skipInvalidRecords;
/**
* Used as an internal buffer when reading into ByteArray that does not have
* an underlying array. This buffer is allocated when needed and is always
* the largest buffer ever needed during the read process.
*/
private byte[] maxBuffer;
public InputStreamRecordReader(InputStream is) {
this(is, false);
}
public InputStreamRecordReader(InputStream is, boolean skipInvalidRecords) {
super();
this.is = is;
this.skipInvalidRecords = skipInvalidRecords;
}
public void close() throws IOException {
if (is != null) {
is.close();
is = null;
}
}
public boolean isSkipInvalidRecords() {
return skipInvalidRecords;
}
public void setSkipInvalidRecords(boolean skipInvalidRecords) {
this.skipInvalidRecords = skipInvalidRecords;
}
/**
* @return a boolean indicating if the stream is available
* @throws IOException if an I/O error occurs.
*/
protected final boolean isStreamAvailable() throws IOException {
if (is == null || is.available() == 0) {
return false;
}
return true;
}
protected final int read(ByteArray record) throws IOException {
return read(record, 0, record.getLength());
}
protected final int read(ByteArray record, int displacement, int length)
throws IOException {
int count;
if (record.hasArray()) {
ByteArray.UnderlyingArray array = record.getArray();
count = read(array.value, array.offset + displacement, length);
} else {
byte[] buffer = getBuffer(length);
count = read(buffer, 0, length);
record.write(buffer, 0, count, displacement);
}
return count;
}
private byte[] getBuffer(int size) {
if (maxBuffer == null || maxBuffer.length < size) {
maxBuffer = new byte[size];
}
return maxBuffer;
}
private int read(byte[] data, int displacement, int length)
throws IOException {
int totalBytesRead = 0;
int bytesRemaining = length;
int currentDisplacement = displacement;
while (bytesRemaining > 0) {
int bytesRead = is.read(data, currentDisplacement, bytesRemaining);
if (bytesRead == -1) {
break;
}
bytesRemaining -= bytesRead;
currentDisplacement += bytesRead;
totalBytesRead += bytesRead;
}
bytesReadForRecord += totalBytesRead;
return totalBytesRead;
}
/**
* When a new record is read, it is passed through all of the filters in the
* list. Any filter can provide a replacement object for the original item.
* The new object will be the one passed to subsequent filters. If any
* filter returns null, then no more filters are called and the record is
* skipped and NOT returned to the caller.
*
* @param record
* @return
*/
private Record processFilters(Record record) {
for (RecordFilter filter : filterList) {
record = filter.filter(record);
if (record == null) {
return null;
}
}
return record;
}
/**
* Adds a filter to the list.
*
* @param recordFilter
*/
public void addFilter(RecordFilter recordFilter) {
filterList.add(recordFilter);
}
/**
* This method returns the next record in the stream. If there are no more
* records, return null.
*
* @return the next record in the stream or null.
* @throws IOException
* @throws EOFException
* @throws InvalidDataException
* @throws InvalidStandardLevelException
* @throws InvalidFormatException
*/
public Record nextRecord() throws IOException {
if (cachedRecord != null) {
Record record = cachedRecord;
cachedRecord = null;
return record;
}
Record record = null;
do {
if (!isStreamAvailable()) {
return null;
}
try {
bytesReadForRecord = 0;
record = readNextRecord();
record.recordPosition(++recordCount);
record.offsetPosition(offset);
offset += bytesReadForRecord;
record = processFilters(record);
} catch (InvalidFormatException e) {
if (!skipInvalidRecords) throw e;
// go ahead and update the offset counter and total record count
++recordCount;
offset += bytesReadForRecord;
}
} while (record == null);
return record;
}
protected abstract Record readNextRecord() throws IOException;
public Stream stream() {
return StreamSupport.stream(this.spliterator(), false);
}
public Iterator iterator() {
return new Iterator() {
public boolean hasNext() {
/*
* If we already have a record waiting to be retrieved, then
* return true;
*/
if (cachedRecord != null) {
return true;
}
/*
* Go see if we have a next record. If not, return false. If
* true, store it for use in the next pickup.
*/
try {
cachedRecord = nextRecord();
if (cachedRecord == null) {
return false;
}
} catch (Exception e) {
throw new RecordReaderException(e);
}
return true;
}
public Record next() {
Record record;
try {
record = nextRecord();
} catch (Exception e) {
throw new RecordReaderException(e);
}
if (record == null) {
throw new NoSuchElementException();
}
return record;
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}