com.jsftoolkit.utils.DelimitedReader Maven / Gradle / Ivy
package com.jsftoolkit.utils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.io.PushbackReader;
/**
* A DelimitedReader wraps another Reader and allows reading from that Reader,
* up to a given delimeter (a byte sequence).
*
* It is useful for wrapping Readers that you want to pass to a Scanner or other
* buffering reader but that will need to be accessed directly later. i.e. you
* need a Scanner but aren't willing to give up all the data.
*
* This class will never buffer ahead, so it may be desirable to wrap
* the passed in reader in a {@link BufferedReader} before handing it to this
* class.
*
* The matched delimeter can be obtained by calling {@link #getMatch()} after
* EOS is returned.
*
* @author noah
*
*/
public class DelimitedReader extends Reader {
private final String delimeter; // the delimeter to look for
private final PushbackReader reader; // stream to read bytes from
private final boolean[] mask;
private char[] match = null;
/**
*
* @param in
* @param delimeter
*/
public DelimitedReader(Reader in, String delimeter) {
this(new PushbackReader(in, delimeter.length()), delimeter);
}
/**
* Assumes the given delimeter should be matched exactly.
*
* @param in
* @param delimeter
*/
public DelimitedReader(PushbackReader in, String delimeter) {
this(in, delimeter, Utils.fill(new boolean[delimeter.length()], true));
}
/**
*
* @param reader
* a {@link PushbackReader}. Note that the pushback buffer must
* be at least as long as delimeter - 1
* @param delimeter
* the delimeter to halt after
* @param mask
* the delimeter mask. Indicates if the char at the given
* position should be matched (true) or not matched (false).
*/
public DelimitedReader(PushbackReader in, String delimeter, boolean[] mask) {
this.reader = in;
this.delimeter = delimeter;
this.mask = mask;
}
/**
* Wraps the reader in a {@link PushbackReader} and calls
* {@link #DelimitedReader(PushbackReader, String, boolean[])}.
*
* @param in
* @param delimeter
* @param mask
*/
public DelimitedReader(Reader in, String delimeter, boolean[] mask) {
this(new PushbackReader(in, delimeter.length()), delimeter, mask);
}
@Override
/**
* Obeys the general contract of read. Returns -1 at end of stream.
*/
public int read() throws IOException {
if (match == null) {
int read = reader.read();
if (read == -1) {
match = new char[0];
} else if (matches(read, 0)) {
// if the char matches the first character of the delimeter, see
// if the whole delimeter is there
int[] buf = new int[delimeter.length()];
buf[0] = read;
int i = 1;
while (i < delimeter.length()
&& matches((buf[i] = reader.read()), i)) {
i++;
}
if (i == delimeter.length()) {
// matched, so return EOS
match = Utils.toCharArray(buf);
return -1;
} else {
// not a match, so we unread the extra chars
for (int j = i; j > 0; j--) {
reader.unread(buf[j]);
}
}
}
return read;
}
return -1;
}
protected boolean matches(int b, int index) {
return mask[index] ? delimeter.charAt(index) == b : b != -1
&& delimeter.charAt(index) != b;
}
@Override
/**
* Reads up to the delimeter or EOS. In other words, consumes all the bytes
* up to and including the delimeter. Does not close the underlying stream.
*/
public void close() throws IOException {
while (read() != -1)
;
}
/**
* Returns the bytes the matched the delimeter (useful when you specify a
* mask that is not all matching).
*
* This method returns null if the EOS or delimeter has not been reached. It
* returns an empty array if the delimeter was not matched. Note that EOS
* will not match anything, even if the mask bit for the last character is
* set to not match. i.e. If only one more character is needed to match the
* delimeter but the EOS is reached, then this method returns an empty
* array.
*
* @return
*/
public char[] getMatch() {
return match;
}
/**
* Returns the wrapped reader. When this ({@link DelimitedReader}) reader
* reaches the end of stream, the next character returned by the wrapped
* reader will be the character immediately following the delimeter. Note
* that this reader may be different from the one passed into the
* constructor.
*
* @return the wrapped reader.
*/
public PushbackReader getReader() {
return reader;
}
@Override
public int read(char[] cbuf, int off, int len) throws IOException {
int i;
int read = -1;
for (i = 0; i < len && (read = read()) != -1; i++) {
cbuf[i + off] = (char) read;
}
return read == -1 && i == 0 ? -1 : i;
}
}