All Downloads are FREE. Search and download functionalities are using the official Maven repository.

koncept.io.LineStreamer Maven / Gradle / Ivy

package koncept.io;

import java.io.IOException;
import java.io.InputStream;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.LinkedList;
import java.util.List;

/**
 * The resource should read ONLY EXACTLY as much as is required.
*
* N.B. This class will NOT close any underlying resources. * @author koncept * */ public class LineStreamer { private final InputStream wrapped; public static final byte[] crlf = "\r\n".getBytes(); private final byte[][] tokens; private final int size; //these are now stateful List buffHist = new LinkedList<>(); byte[] buff; // = new byte[size]; byte[] recent; // = new byte[maxTokenLength()]; int offsetRecent = 0; int index = 0; int stripLength = 0; public LineStreamer(InputStream wrapped) { this(wrapped, 1024, crlf); } public LineStreamer(InputStream wrapped, int size, byte[]... tokens) { this.wrapped = wrapped; this.size = size; this.tokens = tokens; buff = new byte[size]; recent = new byte[maxTokenLength()]; } public LineStreamer(InputStream wrapped, int size, String... tokens) { this.wrapped = wrapped; this.size = size; this.tokens = new byte[tokens.length][]; for(int i = 0; i < tokens.length; i++) { this.tokens[i] = tokens[i].getBytes(); //platform default encoding... err... not really what you want } buff = new byte[size]; recent = new byte[maxTokenLength()]; } /** * Strips off bytes one at a time and checks for the token as it goes along * @return * @throws IOException */ public byte[] readBytesToToken() throws IOException { return nonBlockingreadBytesToToken(-1); } public byte[] readBytesToToken(long durationMillis) throws IOException { if (durationMillis == -1L) return nonBlockingreadBytesToToken(-1); else return nonBlockingreadBytesToToken(System.currentTimeMillis() + durationMillis); } private byte[] nonBlockingreadBytesToToken(long endTimeMillis) throws IOException { Integer read = null; try { read = maybeNonBlockingRead(endTimeMillis); while (read != null && read.intValue() != -1) { buff[index] = read.byteValue(); recent[offsetRecent] = buff[index]; index++; offsetRecent = (offsetRecent + 1) % recent.length; if (index == size) { index = 0; buffHist.add(buff); buff = new byte[size]; } int tokenIndex = tokenIndex(offsetRecent, recent); if (tokenIndex != -1) { stripLength = tokens[tokenIndex].length; break; } read = maybeNonBlockingRead(endTimeMillis); } //end of stream or token reached } catch (SocketTimeoutException e) { //on read timeout, socket will be closed, so just force the abort return null; //return null for end of stream } if (read == null) return null; //return null for end of stream (read timed out) //didn't read any bytes, return null for end of stream if (buffHist.isEmpty() && index == 0) return null; index -= stripLength; if (index < 0) { //if a -ve index, do a pull back from the buffHist buff = buffHist.remove(buffHist.size() - 1); index += size; } //join the array components into one line int newArraySize = buffHist.size() * size + index; byte[] result = new byte[newArraySize]; for(int i = 0; i < buffHist.size(); i++) { System.arraycopy(buffHist.get(i), 0, result, i * size, size); } System.arraycopy(buff, 0, result, buffHist.size() * size, index); //reset internal state //no need to clean a byte[] bufers contents - we scope read/writes correctly buffHist.clear(); // clear(buff); // = new byte[size]; // clear(recent); // = new byte[maxTokenLength()]; offsetRecent = 0; index = 0; stripLength = 0; return result; } protected Integer maybeNonBlockingRead(long endTimeMillis) throws IOException { try { byte[] b = new byte[1]; int read = wrapped.read(b); //**IF** the underlying is non blocking on this read, we will read 0 bytes. otherwise... we are screwed (and need nio or nio2) if (read == -1) return -1; if (read == 1) return (int)b[0]; while (endTimeMillis == -1 || System.currentTimeMillis() < endTimeMillis) { //do mini wait try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } read = wrapped.read(b); if (read == -1) return -1; if (read == 1) return (int)b[0]; } } catch (SocketException e) { if (e.getMessage().startsWith("Software caused connection abort")) return null; if (e.getMessage().equals("Connection reset")) return null; throw e; } return null; //timeout!! } //TODO: need to protect against zeros matching zeroes (!!) protected int tokenIndex(int startOffset, byte[] scan) { for(int i = 0; i < tokens.length; i++) { if (isToken(startOffset, scan, tokens[i])) return i; } return -1; } protected boolean isToken(int startOffset, byte[] scan, byte[] token) { startOffset += (scan.length - token.length); for(int i = 0; i < token.length; i++) { if (token[i] != scan[(i + startOffset) % scan.length]) return false; } return true; } protected int maxTokenLength() { int length = 0; for(byte[] token: tokens) length = Math.max(length, token.length); return length; } protected String convertToString(byte[] b) { if (b == null) return null; if (b.length == 0) return ""; return new String(b); } public String readLine() throws IOException { return convertToString(readBytesToToken()); } public String readLine(long durationMillis) throws IOException { return convertToString(readBytesToToken(durationMillis)); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy