/****************************************************************
* Licensed to the Apache Software Foundation (ASF) under one *
* or more contributor license agreements. See the NOTICE file *
* distributed with this work for additional information *
* regarding copyright ownership. The ASF licenses this file *
* to you 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. *
****************************************************************/
import java.io.IOException;
import java.io.InputStream;
/**
* An InputStream class that terminates the stream when it encounters a
* particular byte sequence.
*
* @version 1.0.0, 24/04/1999
*/
public class CharTerminatedInputStream
extends InputStream {
/**
* The wrapped input stream
*/
private InputStream in;
/**
* The terminating character array
*/
private int match[];
/**
* An array containing the last N characters read from the stream, where
* N is the length of the terminating character array
*/
private int buffer[];
/**
* The number of bytes that have been read that have not been placed
* in the internal buffer.
*/
private int pos = 0;
/**
* Whether the terminating sequence has been read from the stream
*/
private boolean endFound = false;
/**
* A constructor for this object that takes a stream to be wrapped
* and a terminating character sequence.
*
* @param in the <code>InputStream</code> to be wrapped
* @param terminator the array of characters that will terminate the stream.
*
* @throws IllegalArgumentException if the terminator array is null or empty
*/
public CharTerminatedInputStream(InputStream in, char[] terminator) {
if (terminator == null) {
throw new IllegalArgumentException("The terminating character array cannot be null.");
}
if (terminator.length == 0) {
throw new IllegalArgumentException("The terminating character array cannot be of zero length.");
}
match = new int[terminator.length];
buffer = new int[terminator.length];
for (int i = 0; i < terminator.length; i++) {
match[i] = (int)terminator[i];
buffer[i] = (int)terminator[i];
}
this.in = in;
}
/**
* Read a byte off this stream.
*
* @return the byte read off the stream
* @throws IOException if an IOException is encountered while reading off the stream
* @throws ProtocolException if the underlying stream returns -1 before the terminator is seen.
*/
public int read() throws IOException {
if (endFound) {
//We've found the match to the terminator
return -1;
}
if (pos == 0) {
//We have no data... read in a record
int b = in.read();
if (b == -1) {
//End of stream reached without seeing the terminator
throw new java.net.ProtocolException("pre-mature end of data");
}
if (b != match[0]) {
//this char is not the first char of the match
return b;
}
//this is a match...put this in the first byte of the buffer,
// and fall through to matching logic
buffer[0] = b;
pos++;
} else {
if (buffer[0] != match[0]) {
//Maybe from a previous scan, there is existing data,
// and the first available char does not match the
// beginning of the terminating string.
return topChar();
}
//we have a match... fall through to matching logic.
}
//MATCHING LOGIC
//The first character is a match... scan for complete match,
// reading extra chars as needed, until complete match is found
for (int i = 0; i < match.length; i++) {
if (i >= pos) {
int b = in.read();
if (b == -1) {
//end of stream found, so match cannot be fulfilled.
// note we don't set endFound, because otherwise
// remaining part of buffer won't be returned.
return topChar();
}
//put the read char in the buffer
buffer[pos] = b;
pos++;
}
if (buffer[i] != match[i]) {
//we did not find a match... return the top char
return topChar();
}
}
//A complete match was made...
endFound = true;
return -1;
}
/**
* Private helper method to update the internal buffer of last read characters
*
* @return the byte that was previously at the front of the internal buffer
*/
private int topChar() {
int b = buffer[0];
if (pos > 1) {
//copy down the buffer to keep the fresh data at top
System.arraycopy(buffer, 1, buffer, 0, pos - 1);
}
pos--;
return b;
}
}