View Javadoc

    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 }