AbstractSessionInputBuffer.java revision 069490a5ca2fd1988d29daf45d892f47ad665115
1/*
2 * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/AbstractSessionInputBuffer.java $
3 * $Revision: 576077 $
4 * $Date: 2007-09-16 04:50:22 -0700 (Sun, 16 Sep 2007) $
5 *
6 * ====================================================================
7 * Licensed to the Apache Software Foundation (ASF) under one
8 * or more contributor license agreements.  See the NOTICE file
9 * distributed with this work for additional information
10 * regarding copyright ownership.  The ASF licenses this file
11 * to you under the Apache License, Version 2.0 (the
12 * "License"); you may not use this file except in compliance
13 * with the License.  You may obtain a copy of the License at
14 *
15 *   http://www.apache.org/licenses/LICENSE-2.0
16 *
17 * Unless required by applicable law or agreed to in writing,
18 * software distributed under the License is distributed on an
19 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20 * KIND, either express or implied.  See the License for the
21 * specific language governing permissions and limitations
22 * under the License.
23 * ====================================================================
24 *
25 * This software consists of voluntary contributions made by many
26 * individuals on behalf of the Apache Software Foundation.  For more
27 * information on the Apache Software Foundation, please see
28 * <http://www.apache.org/>.
29 *
30 */
31
32package org.apache.http.impl.io;
33
34import java.io.IOException;
35import java.io.InputStream;
36
37import org.apache.http.io.SessionInputBuffer;
38import org.apache.http.io.HttpTransportMetrics;
39import org.apache.http.params.CoreConnectionPNames;
40import org.apache.http.params.HttpParams;
41import org.apache.http.params.HttpProtocolParams;
42import org.apache.http.protocol.HTTP;
43import org.apache.http.util.ByteArrayBuffer;
44import org.apache.http.util.CharArrayBuffer;
45
46/**
47 * Abstract base class for session input buffers that stream data
48 * from a {@link InputStream}.
49 *
50 * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a>
51 *
52 */
53public abstract class AbstractSessionInputBuffer implements SessionInputBuffer {
54
55    private InputStream instream;
56    private byte[] buffer;
57    private int bufferpos;
58    private int bufferlen;
59
60    private ByteArrayBuffer linebuffer = null;
61
62    private String charset = HTTP.US_ASCII;
63    private boolean ascii = true;
64    private int maxLineLen = -1;
65
66    private HttpTransportMetricsImpl metrics;
67
68    protected void init(final InputStream instream, int buffersize, final HttpParams params) {
69        if (instream == null) {
70            throw new IllegalArgumentException("Input stream may not be null");
71        }
72        if (buffersize <= 0) {
73            throw new IllegalArgumentException("Buffer size may not be negative or zero");
74        }
75        if (params == null) {
76            throw new IllegalArgumentException("HTTP parameters may not be null");
77        }
78        this.instream = instream;
79        this.buffer = new byte[buffersize];
80        this.bufferpos = 0;
81        this.bufferlen = 0;
82        this.linebuffer = new ByteArrayBuffer(buffersize);
83        this.charset = HttpProtocolParams.getHttpElementCharset(params);
84        this.ascii = this.charset.equalsIgnoreCase(HTTP.US_ASCII)
85                     || this.charset.equalsIgnoreCase(HTTP.ASCII);
86        this.maxLineLen = params.getIntParameter(CoreConnectionPNames.MAX_LINE_LENGTH, -1);
87        this.metrics = new HttpTransportMetricsImpl();
88    }
89
90    protected int fillBuffer() throws IOException {
91        // compact the buffer if necessary
92        if (this.bufferpos > 0) {
93            int len = this.bufferlen - this.bufferpos;
94            if (len > 0) {
95                System.arraycopy(this.buffer, this.bufferpos, this.buffer, 0, len);
96            }
97            this.bufferpos = 0;
98            this.bufferlen = len;
99        }
100        int l;
101        int off = this.bufferlen;
102        int len = this.buffer.length - off;
103        l = this.instream.read(this.buffer, off, len);
104        if (l == -1) {
105            return -1;
106        } else {
107            this.bufferlen = off + l;
108            this.metrics.incrementBytesTransferred(l);
109            return l;
110        }
111    }
112
113    protected boolean hasBufferedData() {
114        return this.bufferpos < this.bufferlen;
115    }
116
117    public int read() throws IOException {
118        int noRead = 0;
119        while (!hasBufferedData()) {
120            noRead = fillBuffer();
121            if (noRead == -1) {
122                return -1;
123            }
124        }
125        return this.buffer[this.bufferpos++] & 0xff;
126    }
127
128    public int read(final byte[] b, int off, int len) throws IOException {
129        if (b == null) {
130            return 0;
131        }
132        int noRead = 0;
133        while (!hasBufferedData()) {
134            noRead = fillBuffer();
135            if (noRead == -1) {
136                return -1;
137            }
138        }
139        int chunk = this.bufferlen - this.bufferpos;
140        if (chunk > len) {
141            chunk = len;
142        }
143        System.arraycopy(this.buffer, this.bufferpos, b, off, chunk);
144        this.bufferpos += chunk;
145        return chunk;
146    }
147
148    public int read(final byte[] b) throws IOException {
149        if (b == null) {
150            return 0;
151        }
152        return read(b, 0, b.length);
153    }
154
155    private int locateLF() {
156        for (int i = this.bufferpos; i < this.bufferlen; i++) {
157            if (this.buffer[i] == HTTP.LF) {
158                return i;
159            }
160        }
161        return -1;
162    }
163
164    public int readLine(final CharArrayBuffer charbuffer) throws IOException {
165        if (charbuffer == null) {
166            throw new IllegalArgumentException("Char array buffer may not be null");
167        }
168        this.linebuffer.clear();
169        int noRead = 0;
170        boolean retry = true;
171        while (retry) {
172            // attempt to find end of line (LF)
173            int i = locateLF();
174            if (i != -1) {
175                // end of line found.
176                if (this.linebuffer.isEmpty()) {
177                    // the entire line is preset in the read buffer
178                    return lineFromReadBuffer(charbuffer, i);
179                }
180                retry = false;
181                int len = i + 1 - this.bufferpos;
182                this.linebuffer.append(this.buffer, this.bufferpos, len);
183                this.bufferpos = i + 1;
184            } else {
185                // end of line not found
186                if (hasBufferedData()) {
187                    int len = this.bufferlen - this.bufferpos;
188                    this.linebuffer.append(this.buffer, this.bufferpos, len);
189                    this.bufferpos = this.bufferlen;
190                }
191                noRead = fillBuffer();
192                if (noRead == -1) {
193                    retry = false;
194                }
195            }
196            if (this.maxLineLen > 0 && this.linebuffer.length() >= this.maxLineLen) {
197                throw new IOException("Maximum line length limit exceeded");
198            }
199        }
200        if (noRead == -1 && this.linebuffer.isEmpty()) {
201            // indicate the end of stream
202            return -1;
203        }
204        return lineFromLineBuffer(charbuffer);
205    }
206
207    private int lineFromLineBuffer(final CharArrayBuffer charbuffer)
208            throws IOException {
209        // discard LF if found
210        int l = this.linebuffer.length();
211        if (l > 0) {
212            if (this.linebuffer.byteAt(l - 1) == HTTP.LF) {
213                l--;
214                this.linebuffer.setLength(l);
215            }
216            // discard CR if found
217            if (l > 0) {
218                if (this.linebuffer.byteAt(l - 1) == HTTP.CR) {
219                    l--;
220                    this.linebuffer.setLength(l);
221                }
222            }
223        }
224        l = this.linebuffer.length();
225        if (this.ascii) {
226            charbuffer.append(this.linebuffer, 0, l);
227        } else {
228            // This is VERY memory inefficient, BUT since non-ASCII charsets are
229            // NOT meant to be used anyway, there's no point optimizing it
230            String s = new String(this.linebuffer.buffer(), 0, l, this.charset);
231            charbuffer.append(s);
232        }
233        return l;
234    }
235
236    private int lineFromReadBuffer(final CharArrayBuffer charbuffer, int pos)
237            throws IOException {
238        int off = this.bufferpos;
239        int len;
240        this.bufferpos = pos + 1;
241        if (pos > 0 && this.buffer[pos - 1] == HTTP.CR) {
242            // skip CR if found
243            pos--;
244        }
245        len = pos - off;
246        if (this.ascii) {
247            charbuffer.append(this.buffer, off, len);
248        } else {
249            // This is VERY memory inefficient, BUT since non-ASCII charsets are
250            // NOT meant to be used anyway, there's no point optimizing it
251            String s = new String(this.buffer, off, len, this.charset);
252            charbuffer.append(s);
253        }
254        return len;
255    }
256
257    public String readLine() throws IOException {
258        CharArrayBuffer charbuffer = new CharArrayBuffer(64);
259        int l = readLine(charbuffer);
260        if (l != -1) {
261            return charbuffer.toString();
262        } else {
263            return null;
264        }
265    }
266
267    public HttpTransportMetrics getMetrics() {
268        return this.metrics;
269    }
270
271}
272