001/**
002 * Java Web Archive Toolkit - Software to read and validate ARC, WARC
003 * and GZip files. (http://jwat.org/)
004 * Copyright 2011-2012 Netarkivet.dk (http://netarkivet.dk/)
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.jwat.common;
019
020import java.io.ByteArrayOutputStream;
021import java.io.FilterInputStream;
022import java.io.IOException;
023import java.io.InputStream;
024
025/**
026 * <code>InputStream</code> with a maximum amount of bytes available to read.
027 * When the stream is closed the remaining bytes are left untouched.
028 *
029 * @author nicl
030 */
031public class MaxLengthRecordingInputStream extends FilterInputStream {
032
033    /** Buffer size to use when read skipping. */
034    public static final int SKIP_READ_BUFFER_SIZE = 1024;
035
036    /** Read buffer used by the skip method. */
037    protected byte[] skip_read_buffer = new byte[SKIP_READ_BUFFER_SIZE];
038
039    /** Output stream used to keep a record of data read. */
040    protected ByteArrayOutputStream record;
041
042    /** Maximum remaining bytes available. */
043    protected long available;
044
045    /**
046     * Create a new input stream with a maximum number of bytes available from
047     * the underlying stream.
048     * @param in the input stream to wrap
049     * @param available maximum number of bytes available through this stream
050     */
051    public MaxLengthRecordingInputStream(InputStream in, long available) {
052        super(in);
053        this.record = new ByteArrayOutputStream();
054        this.available = available;
055    }
056
057    /**
058     * Return the bytes recorded by the stream.
059     * @return recorded data as a byte array
060     */
061    public byte[] getRecording() {
062        return record.toByteArray();
063    }
064
065    /**
066     * Closing will only close the recording and not call the parent's close
067     * method.
068     * @throws IOException if an I/O error occurs while closing stream
069     */
070    @Override
071    public void close() throws IOException {
072        record.close();
073    }
074
075    @Override
076    public int available() throws IOException {
077        return (available > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) (available);
078    }
079
080    @Override
081    public boolean markSupported() {
082        return false;
083    }
084
085    @Override
086    public synchronized void mark(int readlimit) {
087        throw new UnsupportedOperationException();
088    }
089
090    @Override
091    public synchronized void reset() throws IOException {
092        throw new UnsupportedOperationException();
093    }
094
095    @Override
096    public int read() throws IOException {
097        int b = -1;
098        if (available > 0) {
099            b = in.read();
100            if (b != -1) {
101                --available;
102                record.write(b);
103            }
104        }
105        return b;
106    }
107
108    @Override
109    public int read(byte[] b) throws IOException {
110        return read(b, 0, b.length);
111    }
112
113    @Override
114    public int read(byte[] b, int off, int len) throws IOException {
115        int bytesRead = -1;
116        if (available > 0) {
117            bytesRead = in.read(b, off, (int) Math.min(len, available));
118            if (bytesRead > 0) {
119                available -= bytesRead;
120                record.write(b, off, bytesRead);
121            }
122        }
123        return bytesRead;
124    }
125
126    @Override
127    public long skip(long n) throws IOException {
128        long bytesSkipped = 0;
129        if (available > 0) {
130            bytesSkipped = read(skip_read_buffer, 0, (int) Math.min(Math.min(n, available), SKIP_READ_BUFFER_SIZE));
131            if (bytesSkipped == -1) {
132                bytesSkipped = 0;
133            }
134        }
135        return bytesSkipped;
136    }
137
138}