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}