1/*
2 * Copyright (C) 2014 Square, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package okio;
17
18import java.io.EOFException;
19import java.io.IOException;
20import java.io.InputStream;
21
22import static okio.Util.checkOffsetAndCount;
23
24final class RealBufferedSource implements BufferedSource {
25  public final OkBuffer buffer;
26  public final Source source;
27  private boolean closed;
28
29  public RealBufferedSource(Source source, OkBuffer buffer) {
30    if (source == null) throw new IllegalArgumentException("source == null");
31    this.buffer = buffer;
32    this.source = source;
33  }
34
35  public RealBufferedSource(Source source) {
36    this(source, new OkBuffer());
37  }
38
39  @Override public OkBuffer buffer() {
40    return buffer;
41  }
42
43  @Override public long read(OkBuffer sink, long byteCount) throws IOException {
44    if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
45    if (closed) throw new IllegalStateException("closed");
46
47    if (buffer.size == 0) {
48      long read = source.read(buffer, Segment.SIZE);
49      if (read == -1) return -1;
50    }
51
52    long toRead = Math.min(byteCount, buffer.size);
53    return buffer.read(sink, toRead);
54  }
55
56  @Override public boolean exhausted() throws IOException {
57    if (closed) throw new IllegalStateException("closed");
58    return buffer.exhausted() && source.read(buffer, Segment.SIZE) == -1;
59  }
60
61  @Override public void require(long byteCount) throws IOException {
62    if (closed) throw new IllegalStateException("closed");
63    while (buffer.size < byteCount) {
64      if (source.read(buffer, Segment.SIZE) == -1) throw new EOFException();
65    }
66  }
67
68  @Override public byte readByte() throws IOException {
69    require(1);
70    return buffer.readByte();
71  }
72
73  @Override public ByteString readByteString(long byteCount) throws IOException {
74    require(byteCount);
75    return buffer.readByteString(byteCount);
76  }
77
78  @Override public String readUtf8(long byteCount) throws IOException {
79    require(byteCount);
80    return buffer.readUtf8(byteCount);
81  }
82
83  @Override public String readUtf8Line() throws IOException {
84    long newline = indexOf((byte) '\n');
85
86    if (newline == -1) {
87      return buffer.size != 0 ? readUtf8(buffer.size) : null;
88    }
89
90    return buffer.readUtf8Line(newline);
91  }
92
93  @Override public String readUtf8LineStrict() throws IOException {
94    long newline = indexOf((byte) '\n');
95    if (newline == -1L) throw new EOFException();
96    return buffer.readUtf8Line(newline);
97  }
98
99  @Override public short readShort() throws IOException {
100    require(2);
101    return buffer.readShort();
102  }
103
104  @Override public short readShortLe() throws IOException {
105    require(2);
106    return buffer.readShortLe();
107  }
108
109  @Override public int readInt() throws IOException {
110    require(4);
111    return buffer.readInt();
112  }
113
114  @Override public int readIntLe() throws IOException {
115    require(4);
116    return buffer.readIntLe();
117  }
118
119  @Override public long readLong() throws IOException {
120    require(8);
121    return buffer.readLong();
122  }
123
124  @Override public long readLongLe() throws IOException {
125    require(8);
126    return buffer.readLongLe();
127  }
128
129  @Override public void skip(long byteCount) throws IOException {
130    if (closed) throw new IllegalStateException("closed");
131    while (byteCount > 0) {
132      if (buffer.size == 0 && source.read(buffer, Segment.SIZE) == -1) {
133        throw new EOFException();
134      }
135      long toSkip = Math.min(byteCount, buffer.size());
136      buffer.skip(toSkip);
137      byteCount -= toSkip;
138    }
139  }
140
141  @Override public long indexOf(byte b) throws IOException {
142    if (closed) throw new IllegalStateException("closed");
143    long start = 0;
144    long index;
145    while ((index = buffer.indexOf(b, start)) == -1) {
146      start = buffer.size;
147      if (source.read(buffer, Segment.SIZE) == -1) return -1L;
148    }
149    return index;
150  }
151
152  @Override public InputStream inputStream() {
153    return new InputStream() {
154      @Override public int read() throws IOException {
155        if (closed) throw new IOException("closed");
156        if (buffer.size == 0) {
157          long count = source.read(buffer, Segment.SIZE);
158          if (count == -1) return -1;
159        }
160        return buffer.readByte() & 0xff;
161      }
162
163      @Override public int read(byte[] data, int offset, int byteCount) throws IOException {
164        if (closed) throw new IOException("closed");
165        checkOffsetAndCount(data.length, offset, byteCount);
166
167        if (buffer.size == 0) {
168          long count = source.read(buffer, Segment.SIZE);
169          if (count == -1) return -1;
170        }
171
172        return buffer.read(data, offset, byteCount);
173      }
174
175      @Override public int available() throws IOException {
176        if (closed) throw new IOException("closed");
177        return (int) Math.min(buffer.size, Integer.MAX_VALUE);
178      }
179
180      @Override public void close() throws IOException {
181        RealBufferedSource.this.close();
182      }
183
184      @Override public String toString() {
185        return RealBufferedSource.this + ".inputStream()";
186      }
187    };
188  }
189
190  @Override public Source deadline(Deadline deadline) {
191    source.deadline(deadline);
192    return this;
193  }
194
195  @Override public void close() throws IOException {
196    if (closed) return;
197    closed = true;
198    source.close();
199    buffer.clear();
200  }
201
202  @Override public String toString() {
203    return "buffer(" + source + ")";
204  }
205}
206