/ org.apache.commons.httpclient / src / org / apache / commons / httpclient / ContentLengthInputStream.java
ContentLengthInputStream.java
  1  /*
  2   * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/ContentLengthInputStream.java,v 1.12 2004/10/04 22:05:44 olegk Exp $
  3   * $Revision: 480424 $
  4   * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
  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  }