View Javadoc

1   /*
2    * $HeadURL: https://svn.apache.org/repos/asf/httpcomponents/oac.hc3x/trunk/src/java/org/apache/commons/httpclient/ContentLengthInputStream.java $
3    * $Revision$
4    * $Date$
5    *
6    * ====================================================================
7    *
8    *  Licensed to the Apache Software Foundation (ASF) under one or more
9    *  contributor license agreements.  See the NOTICE file distributed with
10   *  this work for additional information regarding copyright ownership.
11   *  The ASF licenses this file to You under the Apache License, Version 2.0
12   *  (the "License"); you may not use this file except in compliance with
13   *  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, software
18   *  distributed under the License is distributed on an "AS IS" BASIS,
19   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20   *  See the License for the specific language governing permissions and
21   *  limitations under the License.
22   * ====================================================================
23   *
24   * This software consists of voluntary contributions made by many
25   * individuals on behalf of the Apache Software Foundation.  For more
26   * information on the Apache Software Foundation, please see
27   * <http://www.apache.org/>.
28   *
29   */
30  
31  package org.apache.commons.httpclient;
32  
33  import java.io.IOException;
34  import java.io.InputStream;
35  
36  /***
37   * Cuts the wrapped InputStream off after a specified number of bytes.
38   *
39   * <p>Implementation note: Choices abound. One approach would pass
40   * through the {@link InputStream#mark} and {@link InputStream#reset} calls to
41   * the underlying stream.  That's tricky, though, because you then have to
42   * start duplicating the work of keeping track of how much a reset rewinds.
43   * Further, you have to watch out for the "readLimit", and since the semantics
44   * for the readLimit leave room for differing implementations, you might get
45   * into a lot of trouble.</p>
46   *
47   * <p>Alternatively, you could make this class extend {@link java.io.BufferedInputStream}
48   * and then use the protected members of that class to avoid duplicated effort.
49   * That solution has the side effect of adding yet another possible layer of
50   * buffering.</p>
51   *
52   * <p>Then, there is the simple choice, which this takes - simply don't
53   * support {@link InputStream#mark} and {@link InputStream#reset}.  That choice
54   * has the added benefit of keeping this class very simple.</p>
55   *
56   * @author Ortwin Glueck
57   * @author Eric Johnson
58   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
59   * @since 2.0
60   */
61  public class ContentLengthInputStream extends InputStream {
62      
63      /***
64       * The maximum number of bytes that can be read from the stream. Subsequent
65       * read operations will return -1.
66       */
67      private long contentLength;
68  
69      /*** The current position */
70      private long pos = 0;
71  
72      /*** True if the stream is closed. */
73      private boolean closed = false;
74  
75      /***
76       * Wrapped input stream that all calls are delegated to.
77       */
78      private InputStream wrappedStream = null;
79  
80      /***
81       * @deprecated use {@link #ContentLengthInputStream(InputStream, long)}
82       * 
83       * Creates a new length limited stream
84       *
85       * @param in The stream to wrap
86       * @param contentLength The maximum number of bytes that can be read from
87       * the stream. Subsequent read operations will return -1.
88       */
89      public ContentLengthInputStream(InputStream in, int contentLength) {
90          this(in, (long)contentLength);
91      }
92  
93      /***
94       * Creates a new length limited stream
95       *
96       * @param in The stream to wrap
97       * @param contentLength The maximum number of bytes that can be read from
98       * the stream. Subsequent read operations will return -1.
99       * 
100      * @since 3.0
101      */
102     public ContentLengthInputStream(InputStream in, long contentLength) {
103         super();
104         this.wrappedStream = in;
105         this.contentLength = contentLength;
106     }
107 
108     /***
109      * <p>Reads until the end of the known length of content.</p>
110      *
111      * <p>Does not close the underlying socket input, but instead leaves it
112      * primed to parse the next response.</p>
113      * @throws IOException If an IO problem occurs.
114      */
115     public void close() throws IOException {
116         if (!closed) {
117             try {
118                 ChunkedInputStream.exhaustInputStream(this);
119             } finally {
120                 // close after above so that we don't throw an exception trying
121                 // to read after closed!
122                 closed = true;
123             }
124         }
125     }
126 
127 
128     /***
129      * Read the next byte from the stream
130      * @return The next byte or -1 if the end of stream has been reached.
131      * @throws IOException If an IO problem occurs
132      * @see java.io.InputStream#read()
133      */
134     public int read() throws IOException {
135         if (closed) {
136             throw new IOException("Attempted read from closed stream.");
137         }
138 
139         if (pos >= contentLength) {
140             return -1;
141         }
142         pos++;
143         return this.wrappedStream.read();
144     }
145 
146     /***
147      * Does standard {@link InputStream#read(byte[], int, int)} behavior, but
148      * also notifies the watcher when the contents have been consumed.
149      *
150      * @param b     The byte array to fill.
151      * @param off   Start filling at this position.
152      * @param len   The number of bytes to attempt to read.
153      * @return The number of bytes read, or -1 if the end of content has been
154      *  reached.
155      *
156      * @throws java.io.IOException Should an error occur on the wrapped stream.
157      */
158     public int read (byte[] b, int off, int len) throws java.io.IOException {
159         if (closed) {
160             throw new IOException("Attempted read from closed stream.");
161         }
162 
163         if (pos >= contentLength) {
164             return -1;
165         }
166 
167         if (pos + len > contentLength) {
168             len = (int) (contentLength - pos);
169         }
170         int count = this.wrappedStream.read(b, off, len);
171         pos += count;
172         return count;
173     }
174 
175 
176     /***
177      * Read more bytes from the stream.
178      * @param b The byte array to put the new data in.
179      * @return The number of bytes read into the buffer.
180      * @throws IOException If an IO problem occurs
181      * @see java.io.InputStream#read(byte[])
182      */
183     public int read(byte[] b) throws IOException {
184         return read(b, 0, b.length);
185     }
186 
187     /***
188      * Skips and discards a number of bytes from the input stream.
189      * @param n The number of bytes to skip.
190      * @return The actual number of bytes skipped. <= 0 if no bytes
191      * are skipped.
192      * @throws IOException If an error occurs while skipping bytes.
193      * @see InputStream#skip(long)
194      */
195     public long skip(long n) throws IOException {
196         // make sure we don't skip more bytes than are 
197         // still available
198         long length = Math.min(n, contentLength - pos);
199         // skip and keep track of the bytes actually skipped
200         length = this.wrappedStream.skip(length);
201         // only add the skipped bytes to the current position
202         // if bytes were actually skipped
203         if (length > 0) {
204             pos += length;
205         }
206         return length;
207     }
208 
209     public int available() throws IOException {
210         if (this.closed) {
211             return 0;
212         }
213         int avail = this.wrappedStream.available();
214         if (this.pos + avail > this.contentLength ) {
215             avail = (int)(this.contentLength - this.pos);
216         }
217         return avail;     
218     }
219     
220 }