    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 */
    017package org.apache.commons.io.input;
    018
    019import static org.apache.commons.io.IOUtils.EOF;
    020
    021import java.io.IOException;
    022import java.io.InputStream;
    023
    024import org.apache.commons.io.IOUtils;
    025import org.apache.commons.io.build.AbstractStreamBuilder;
    026
    027//@formatter:off
    028/**
    029 * Reads bytes up to a maximum count and stops once reached.
    030 * <p>
    031 * To build an instance, see {@link AbstractBuilder}.
    032 * </p>
    033 * <p>
    034 * By default, a {@link BoundedInputStream} is <em>unbound</em>; so make sure to call {@link AbstractBuilder#setMaxCount(long)}.
    035 * </p>
    036 * <p>
    037 * You can find out how many bytes this stream has seen so far by calling {@link BoundedInputStream#getCount()}. This value reflects bytes read and skipped.
    038 * </p>
    039 * <h2>Using a ServletInputStream</h2>
    040 * <p>
    041 * A {@code ServletInputStream} can block if you try to read content that isn't there
    042 * because it doesn't know whether the content hasn't arrived yet or whether the content has finished. Initialize an {@link BoundedInputStream} with the
    043 * {@code Content-Length} sent in the {@code ServletInputStream}'s header, this stop it from blocking, providing it's been sent with a correct content
    044 * length in the first place.
    045 * </p>
    046 * <h2>Using NIO</h2>
    047 * <pre>{@code
    048 * BoundedInputStream s = BoundedInputStream.builder()
    049 *   .setPath(Paths.get("MyFile.xml"))
    050 *   .setMaxCount(1024)
    051 *   .setPropagateClose(false)
    052 *   .get();
    053 * }
    054 * </pre>
    055 * <h2>Using IO</h2>
    056 * <pre>{@code
    057 * BoundedInputStream s = BoundedInputStream.builder()
    058 *   .setFile(new File("MyFile.xml"))
    059 *   .setMaxCount(1024)
    060 *   .setPropagateClose(false)
    061 *   .get();
    062 * }
    063 * </pre>
    064 * <h2>Counting Bytes</h2>
    065 * <p>You can set the running count when building, which is most useful when starting from another stream:
    066 * <pre>{@code
    067 * InputStream in = ...;
    068 * BoundedInputStream s = BoundedInputStream.builder()
    069 *   .setInputStream(in)
    070 *   .setCount(12)
    071 *   .setMaxCount(1024)
    072 *   .setPropagateClose(false)
    073 *   .get();
    074 * }
    075 * </pre>
    076 * @see Builder
    077 * @since 2.0
    078 */
    079//@formatter:on
    080public class BoundedInputStream extends ProxyInputStream {
    081
    082    /**
    083     * For subclassing builders from {@link BoundedInputStream} subclassses.
    084     *
    085     * @param <T> The subclass.
    086     */
    087    static abstract class AbstractBuilder<T extends AbstractBuilder<T>> extends AbstractStreamBuilder<BoundedInputStream, T> {
    088
    089        /** The current count of bytes counted. */
    090        private long count;
    091
    092        /** The max count of bytes to read. */
    093        private long maxCount = EOF;
    094
    095        /** Flag if {@link #close()} should be propagated, {@code true} by default. */
    096        private boolean propagateClose = true;
    097
    098        long getCount() {
    099            return count;
    100        }
    101
    102        long getMaxCount() {
    103            return maxCount;
    104        }
    105
    106        boolean isPropagateClose() {
    107            return propagateClose;
    108        }
    109
    110        /**
    111         * Sets the current number of bytes counted.
    112         * <p>
    113         * Useful when building from another stream to carry forward a read count.
    114         * </p>
    115         * <p>
    116         * Default is {@code 0}, negative means 0.
    117         * </p>
    118         *
    119         * @param count The current number of bytes counted.
    120         * @return this.
    121         */
    122        public T setCount(final long count) {
    123            this.count = Math.max(0, count);
    124            return asThis();
    125        }
    126
    127        /**
    128         * Sets the maximum number of bytes to return.
    129         * <p>
    130         * Default is {@value IOUtils#EOF}, negative means unbound.
    131         * </p>
    132         *
    133         * @param maxCount The maximum number of bytes to return.
    134         * @return this.
    135         */
    136        public T setMaxCount(final long maxCount) {
    137            this.maxCount = Math.max(EOF, maxCount);
    138            return asThis();
    139        }
    140
    141        /**
    142         * Sets whether the {@link #close()} method should propagate to the underling {@link InputStream}.
    143         * <p>
    144         * Default is {@code true}.
    145         * </p>
    146         *
    147         * @param propagateClose {@code true} if calling {@link #close()} propagates to the {@code close()} method of the underlying stream or {@code false} if
    148         *                       it does not.
    149         * @return this.
    150         */
    151        public T setPropagateClose(final boolean propagateClose) {
    152            this.propagateClose = propagateClose;
    153            return asThis();
    154        }
    155
    156    }
    157
    158    //@formatter:off
    159    /**
    160     * Builds a new {@link BoundedInputStream}.
    161     * <p>
    162     * By default, a {@link BoundedInputStream} is <em>unbound</em>; so make sure to call {@link AbstractBuilder#setMaxCount(long)}.
    163     * </p>
    164     * <p>
    165     * You can find out how many bytes this stream has seen so far by calling {@link BoundedInputStream#getCount()}. This value reflects bytes read and skipped.
    166     * </p>
    167     * <h2>Using a ServletInputStream</h2>
    168     * <p>
    169     * A {@code ServletInputStream} can block if you try to read content that isn't there
    170     * because it doesn't know whether the content hasn't arrived yet or whether the content has finished. Initialize an {@link BoundedInputStream} with the
    171     * {@code Content-Length} sent in the {@code ServletInputStream}'s header, this stop it from blocking, providing it's been sent with a correct content
    172     * length in the first place.
    173     * </p>
    174     * <h2>Using NIO</h2>
    175     * <pre>{@code
    176     * BoundedInputStream s = BoundedInputStream.builder()
    177     *   .setPath(Paths.get("MyFile.xml"))
    178     *   .setMaxCount(1024)
    179     *   .setPropagateClose(false)
    180     *   .get();
    181     * }
    182     * </pre>
    183     * <h2>Using IO</h2>
    184     * <pre>{@code
    185     * BoundedInputStream s = BoundedInputStream.builder()
    186     *   .setFile(new File("MyFile.xml"))
    187     *   .setMaxCount(1024)
    188     *   .setPropagateClose(false)
    189     *   .get();
    190     * }
    191     * </pre>
    192     * <h2>Counting Bytes</h2>
    193     * <p>You can set the running count when building, which is most useful when starting from another stream:
    194     * <pre>{@code
    195     * InputStream in = ...;
    196     * BoundedInputStream s = BoundedInputStream.builder()
    197     *   .setInputStream(in)
    198     *   .setCount(12)
    199     *   .setMaxCount(1024)
    200     *   .setPropagateClose(false)
    201     *   .get();
    202     * }
    203     * </pre>
    204     *
    205     * @see #get()
    206     * @since 2.16.0
    207     */
    208    //@formatter:on
    209    public static class Builder extends AbstractBuilder<Builder> {
    210
    211        /**
    212         * Builds a new {@link BoundedInputStream}.
    213         * <p>
    214         * You must set input that supports {@link #getInputStream()}, otherwise, this method throws an exception.
    215         * </p>
    216         * <p>
    217         * This builder use the following aspects:
    218         * </p>
    219         * <ul>
    220         * <li>{@link #getInputStream()}</li>
    221         * <li>maxCount</li>
    222         * <li>propagateClose</li>
    223         * </ul>
    224         *
    225         * @return a new instance.
    226         * @throws IllegalStateException         if the {@code origin} is {@code null}.
    227         * @throws UnsupportedOperationException if the origin cannot be converted to an {@link InputStream}.
    228         * @throws IOException                   if an I/O error occurs.
    229         * @see #getInputStream()
    230         */
    231        @SuppressWarnings("resource")
    232        @Override
    233        public BoundedInputStream get() throws IOException {
    234            return new BoundedInputStream(getInputStream(), getCount(), getMaxCount(), isPropagateClose());
    235        }
    236
    237    }
    238
    239    /**
    240     * Constructs a new {@link AbstractBuilder}.
    241     *
    242     * @return a new {@link AbstractBuilder}.
    243     * @since 2.16.0
    244     */
    245    public static Builder builder() {
    246        return new Builder();
    247    }
    248
    249    /** The current count of bytes counted. */
    250    private long count;
    251
    252    /** The max count of bytes to read. */
    253    private final long maxCount;
    254
    255    /**
    256     * Flag if close should be propagated.
    257     *
    258     * TODO Make final in 3.0.
    259     */
    260    private boolean propagateClose = true;
    261
    262    /**
    263     * Constructs a new {@link BoundedInputStream} that wraps the given input stream and is unlimited.
    264     *
    265     * @param in The wrapped input stream.
    266     * @deprecated Use {@link AbstractBuilder#get()}.
    267     */
    268    @Deprecated
    269    public BoundedInputStream(final InputStream in) {
    270        this(in, EOF);
    271    }
    272
    273    /**
    274     * Constructs a new {@link BoundedInputStream} that wraps the given input stream and limits it to a certain size.
    275     *
    276     * @param inputStream The wrapped input stream.
    277     * @param maxCount    The maximum number of bytes to return.
    278     * @deprecated Use {@link AbstractBuilder#get()}.
    279     */
    280    @Deprecated
    281    public BoundedInputStream(final InputStream inputStream, final long maxCount) {
    282        // Some badly designed methods - e.g. the Servlet API - overload length
    283        // such that "-1" means stream finished
    284        this(inputStream, 0, maxCount, true);
    285    }
    286
    287    /**
    288     * Constructs a new {@link BoundedInputStream} that wraps the given input stream and limits it to a certain size.
    289     *
    290     * @param inputStream    The wrapped input stream.
    291     * @param count          The current number of bytes read.
    292     * @param maxCount       The maximum number of bytes to return.
    293     * @param propagateClose {@code true} if calling {@link #close()} propagates to the {@code close()} method of the underlying stream or {@code false} if it
    294     *                       does not.
    295     */
    296    BoundedInputStream(final InputStream inputStream, final long count, final long maxCount, final boolean propagateClose) {
    297        // Some badly designed methods - e.g. the Servlet API - overload length
    298        // such that "-1" means stream finished
    299        // Can't throw because we start from an InputStream.
    300        super(inputStream);
    301        this.count = count;
    302        this.maxCount = maxCount;
    303        this.propagateClose = propagateClose;
    304    }
    305
    306    /**
    307     * Adds the number of read bytes to the count.
    308     *
    309     * @param n number of bytes read, or -1 if no more bytes are available
    310     * @throws IOException Not thrown here but subclasses may throw.
    311     * @since 2.0
    312     */
    313    @Override
    314    protected synchronized void afterRead(final int n) throws IOException {
    315        if (n != EOF) {
    316            count += n;
    317        }
    318    }
    319
    320    /**
    321     * {@inheritDoc}
    322     */
    323    @Override
    324    public int available() throws IOException {
    325        if (isMaxCount()) {
    326            onMaxLength(maxCount, getCount());
    327            return 0;
    328        }
    329        return in.available();
    330    }
    331
    332    /**
    333     * Invokes the delegate's {@link InputStream#close()} method if {@link #isPropagateClose()} is {@code true}.
    334     *
    335     * @throws IOException if an I/O error occurs.
    336     */
    337    @Override
    338    public void close() throws IOException {
    339        if (propagateClose) {
    340            in.close();
    341        }
    342    }
    343
    344    /**
    345     * Gets the count of bytes read.
    346     *
    347     * @return The count of bytes read.
    348     * @since 2.12.0
    349     */
    350    public long getCount() {
    351        return count;
    352    }
    353
    354    /**
    355     * Gets the max count of bytes to read.
    356     *
    357     * @return The max count of bytes to read.
    358     * @since 2.16.0
    359     */
    360    public long getMaxCount() {
    361        return maxCount;
    362    }
    363
    364    /**
    365     * Gets the max count of bytes to read.
    366     *
    367     * @return The max count of bytes to read.
    368     * @since 2.12.0
    369     * @deprecated Use {@link #getMaxCount()}.
    370     */
    371    @Deprecated
    372    public long getMaxLength() {
    373        return maxCount;
    374    }
    375
    376    /**
    377     * Gets how many bytes remain to read.
    378     *
    379     * @return bytes how many bytes remain to read.
    380     * @since 2.16.0
    381     */
    382    public long getRemaining() {
    383        return Math.max(0, getMaxCount() - getCount());
    384    }
    385
    386    private boolean isMaxCount() {
    387        return maxCount >= 0 && getCount() >= maxCount;
    388    }
    389
    390    /**
    391     * Tests whether the {@link #close()} method should propagate to the underling {@link InputStream}.
    392     *
    393     * @return {@code true} if calling {@link #close()} propagates to the {@code close()} method of the underlying stream or {@code false} if it does not.
    394     */
    395    public boolean isPropagateClose() {
    396        return propagateClose;
    397    }
    398
    399    /**
    400     * Invokes the delegate's {@link InputStream#mark(int)} method.
    401     *
    402     * @param readLimit read ahead limit
    403     */
    404    @Override
    405    public synchronized void mark(final int readLimit) {
    406        in.mark(readLimit);
    407    }
    408
    409    /**
    410     * Invokes the delegate's {@link InputStream#markSupported()} method.
    411     *
    412     * @return true if mark is supported, otherwise false
    413     */
    414    @Override
    415    public boolean markSupported() {
    416        return in.markSupported();
    417    }
    418
    419    /**
    420     * A caller has caused a request that would cross the {@code maxLength} boundary.
    421     *
    422     * @param maxLength The max count of bytes to read.
    423     * @param count     The count of bytes read.
    424     * @throws IOException Subclasses may throw.
    425     * @since 2.12.0
    426     */
    427    @SuppressWarnings("unused")
    428    protected void onMaxLength(final long maxLength, final long count) throws IOException {
    429        // for subclasses
    430    }
    431
    432    /**
    433     * Invokes the delegate's {@link InputStream#read()} method if the current position is less than the limit.
    434     *
    435     * @return the byte read or -1 if the end of stream or the limit has been reached.
    436     * @throws IOException if an I/O error occurs.
    437     */
    438    @Override
    439    public int read() throws IOException {
    440        if (isMaxCount()) {
    441            onMaxLength(maxCount, getCount());
    442            return EOF;
    443        }
    444        return super.read();
    445    }
    446
    447    /**
    448     * Invokes the delegate's {@link InputStream#read(byte[])} method.
    449     *
    450     * @param b the buffer to read the bytes into
    451     * @return the number of bytes read or -1 if the end of stream or the limit has been reached.
    452     * @throws IOException if an I/O error occurs.
    453     */
    454    @Override
    455    public int read(final byte[] b) throws IOException {
    456        return read(b, 0, b.length);
    457    }
    458
    459    /**
    460     * Invokes the delegate's {@link InputStream#read(byte[], int, int)} method.
    461     *
    462     * @param b   the buffer to read the bytes into
    463     * @param off The start offset
    464     * @param len The number of bytes to read
    465     * @return the number of bytes read or -1 if the end of stream or the limit has been reached.
    466     * @throws IOException if an I/O error occurs.
    467     */
    468    @Override
    469    public int read(final byte[] b, final int off, final int len) throws IOException {
    470        if (isMaxCount()) {
    471            onMaxLength(maxCount, getCount());
    472            return EOF;
    473        }
    474        return super.read(b, off, (int) toReadLen(len));
    475    }
    476
    477    /**
    478     * Invokes the delegate's {@link InputStream#reset()} method.
    479     *
    480     * @throws IOException if an I/O error occurs.
    481     */
    482    @Override
    483    public synchronized void reset() throws IOException {
    484        in.reset();
    485    }
    486
    487    /**
    488     * Sets whether the {@link #close()} method should propagate to the underling {@link InputStream}.
    489     *
    490     * @param propagateClose {@code true} if calling {@link #close()} propagates to the {@code close()} method of the underlying stream or {@code false} if it
    491     *                       does not.
    492     * @deprecated Use {@link AbstractBuilder#setPropagateClose(boolean)}.
    493     */
    494    @Deprecated
    495    public void setPropagateClose(final boolean propagateClose) {
    496        this.propagateClose = propagateClose;
    497    }
    498
    499    /**
    500     * Invokes the delegate's {@link InputStream#skip(long)} method.
    501     *
    502     * @param n the number of bytes to skip
    503     * @return the actual number of bytes skipped
    504     * @throws IOException if an I/O error occurs.
    505     */
    506    @Override
    507    public long skip(final long n) throws IOException {
    508        final long skip = super.skip(toReadLen(n));
    509        count += skip;
    510        return skip;
    511    }
    512
    513    private long toReadLen(final long len) {
    514        return maxCount >= 0 ? Math.min(len, maxCount - getCount()) : len;
    515    }
    516
    517    /**
    518     * Invokes the delegate's {@link InputStream#toString()} method.
    519     *
    520     * @return the delegate's {@link InputStream#toString()}
    521     */
    522    @Override
    523    public String toString() {
    524        return in.toString();
    525    }
    526}