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