17899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath/* 27899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * Copyright (C) 2012 The Android Open Source Project 37899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * 47899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * Licensed under the Apache License, Version 2.0 (the "License"); 57899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * you may not use this file except in compliance with the License. 67899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * You may obtain a copy of the License at 77899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * 87899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * http://www.apache.org/licenses/LICENSE-2.0 97899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * 107899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * Unless required by applicable law or agreed to in writing, software 117899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * distributed under the License is distributed on an "AS IS" BASIS, 127899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 137899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * See the License for the specific language governing permissions and 147899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * limitations under the License. 157899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath */ 167899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath 172231db3e6bb54447a9b14cf004a6cb03c373651cjwilsonpackage com.squareup.okhttp.internal; 187899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath 197899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamathimport java.io.ByteArrayOutputStream; 207899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamathimport java.io.Closeable; 217899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamathimport java.io.EOFException; 227899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamathimport java.io.IOException; 237899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamathimport java.io.InputStream; 24faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamathimport java.io.UnsupportedEncodingException; 257899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamathimport java.nio.charset.Charset; 267899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath 277899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath/** 287899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * Buffers input from an {@link InputStream} for reading lines. 297899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * 30faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * <p>This class is used for buffered reading of lines. For purposes of this class, a line ends with 317899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * "\n" or "\r\n". End of input is reported by throwing {@code EOFException}. Unterminated line at 327899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * end of input is invalid and will be ignored, the caller may use {@code hasUnterminatedLine()} 337899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * to detect it after catching the {@code EOFException}. 347899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * 35faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * <p>This class is intended for reading input that strictly consists of lines, such as line-based 36faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * cache entries or cache journal. Unlike the {@link java.io.BufferedReader} which in conjunction 37faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * with {@link java.io.InputStreamReader} provides similar functionality, this class uses different 387899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * end-of-input reporting and a more restrictive definition of a line. 397899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * 40faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * <p>This class supports only charsets that encode '\r' and '\n' as a single byte with value 13 417899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * and 10, respectively, and the representation of no other character contains these values. 427899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * We currently check in constructor that the charset is one of US-ASCII, UTF-8 and ISO-8859-1. 437899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath * The default charset is US_ASCII. 447899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath */ 457899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamathpublic class StrictLineReader implements Closeable { 4654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private static final byte CR = (byte) '\r'; 4754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private static final byte LF = (byte) '\n'; 4854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 4954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private final InputStream in; 5054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private final Charset charset; 5154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 52faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath /* 53faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * Buffered data is stored in {@code buf}. As long as no exception occurs, 0 <= pos <= end 54faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * and the data in the range [pos, end) is buffered for reading. At end of input, if there is 55faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * an unterminated line, we set end == -1, otherwise end == pos. If the underlying 56faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * {@code InputStream} throws an {@code IOException}, end may remain as either pos or -1. 57faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath */ 5854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private byte[] buf; 5954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private int pos; 6054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private int end; 6154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 6254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** 6354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * Constructs a new {@code LineReader} with the specified charset and the default capacity. 6454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * 6554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @param in the {@code InputStream} to read data from. 66faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are 67faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * supported. 6854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @throws NullPointerException if {@code in} or {@code charset} is null. 6954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @throws IllegalArgumentException if the specified charset is not supported. 7054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson */ 7154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public StrictLineReader(InputStream in, Charset charset) { 7254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson this(in, 8192, charset); 7354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 7454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 7554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** 7654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * Constructs a new {@code LineReader} with the specified capacity and charset. 7754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * 7854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @param in the {@code InputStream} to read data from. 7954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @param capacity the capacity of the buffer. 80faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are 81faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * supported. 8254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @throws NullPointerException if {@code in} or {@code charset} is null. 8354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @throws IllegalArgumentException if {@code capacity} is negative or zero 84faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * or the specified charset is not supported. 8554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson */ 8654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public StrictLineReader(InputStream in, int capacity, Charset charset) { 8754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (in == null || charset == null) { 8854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson throw new NullPointerException(); 897899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath } 9054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (capacity < 0) { 9154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson throw new IllegalArgumentException("capacity <= 0"); 927899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath } 93faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath if (!(charset.equals(Util.US_ASCII))) { 9454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson throw new IllegalArgumentException("Unsupported encoding"); 957899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath } 967899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath 9754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson this.in = in; 9854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson this.charset = charset; 9954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson buf = new byte[capacity]; 10054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 10154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 10254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** 10354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * Closes the reader by closing the underlying {@code InputStream} and 10454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * marking this reader as closed. 10554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * 10654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @throws IOException for errors when closing the underlying {@code InputStream}. 10754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson */ 10854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public void close() throws IOException { 10954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson synchronized (in) { 11054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (buf != null) { 11154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson buf = null; 11254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson in.close(); 11354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 1147899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath } 11554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 11654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 11754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** 11854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * Reads the next line. A line ends with {@code "\n"} or {@code "\r\n"}, 11954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * this end of line marker is not included in the result. 12054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * 12154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @return the next line from the input. 12254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @throws IOException for underlying {@code InputStream} errors. 12354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @throws EOFException for the end of source stream. 12454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson */ 12554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public String readLine() throws IOException { 12654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson synchronized (in) { 12754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (buf == null) { 12854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson throw new IOException("LineReader is closed"); 12954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 13054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 13154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson // Read more data if we are at the end of the buffered data. 13254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson // Though it's an error to read after an exception, we will let {@code fillBuf()} 13354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson // throw again if that happens; thus we need to handle end == -1 as well as end == pos. 13454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (pos >= end) { 13554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson fillBuf(); 13654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 13754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson // Try to find LF in the buffered data and return the line if successful. 13854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson for (int i = pos; i != end; ++i) { 13954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (buf[i] == LF) { 14054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson int lineEnd = (i != pos && buf[i - 1] == CR) ? i - 1 : i; 141faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath String res = new String(buf, pos, lineEnd - pos, charset.name()); 14254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson pos = i + 1; 14354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson return res; 1447899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath } 14554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 14654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 14754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson // Let's anticipate up to 80 characters on top of those already read. 14854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson ByteArrayOutputStream out = new ByteArrayOutputStream(end - pos + 80) { 149a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath @Override public String toString() { 15054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count; 151faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath try { 152faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath return new String(buf, 0, length, charset.name()); 153faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath } catch (UnsupportedEncodingException e) { 154faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath throw new AssertionError(e); // Since we control the charset this will never happen. 155faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath } 15654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 15754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson }; 15854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 15954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson while (true) { 16054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson out.write(buf, pos, end - pos); 16154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson // Mark unterminated line in case fillBuf throws EOFException or IOException. 16254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson end = -1; 16354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson fillBuf(); 16454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson // Try to find LF in the buffered data and return the line if successful. 16554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson for (int i = pos; i != end; ++i) { 16654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (buf[i] == LF) { 16754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (i != pos) { 16854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson out.write(buf, pos, i - pos); 1697899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath } 17054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson pos = i + 1; 17154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson return out.toString(); 17254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 1737899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath } 17454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 1757899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath } 17654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 17754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 17854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** 17954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * Read an {@code int} from a line containing its decimal representation. 18054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * 18154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @return the value of the {@code int} from the next line. 18254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @throws IOException for underlying {@code InputStream} errors or conversion error. 18354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * @throws EOFException for the end of source stream. 18454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson */ 18554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public int readInt() throws IOException { 18654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson String intString = readLine(); 18754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson try { 18854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson return Integer.parseInt(intString); 18954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } catch (NumberFormatException e) { 19054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson throw new IOException("expected an int but was \"" + intString + "\""); 1917899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath } 19254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 19354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 19454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson /** 19554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * Reads new input data into the buffer. Call only with pos == end or end == -1, 19654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * depending on the desired outcome if the function throws. 19754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson */ 19854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private void fillBuf() throws IOException { 19954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson int result = in.read(buf, 0, buf.length); 20054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (result == -1) { 20154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson throw new EOFException(); 2027899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath } 20354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson pos = 0; 20454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson end = result; 20554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 2067899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath} 2077899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath 208