1/*
2 * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/ContentLengthInputStream.java $
3 * $Revision: 652091 $
4 * $Date: 2008-04-29 13:41:07 -0700 (Tue, 29 Apr 2008) $
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;
38
39/**
40 * Stream that cuts off after a specified number of bytes.
41 * Note that this class NEVER closes the underlying stream, even when close
42 * gets called.  Instead, it will read until the "end" of its chunking on
43 * close, which allows for the seamless execution of subsequent HTTP 1.1
44 * requests, while not requiring the client to remember to read the entire
45 * contents of the response.
46 *
47 * <p>Implementation note: Choices abound. One approach would pass
48 * through the {@link InputStream#mark} and {@link InputStream#reset} calls to
49 * the underlying stream.  That's tricky, though, because you then have to
50 * start duplicating the work of keeping track of how much a reset rewinds.
51 * Further, you have to watch out for the "readLimit", and since the semantics
52 * for the readLimit leave room for differing implementations, you might get
53 * into a lot of trouble.</p>
54 *
55 * <p>Alternatively, you could make this class extend
56 * {@link java.io.BufferedInputStream}
57 * and then use the protected members of that class to avoid duplicated effort.
58 * That solution has the side effect of adding yet another possible layer of
59 * buffering.</p>
60 *
61 * <p>Then, there is the simple choice, which this takes - simply don't
62 * support {@link InputStream#mark} and {@link InputStream#reset}.  That choice
63 * has the added benefit of keeping this class very simple.</p>
64 *
65 * @author Ortwin Glueck
66 * @author Eric Johnson
67 * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
68 *
69 * @since 4.0
70 *
71 * @deprecated Please use {@link java.net.URL#openConnection} instead.
72 *     Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
73 *     for further details.
74 */
75@Deprecated
76public class ContentLengthInputStream extends InputStream {
77
78    private static final int BUFFER_SIZE = 2048;
79    /**
80     * The maximum number of bytes that can be read from the stream. Subsequent
81     * read operations will return -1.
82     */
83    private long contentLength;
84
85    /** The current position */
86    private long pos = 0;
87
88    /** True if the stream is closed. */
89    private boolean closed = false;
90
91    /**
92     * Wrapped input stream that all calls are delegated to.
93     */
94    private SessionInputBuffer in = null;
95
96    /**
97     * Creates a new length limited stream
98     *
99     * @param in The session input buffer to wrap
100     * @param contentLength The maximum number of bytes that can be read from
101     * the stream. Subsequent read operations will return -1.
102     */
103    public ContentLengthInputStream(final SessionInputBuffer in, long contentLength) {
104        super();
105        if (in == null) {
106            throw new IllegalArgumentException("Input stream may not be null");
107        }
108        if (contentLength < 0) {
109            throw new IllegalArgumentException("Content length may not be negative");
110        }
111        this.in = in;
112        this.contentLength = contentLength;
113    }
114
115    /**
116     * <p>Reads until the end of the known length of content.</p>
117     *
118     * <p>Does not close the underlying socket input, but instead leaves it
119     * primed to parse the next response.</p>
120     * @throws IOException If an IO problem occurs.
121     */
122    public void close() throws IOException {
123        if (!closed) {
124            try {
125                byte buffer[] = new byte[BUFFER_SIZE];
126                while (read(buffer) >= 0) {
127                }
128            } finally {
129                // close after above so that we don't throw an exception trying
130                // to read after closed!
131                closed = true;
132            }
133        }
134    }
135
136
137    /**
138     * Read the next byte from the stream
139     * @return The next byte or -1 if the end of stream has been reached.
140     * @throws IOException If an IO problem occurs
141     * @see java.io.InputStream#read()
142     */
143    public int read() throws IOException {
144        if (closed) {
145            throw new IOException("Attempted read from closed stream.");
146        }
147
148        if (pos >= contentLength) {
149            return -1;
150        }
151        pos++;
152        return this.in.read();
153    }
154
155    /**
156     * Does standard {@link InputStream#read(byte[], int, int)} behavior, but
157     * also notifies the watcher when the contents have been consumed.
158     *
159     * @param b     The byte array to fill.
160     * @param off   Start filling at this position.
161     * @param len   The number of bytes to attempt to read.
162     * @return The number of bytes read, or -1 if the end of content has been
163     *  reached.
164     *
165     * @throws java.io.IOException Should an error occur on the wrapped stream.
166     */
167    public int read (byte[] b, int off, int len) throws java.io.IOException {
168        if (closed) {
169            throw new IOException("Attempted read from closed stream.");
170        }
171
172        if (pos >= contentLength) {
173            return -1;
174        }
175
176        if (pos + len > contentLength) {
177            len = (int) (contentLength - pos);
178        }
179        int count = this.in.read(b, off, len);
180        pos += count;
181        return count;
182    }
183
184
185    /**
186     * Read more bytes from the stream.
187     * @param b The byte array to put the new data in.
188     * @return The number of bytes read into the buffer.
189     * @throws IOException If an IO problem occurs
190     * @see java.io.InputStream#read(byte[])
191     */
192    public int read(byte[] b) throws IOException {
193        return read(b, 0, b.length);
194    }
195
196    /**
197     * Skips and discards a number of bytes from the input stream.
198     * @param n The number of bytes to skip.
199     * @return The actual number of bytes skipped. <= 0 if no bytes
200     * are skipped.
201     * @throws IOException If an error occurs while skipping bytes.
202     * @see InputStream#skip(long)
203     */
204    public long skip(long n) throws IOException {
205        if (n <= 0) {
206            return 0;
207        }
208        byte[] buffer = new byte[BUFFER_SIZE];
209        // make sure we don't skip more bytes than are
210        // still available
211        long remaining = Math.min(n, this.contentLength - this.pos);
212        // skip and keep track of the bytes actually skipped
213        long count = 0;
214        while (remaining > 0) {
215            int l = read(buffer, 0, (int)Math.min(BUFFER_SIZE, remaining));
216            if (l == -1) {
217                break;
218            }
219            count += l;
220            remaining -= l;
221        }
222        this.pos += count;
223        return count;
224    }
225}
226