// Copyright (C) 2003-2009 by Object Mentor, Inc. All rights reserved. // Released under the terms of the CPL Common Public License version 1.0. package util; import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; public class StreamReader implements Closeable { private InputStream input; private State state; ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream(); OutputStream output; private int readGoal; private int readStatus; // timeout limit in milli seconds // 0 = wait forever, never timeout private int timeoutLimit = 0; private boolean eof = false; private boolean isTimeout = false; private byte[] boundary; private int boundaryLength; private int matchingBoundaryIndex; private byte[] matchedBoundaryBytes; private long bytesConsumed; public StreamReader(InputStream input) { this.input = input; } @Override public void close() throws IOException { input.close(); } public void setTimeoutLimit(int timeout) { timeoutLimit = timeout; } public int timeoutLimit() { return timeoutLimit; } public boolean isTimeout() { return isTimeout; } public String readLine() throws IOException { return bytesToString(readLineBytes()); } public byte[] readLineBytes() throws IOException { state = READLINE_STATE; return preformRead(); } public String read(int count) throws IOException { return bytesToString(readBytes(count)); } public byte[] readBytes(int count) throws IOException { readGoal = count; readStatus = 0; state = READCOUNT_STATE; return preformRead(); } public void copyBytes(int count, OutputStream output) throws IOException { readGoal = count; state = READCOUNT_STATE; performCopy(output); } public String readUpTo(String boundary) throws IOException { return bytesToString(readBytesUpTo(boundary)); } public byte[] readBytesUpTo(String boundary) throws IOException { prepareForReadUpTo(boundary); return preformRead(); } private void prepareForReadUpTo(String boundary) { this.boundary = boundary.getBytes(); boundaryLength = this.boundary.length; matchedBoundaryBytes = new byte[boundaryLength]; matchingBoundaryIndex = 0; state = READUPTO_STATE; } public void copyBytesUpTo(String boundary, OutputStream outputStream) throws IOException { prepareForReadUpTo(boundary); performCopy(outputStream); } public int byteCount() { return byteBuffer.size(); } public byte[] getBufferedBytes() { return byteBuffer.toByteArray(); } private byte[] preformRead() throws IOException { setReadMode(); clearBuffer(); readUntilFinished(); return getBufferedBytes(); } private void performCopy(OutputStream output) throws IOException { setCopyMode(output); readUntilFinished(); } private void readUntilFinished() throws IOException { int retryCounter = 0; int sleepStep = 10; isTimeout = false; if (timeoutLimit > 0) { retryCounter = timeoutLimit / sleepStep; } else { retryCounter = 0; } while (!state.finished()) //TODO remove the true or make it used only for non SSL streams // Note: SSL sockets don't support the input.available() function :( if (timeoutLimit == 0 || input.available() != 0) { state.read(input); } else { try { Thread.sleep(sleepStep); } catch (InterruptedException e) { throw new InterruptedIOException("Interrupted while awaiting data: " + e.getMessage()); } retryCounter--; if (retryCounter <= 0) { isTimeout = true; changeState(FINAL_STATE); } } } private void clearBuffer() { byteBuffer.reset(); } private void setCopyMode(OutputStream output) { this.output = output; } private void setReadMode() { output = byteBuffer; } private String bytesToString(byte[] bytes) throws UnsupportedEncodingException { return new String(bytes, FileUtil.CHARENCODING); } private void changeState(State state) { this.state = state; } public boolean isEof() { return eof; } public long numberOfBytesConsumed() { return bytesConsumed; } public void resetNumberOfBytesConsumed() { bytesConsumed = 0; } private abstract static class State { public void read(InputStream input) throws IOException { } public boolean finished() { return false; } } private final State READLINE_STATE = new State() { @Override public void read(InputStream input) throws IOException { int b = input.read(); if (b == -1) { changeState(FINAL_STATE); eof = true; } else { bytesConsumed++; if (b == '\n') changeState(FINAL_STATE); else if (b != '\r') output.write((byte) b); } } }; private final State READCOUNT_STATE = new State() { @Override public void read(InputStream input) throws IOException { byte[] bytes = new byte[readGoal - readStatus]; int bytesRead = input.read(bytes); if (bytesRead < 0) { changeState(FINAL_STATE); eof = true; } else { bytesConsumed += bytesRead; readStatus += bytesRead; output.write(bytes, 0, bytesRead); } } @Override public boolean finished() { return readStatus >= readGoal; } }; private final State READUPTO_STATE = new State() { @Override public void read(InputStream input) throws IOException { int b = input.read(); if (b == -1) { changeState(FINAL_STATE); eof = true; } else { bytesConsumed++; if (b == boundary[matchingBoundaryIndex]) { matchedBoundaryBytes[matchingBoundaryIndex++] = (byte) b; if (matchingBoundaryIndex >= boundaryLength) changeState(FINAL_STATE); } else if (matchingBoundaryIndex == 0) output.write((byte) b); else { output.write(matchedBoundaryBytes, 0, matchingBoundaryIndex); matchingBoundaryIndex = 0; if (b == boundary[matchingBoundaryIndex]) matchedBoundaryBytes[matchingBoundaryIndex++] = (byte) b; else output.write((byte) b); } } } }; private final State FINAL_STATE = new State() { @Override public boolean finished() { return true; } }; }