Java tutorial
// ======================================================================== // $Id: LineInput.java,v 1.17 2005/10/05 11:32:40 gregwilkins Exp $ // Copyright 1996-2004 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // 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 net.lightbody.bmp.proxy.jetty.util; import net.lightbody.bmp.proxy.jetty.log.LogFactory; import org.apache.commons.logging.Log; import java.io.*; /* ------------------------------------------------------------ */ /** Fast LineInput InputStream. * This buffered InputStream provides methods for reading lines * of bytes. The lines can be converted to String or character * arrays either using the default encoding or a user supplied * encoding. * * Buffering and data copying are highly optimized, making this * an ideal class for protocols that mix character encoding lines * with arbitrary byte data (eg HTTP). * * The buffer size is also the maximum line length in bytes and/or * characters. If the byte length of a line is less than the max, * but the character length is greater, than then trailing characters * are lost. * * Line termination is forgiving and accepts CR, LF, CRLF or EOF. * Line input uses the mark/reset mechanism, so any marks set * prior to a readLine call are lost. * * @version $Id: LineInput.java,v 1.17 2005/10/05 11:32:40 gregwilkins Exp $ * @author Greg Wilkins (gregw) */ public class LineInput extends FilterInputStream { private static Log log = LogFactory.getLog(LineInput.class); /* ------------------------------------------------------------ */ private byte _buf[]; private ByteBuffer _byteBuffer; private InputStreamReader _reader; private int _mark = -1; // reset marker private int _pos; // Start marker private int _avail; // Available back marker, may be byte limited private int _contents; // Absolute back marker of buffer private int _byteLimit = -1; private boolean _newByteLimit; private LineBuffer _lineBuffer; private String _encoding; private boolean _eof = false; private boolean _lastCr = false; private boolean _seenCrLf = false; private final static int LF = 10; private final static int CR = 13; /* ------------------------------------------------------------ */ /** Constructor. * Default buffer and maximum line size is 2048. * @param in The underlying input stream. */ public LineInput(InputStream in) { this(in, 0); } /* ------------------------------------------------------------ */ /** Constructor. * @param in The underlying input stream. * @param bufferSize The buffer size and maximum line length. */ public LineInput(InputStream in, int bufferSize) { super(in); _mark = -1; if (bufferSize == 0) bufferSize = 8192; _buf = ByteArrayPool.getByteArray(bufferSize); _byteBuffer = new ByteBuffer(_buf); _lineBuffer = new LineBuffer(bufferSize); try { _reader = new InputStreamReader(_byteBuffer, "UTF-8"); } catch (UnsupportedEncodingException e) { _reader = new InputStreamReader(_byteBuffer); } } /* ------------------------------------------------------------ */ /** Constructor. * @param in The underlying input stream. * @param bufferSize The buffer size and maximum line length. * @param encoding the character encoding to use for readLine methods. * @exception UnsupportedEncodingException */ public LineInput(InputStream in, int bufferSize, String encoding) throws UnsupportedEncodingException { super(in); _mark = -1; if (bufferSize == 0) bufferSize = 2048; _buf = ByteArrayPool.getByteArray(bufferSize); _byteBuffer = new ByteBuffer(_buf); _lineBuffer = new LineBuffer(bufferSize); _reader = new InputStreamReader(_byteBuffer, encoding); _encoding = encoding; } /* ------------------------------------------------------------ */ public InputStream getInputStream() { return in; } /* ------------------------------------------------------------ */ /** Set the byte limit. * If set, only this number of bytes are read before EOF. * @param bytes Limit number of bytes, or -1 for no limit. */ public void setByteLimit(int bytes) { _byteLimit = bytes; if (bytes >= 0) { _newByteLimit = true; _byteLimit -= _contents - _pos; if (_byteLimit < 0) { _avail += _byteLimit; _byteLimit = 0; } } else { _newByteLimit = false; _avail = _contents; _eof = false; } } /* ------------------------------------------------------------ */ /** Get the byte limit. * @return Number of bytes until EOF is returned or -1 for no limit. */ public int getByteLimit() { if (_byteLimit < 0) return _byteLimit; return _byteLimit + _avail - _pos; } /* ------------------------------------------------------------ */ /** Read a line ended by CR, LF or CRLF. * The default or supplied encoding is used to convert bytes to * characters. * @return The line as a String or null for EOF. * @exception IOException */ public synchronized String readLine() throws IOException { int len = fillLine(_buf.length); if (len < 0) return null; String s = null; if (_encoding == null) s = new String(_buf, _mark, len); else { try { s = new String(_buf, _mark, len, _encoding); } catch (UnsupportedEncodingException e) { log.warn(LogSupport.EXCEPTION, e); } } _mark = -1; return s; } /* ------------------------------------------------------------ */ /** Read a line ended by CR, LF or CRLF. * The default or supplied encoding is used to convert bytes to * characters. * @param c Character buffer to place the line into. * @param off Offset into the buffer. * @param len Maximum length of line. * @return The length of the line or -1 for EOF. * @exception IOException */ public int readLine(char[] c, int off, int len) throws IOException { int blen = fillLine(len); if (blen < 0) return -1; if (blen == 0) return 0; _byteBuffer.setStream(_mark, blen); int read = 0; while (read < len && _reader.ready()) { int r = _reader.read(c, off + read, len - read); if (r <= 0) break; read += r; } _mark = -1; return read; } /* ------------------------------------------------------------ */ /** Read a line ended by CR, LF or CRLF. * @param b Byte array to place the line into. * @param off Offset into the buffer. * @param len Maximum length of line. * @return The length of the line or -1 for EOF. * @exception IOException */ public int readLine(byte[] b, int off, int len) throws IOException { len = fillLine(len); if (len < 0) return -1; if (len == 0) return 0; System.arraycopy(_buf, _mark, b, off, len); _mark = -1; return len; } /* ------------------------------------------------------------ */ /** Read a Line ended by CR, LF or CRLF. * Read a line into a shared LineBuffer instance. The LineBuffer is * resused between calls and should not be held by the caller. * The default or supplied encoding is used to convert bytes to * characters. * @return LineBuffer instance or null for EOF. * @exception IOException */ public LineBuffer readLineBuffer() throws IOException { return readLineBuffer(_buf.length); } /* ------------------------------------------------------------ */ /** Read a Line ended by CR, LF or CRLF. * Read a line into a shared LineBuffer instance. The LineBuffer is * resused between calls and should not be held by the caller. * The default or supplied encoding is used to convert bytes to * characters. * @param len Maximum length of a line, or 0 for default * @return LineBuffer instance or null for EOF. * @exception IOException */ public LineBuffer readLineBuffer(int len) throws IOException { len = fillLine(len > 0 ? len : _buf.length); if (len < 0) return null; if (len == 0) { _lineBuffer.size = 0; return _lineBuffer; } _byteBuffer.setStream(_mark, len); _lineBuffer.size = 0; int read = 0; while (read < len && _reader.ready()) { int r = _reader.read(_lineBuffer.buffer, read, len - read); if (r <= 0) break; read += r; } _lineBuffer.size = read; _mark = -1; return _lineBuffer; } /* ------------------------------------------------------------ */ public synchronized int read() throws IOException { int b; if (_pos >= _avail) fill(); if (_pos >= _avail) b = -1; else b = _buf[_pos++] & 255; return b; } /* ------------------------------------------------------------ */ public synchronized int read(byte b[], int off, int len) throws IOException { int avail = _avail - _pos; if (avail <= 0) { fill(); avail = _avail - _pos; } if (avail <= 0) len = -1; else { len = (avail < len) ? avail : len; System.arraycopy(_buf, _pos, b, off, len); _pos += len; } return len; } /* ------------------------------------------------------------ */ public long skip(long n) throws IOException { int avail = _avail - _pos; if (avail <= 0) { fill(); avail = _avail - _pos; } if (avail <= 0) n = 0; else { n = (avail < n) ? avail : n; _pos += n; } return n; } /* ------------------------------------------------------------ */ public synchronized int available() throws IOException { int in_stream = in.available(); if (_byteLimit >= 0 && in_stream > _byteLimit) in_stream = _byteLimit; return _avail - _pos + in_stream; } /* ------------------------------------------------------------ */ public synchronized void mark(int limit) throws IllegalArgumentException { if (limit > _buf.length) { byte[] new_buf = new byte[limit]; System.arraycopy(_buf, _pos, new_buf, _pos, _avail - _pos); _buf = new_buf; if (_byteBuffer != null) _byteBuffer.setBuffer(_buf); } _mark = _pos; } /* ------------------------------------------------------------ */ public synchronized void reset() throws IOException { if (_mark < 0) throw new IOException("Resetting to invalid mark"); _pos = _mark; _mark = -1; } /* ------------------------------------------------------------ */ public boolean markSupported() { return true; } /* ------------------------------------------------------------ */ private void fill() throws IOException { // if the mark is in the middle of the buffer if (_mark > 0) { // moved saved bytes to start of buffer int saved = _contents - _mark; System.arraycopy(_buf, _mark, _buf, 0, saved); _pos -= _mark; _avail -= _mark; _contents = saved; _mark = 0; } else if (_mark < 0 && _pos > 0) { // move remaining bytes to start of buffer int saved = _contents - _pos; System.arraycopy(_buf, _pos, _buf, 0, saved); _avail -= _pos; _contents = saved; _pos = 0; } else if (_mark == 0 && _pos > 0 && _contents == _buf.length) { // Discard the mark as we need the space. _mark = -1; fill(); return; } // Get ready to top up the buffer int n = 0; _eof = false; // Handle byte limited EOF if (_byteLimit == 0) _eof = true; // else loop until something is read. else while (!_eof && n == 0 && _buf.length > _contents) { // try to read as much as will fit. int space = _buf.length - _contents; n = in.read(_buf, _contents, space); if (n <= 0) { // If no bytes - we could be NBIO, so we want to avoid // a busy loop. if (n == 0) { // Yield to give a chance for some bytes to turn up Thread.yield(); // Do a byte read as that is blocking int b = in.read(); if (b >= 0) { n = 1; _buf[_contents++] = (byte) b; } else _eof = true; } else _eof = true; } else _contents += n; _avail = _contents; // If we have a byte limit if (_byteLimit > 0) { // adjust the bytes available if (_contents - _pos >= _byteLimit) _avail = _byteLimit + _pos; if (n > _byteLimit) _byteLimit = 0; else if (n >= 0) _byteLimit -= n; else if (n == -1) throw new IOException("Premature EOF"); } } // If we have some characters and the last read was a CR and // the first char is a LF, skip it if (_avail - _pos > 0 && _lastCr && _buf[_pos] == LF) { _seenCrLf = true; _pos++; if (_mark >= 0) _mark++; _lastCr = false; // If the byte limit has just been imposed, dont count // LF as content. if (_byteLimit >= 0 && _newByteLimit) { if (_avail < _contents) _avail++; else _byteLimit++; } // If we ate all that ws filled, fill some more if (_pos == _avail) fill(); } _newByteLimit = false; } /* ------------------------------------------------------------ */ private int fillLine(int maxLen) throws IOException { _mark = _pos; if (_pos >= _avail) fill(); if (_pos >= _avail) return -1; byte b; boolean cr = _lastCr; boolean lf = false; _lastCr = false; int len = 0; LineLoop: while (_pos <= _avail) { // if we have gone past the end of the buffer while (_pos == _avail) { // If EOF or no more space in the buffer, // return a line. if (_eof || (_mark == 0 && _contents == _buf.length)) { _lastCr = !_eof && _buf[_avail - 1] == CR; cr = true; lf = true; break LineLoop; } // If we have a CR and no more characters are available if (cr && in.available() == 0 && !_seenCrLf) { _lastCr = true; cr = true; lf = true; break LineLoop; } else { // Else just wait for more... _pos = _mark; fill(); _pos = len; cr = false; } } // Get the byte b = _buf[_pos++]; switch (b) { case LF: if (cr) _seenCrLf = true; lf = true; break LineLoop; case CR: if (cr) { // Double CR if (_pos > 1) { _pos--; break LineLoop; } } cr = true; break; default: if (cr) { if (_pos == 1) cr = false; else { _pos--; break LineLoop; } } len++; if (len == maxLen) { // look for EOL if (_mark != 0 && _pos + 2 >= _avail && _avail < _buf.length) fill(); if (_pos < _avail && _buf[_pos] == CR) { cr = true; _pos++; } if (_pos < _avail && _buf[_pos] == LF) { lf = true; _pos++; } if (!cr && !lf) { // fake EOL lf = true; cr = true; } break LineLoop; } break; } } if (!cr && !lf && len == 0) len = -1; return len; } /* ------------------------------------------------------------ */ private static class ByteBuffer extends ByteArrayInputStream { ByteBuffer(byte[] buffer) { super(buffer); } void setBuffer(byte[] buffer) { buf = buffer; } void setStream(int offset, int length) { pos = offset; count = offset + length; mark = -1; } } /* ------------------------------------------------------------ */ /** Reusable LineBuffer. * Externalized LineBuffer for fast line parsing. */ public static class LineBuffer { public char[] buffer; public int size; public LineBuffer(int maxLineLength) { buffer = new char[maxLineLength]; } public String toString() { return new String(buffer, 0, size); } } /* ------------------------------------------------------------ */ public void destroy() { ByteArrayPool.returnByteArray(_buf); _byteBuffer = null; _reader = null; _lineBuffer = null; _encoding = null; } }