    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements.  See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache License, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License.  You may obtain a copy of the License at
    008     *
    009     *      http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the License for the specific language governing permissions and
    015     * limitations under the License.
    016     */
    017    package org.apache.commons.io.input;
    018
    019    import java.io.IOException;
    020    import java.io.InputStream;
    021
    022    /**
    023     * This is a stream that will only supply bytes up to a certain length - if its
    024     * position goes above that, it will stop.
    025     * <p>
    026     * This is useful to wrap ServletInputStreams. The ServletInputStream will block
    027     * if you try to read content from it that isn't there, because it doesn't know
    028     * whether the content hasn't arrived yet or whether the content has finished.
    029     * So, one of these, initialized with the Content-length sent in the
    030     * ServletInputStream's header, will stop it blocking, providing it's been sent
    031     * with a correct content length.
    032     *
    033     * @version $Id$
    034     * @since Commons IO 2.0
    035     */
    036    public class BoundedInputStream extends InputStream {
    037
    038        /** the wrapped input stream */
    039        private final InputStream in;
    040
    041        /** the max length to provide */
    042        private final long max;
    043
    044        /** the number of bytes already returned */
    045        private long pos = 0;
    046
    047        /** the marked position */
    048        private long mark = -1;
    049
    050        /** flag if close shoud be propagated */
    051        private boolean propagateClose = true;
    052
    053        /**
    054         * Creates a new <code>BoundedInputStream</code> that wraps the given input
    055         * stream and limits it to a certain size.
    056         *
    057         * @param in The wrapped input stream
    058         * @param size The maximum number of bytes to return
    059         */
    060        public BoundedInputStream(InputStream in, long size) {
    061            // Some badly designed methods - eg the servlet API - overload length
    062            // such that "-1" means stream finished
    063            this.max = size;
    064            this.in = in;
    065        }
    066
    067        /**
    068         * Creates a new <code>BoundedInputStream</code> that wraps the given input
    069         * stream and is unlimited.
    070         *
    071         * @param in The wrapped input stream
    072         */
    073        public BoundedInputStream(InputStream in) {
    074            this(in, -1);
    075        }
    076
    077        /**
    078         * Invokes the delegate's <code>read()</code> method if
    079         * the current position is less than the limit.
    080         * @return the byte read or -1 if the end of stream or
    081         * the limit has been reached.
    082         * @throws IOException if an I/O error occurs
    083         */
    084        @Override
    085        public int read() throws IOException {
    086            if (max>=0 && pos==max) {
    087                return -1;
    088            }
    089            int result = in.read();
    090            pos++;
    091            return result;
    092        }
    093
    094        /**
    095         * Invokes the delegate's <code>read(byte[])</code> method.
    096         * @param b the buffer to read the bytes into
    097         * @return the number of bytes read or -1 if the end of stream or
    098         * the limit has been reached.
    099         * @throws IOException if an I/O error occurs
    100         */
    101        @Override
    102        public int read(byte[] b) throws IOException {
    103            return this.read(b, 0, b.length);
    104        }
    105
    106        /**
    107         * Invokes the delegate's <code>read(byte[], int, int)</code> method.
    108         * @param b the buffer to read the bytes into
    109         * @param off The start offset
    110         * @param len The number of bytes to read
    111         * @return the number of bytes read or -1 if the end of stream or
    112         * the limit has been reached.
    113         * @throws IOException if an I/O error occurs
    114         */
    115        @Override
    116        public int read(byte[] b, int off, int len) throws IOException {
    117            if (max>=0 && pos>=max) {
    118                return -1;
    119            }
    120            long maxRead = max>=0 ? Math.min(len, max-pos) : len;
    121            int bytesRead = in.read(b, off, (int)maxRead);
    122
    123            if (bytesRead==-1) {
    124                return -1;
    125            }
    126
    127            pos+=bytesRead;
    128            return bytesRead;
    129        }
    130
    131        /**
    132         * Invokes the delegate's <code>skip(long)</code> method.
    133         * @param n the number of bytes to skip
    134         * @return the actual number of bytes skipped
    135         * @throws IOException if an I/O error occurs
    136         */
    137        @Override
    138        public long skip(long n) throws IOException {
    139            long toSkip = max>=0 ? Math.min(n, max-pos) : n;
    140            long skippedBytes = in.skip(toSkip);
    141            pos+=skippedBytes;
    142            return skippedBytes;
    143        }
    144
    145        /**
    146         * {@inheritDoc}
    147         */
    148        @Override
    149        public int available() throws IOException {
    150            if (max>=0 && pos>=max) {
    151                return 0;
    152            }
    153            return in.available();
    154        }
    155
    156        /**
    157         * Invokes the delegate's <code>toString()</code> method.
    158         * @return the delegate's <code>toString()</code>
    159         */
    160        @Override
    161        public String toString() {
    162            return in.toString();
    163        }
    164
    165        /**
    166         * Invokes the delegate's <code>close()</code> method
    167         * if {@link #isPropagateClose()} is <code>true</code>.
    168         * @throws IOException if an I/O error occurs
    169         */
    170        @Override
    171        public void close() throws IOException {
    172            if (propagateClose) {
    173                in.close();
    174            }
    175        }
    176
    177        /**
    178         * Invokes the delegate's <code>reset()</code> method.
    179         * @throws IOException if an I/O error occurs
    180         */
    181        @Override
    182        public synchronized void reset() throws IOException {
    183            in.reset();
    184            pos = mark;
    185        }
    186
    187        /**
    188         * Invokes the delegate's <code>mark(int)</code> method.
    189         * @param readlimit read ahead limit
    190         */
    191        @Override
    192        public synchronized void mark(int readlimit) {
    193            in.mark(readlimit);
    194            mark = pos;
    195        }
    196
    197        /**
    198         * Invokes the delegate's <code>markSupported()</code> method.
    199         * @return true if mark is supported, otherwise false
    200         */
    201        @Override
    202        public boolean markSupported() {
    203            return in.markSupported();
    204        }
    205
    206        /**
    207         * Indicates whether the {@link #close()} method
    208         * should propagate to the underling {@link InputStream}.
    209         *
    210         * @return <code>true</code> if calling {@link #close()}
    211         * propagates to the <code>close()</code> method of the
    212         * underlying stream or <code>false</code> if it does not.
    213         */
    214        public boolean isPropagateClose() {
    215            return propagateClose;
    216        }
    217
    218        /**
    219         * Set whether the {@link #close()} method
    220         * should propagate to the underling {@link InputStream}.
    221         *
    222         * @param propagateClose <code>true</code> if calling
    223         * {@link #close()} propagates to the <code>close()</code>
    224         * method of the underlying stream or
    225         * <code>false</code> if it does not.
    226         */
    227        public void setPropagateClose(boolean propagateClose) {
    228            this.propagateClose = propagateClose;
    229        }
    230    }