/ org.apache.commons.httpclient / src / org / apache / commons / httpclient / ContentLengthInputStream.java
ContentLengthInputStream.java
1 /* 2 * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/ContentLengthInputStream.java,v 1.12 2004/10/04 22:05:44 olegk Exp $ 3 * $Revision: 480424 $ 4 * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $ 5 * 6 * ==================================================================== 7 * 8 * Licensed to the Apache Software Foundation (ASF) under one or more 9 * contributor license agreements. See the NOTICE file distributed with 10 * this work for additional information regarding copyright ownership. 11 * The ASF licenses this file to You under the Apache License, Version 2.0 12 * (the "License"); you may not use this file except in compliance with 13 * the License. You may obtain a copy of the License at 14 * 15 * http://www.apache.org/licenses/LICENSE-2.0 16 * 17 * Unless required by applicable law or agreed to in writing, software 18 * distributed under the License is distributed on an "AS IS" BASIS, 19 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 * See the License for the specific language governing permissions and 21 * limitations under the License. 22 * ==================================================================== 23 * 24 * This software consists of voluntary contributions made by many 25 * individuals on behalf of the Apache Software Foundation. For more 26 * information on the Apache Software Foundation, please see 27 * <http://www.apache.org/>. 28 * 29 */ 30 31 package org.apache.commons.httpclient; 32 33 import java.io.IOException; 34 import java.io.InputStream; 35 36 /** 37 * Cuts the wrapped InputStream off after a specified number of bytes. 38 * 39 * <p>Implementation note: Choices abound. One approach would pass 40 * through the {@link InputStream#mark} and {@link InputStream#reset} calls to 41 * the underlying stream. That's tricky, though, because you then have to 42 * start duplicating the work of keeping track of how much a reset rewinds. 43 * Further, you have to watch out for the "readLimit", and since the semantics 44 * for the readLimit leave room for differing implementations, you might get 45 * into a lot of trouble.</p> 46 * 47 * <p>Alternatively, you could make this class extend {@link java.io.BufferedInputStream} 48 * and then use the protected members of that class to avoid duplicated effort. 49 * That solution has the side effect of adding yet another possible layer of 50 * buffering.</p> 51 * 52 * <p>Then, there is the simple choice, which this takes - simply don't 53 * support {@link InputStream#mark} and {@link InputStream#reset}. That choice 54 * has the added benefit of keeping this class very simple.</p> 55 * 56 * @author Ortwin Glueck 57 * @author Eric Johnson 58 * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> 59 * @since 2.0 60 */ 61 public class ContentLengthInputStream extends InputStream { 62 63 /** 64 * The maximum number of bytes that can be read from the stream. Subsequent 65 * read operations will return -1. 66 */ 67 private long contentLength; 68 69 /** The current position */ 70 private long pos = 0; 71 72 /** True if the stream is closed. */ 73 private boolean closed = false; 74 75 /** 76 * Wrapped input stream that all calls are delegated to. 77 */ 78 private InputStream wrappedStream = null; 79 80 /** 81 * @deprecated use {@link #ContentLengthInputStream(InputStream, long)} 82 * 83 * Creates a new length limited stream 84 * 85 * @param in The stream to wrap 86 * @param contentLength The maximum number of bytes that can be read from 87 * the stream. Subsequent read operations will return -1. 88 */ 89 public ContentLengthInputStream(InputStream in, int contentLength) { 90 this(in, (long)contentLength); 91 } 92 93 /** 94 * Creates a new length limited stream 95 * 96 * @param in The stream to wrap 97 * @param contentLength The maximum number of bytes that can be read from 98 * the stream. Subsequent read operations will return -1. 99 * 100 * @since 3.0 101 */ 102 public ContentLengthInputStream(InputStream in, long contentLength) { 103 super(); 104 this.wrappedStream = in; 105 this.contentLength = contentLength; 106 } 107 108 /** 109 * <p>Reads until the end of the known length of content.</p> 110 * 111 * <p>Does not close the underlying socket input, but instead leaves it 112 * primed to parse the next response.</p> 113 * @throws IOException If an IO problem occurs. 114 */ 115 public void close() throws IOException { 116 if (!closed) { 117 try { 118 ChunkedInputStream.exhaustInputStream(this); 119 } finally { 120 // close after above so that we don't throw an exception trying 121 // to read after closed! 122 closed = true; 123 } 124 } 125 } 126 127 128 /** 129 * Read the next byte from the stream 130 * @return The next byte or -1 if the end of stream has been reached. 131 * @throws IOException If an IO problem occurs 132 * @see java.io.InputStream#read() 133 */ 134 public int read() throws IOException { 135 if (closed) { 136 throw new IOException("Attempted read from closed stream."); 137 } 138 139 if (pos >= contentLength) { 140 return -1; 141 } 142 pos++; 143 return this.wrappedStream.read(); 144 } 145 146 /** 147 * Does standard {@link InputStream#read(byte[], int, int)} behavior, but 148 * also notifies the watcher when the contents have been consumed. 149 * 150 * @param b The byte array to fill. 151 * @param off Start filling at this position. 152 * @param len The number of bytes to attempt to read. 153 * @return The number of bytes read, or -1 if the end of content has been 154 * reached. 155 * 156 * @throws java.io.IOException Should an error occur on the wrapped stream. 157 */ 158 public int read (byte[] b, int off, int len) throws java.io.IOException { 159 if (closed) { 160 throw new IOException("Attempted read from closed stream."); 161 } 162 163 if (pos >= contentLength) { 164 return -1; 165 } 166 167 if (pos + len > contentLength) { 168 len = (int) (contentLength - pos); 169 } 170 int count = this.wrappedStream.read(b, off, len); 171 pos += count; 172 return count; 173 } 174 175 176 /** 177 * Read more bytes from the stream. 178 * @param b The byte array to put the new data in. 179 * @return The number of bytes read into the buffer. 180 * @throws IOException If an IO problem occurs 181 * @see java.io.InputStream#read(byte[]) 182 */ 183 public int read(byte[] b) throws IOException { 184 return read(b, 0, b.length); 185 } 186 187 /** 188 * Skips and discards a number of bytes from the input stream. 189 * @param n The number of bytes to skip. 190 * @return The actual number of bytes skipped. <= 0 if no bytes 191 * are skipped. 192 * @throws IOException If an error occurs while skipping bytes. 193 * @see InputStream#skip(long) 194 */ 195 public long skip(long n) throws IOException { 196 // make sure we don't skip more bytes than are 197 // still available 198 long length = Math.min(n, contentLength - pos); 199 // skip and keep track of the bytes actually skipped 200 length = this.wrappedStream.skip(length); 201 // only add the skipped bytes to the current position 202 // if bytes were actually skipped 203 if (length > 0) { 204 pos += length; 205 } 206 return length; 207 } 208 209 public int available() throws IOException { 210 if (this.closed) { 211 return 0; 212 } 213 int avail = this.wrappedStream.available(); 214 if (this.pos + avail > this.contentLength ) { 215 avail = (int)(this.contentLength - this.pos); 216 } 217 return avail; 218 } 219 220 }