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: BoundedInputStream.java 1307462 2012-03-30 15:13:11Z ggregory $
034     * @since 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}.
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} if calling {@link #close()}
211         * propagates to the <code>close()</code> method of the
212         * underlying stream or {@code false} 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} if calling
223         * {@link #close()} propagates to the <code>close()</code>
224         * method of the underlying stream or
225         * {@code false} if it does not.
226         */
227        public void setPropagateClose(boolean propagateClose) {
228            this.propagateClose = propagateClose;
229        }
230    }