1 /* 2 * Copyright (c) 2000, 2003, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javax.imageio.stream; 27 28 import java.util.ArrayList; 29 import java.io.InputStream; 30 import java.io.OutputStream; 31 import java.io.IOException; 32 33 /** 34 * Package-visible class consolidating common code for 35 * <code>MemoryCacheImageInputStream</code> and 36 * <code>MemoryCacheImageOutputStream</code>. 37 * This class keeps an <code>ArrayList</code> of 8K blocks, 38 * loaded sequentially. Blocks may only be disposed of 39 * from the index 0 forward. As blocks are freed, the 40 * corresponding entries in the array list are set to 41 * <code>null</code>, but no compacting is performed. 42 * This allows the index for each block to never change, 43 * and the length of the cache is always the same as the 44 * total amount of data ever cached. Cached data is 45 * therefore always contiguous from the point of last 46 * disposal to the current length. 47 * 48 * <p> The total number of blocks resident in the cache must not 49 * exceed <code>Integer.MAX_VALUE</code>. In practice, the limit of 50 * available memory will be exceeded long before this becomes an 51 * issue, since a full cache would contain 8192*2^31 = 16 terabytes of 52 * data. 53 * 54 * A <code>MemoryCache</code> may be reused after a call 55 * to <code>reset()</code>. 56 */ 57 class MemoryCache { 58 59 private static final int BUFFER_LENGTH = 8192; 60 61 private ArrayList cache = new ArrayList(); 62 63 private long cacheStart = 0L; 64 65 /** 66 * The largest position ever written to the cache. 67 */ 68 private long length = 0L; 69 70 private byte[] getCacheBlock(long blockNum) throws IOException { 71 long blockOffset = blockNum - cacheStart; 72 if (blockOffset > Integer.MAX_VALUE) { 73 // This can only happen when the cache hits 16 terabytes of 74 // contiguous data... 75 throw new IOException("Cache addressing limit exceeded!"); 76 } 77 return (byte[])cache.get((int)blockOffset); 78 } 79 80 /** 81 * Ensures that at least <code>pos</code> bytes are cached, 82 * or the end of the source is reached. The return value 83 * is equal to the smaller of <code>pos</code> and the 84 * length of the source. 85 */ 86 public long loadFromStream(InputStream stream, long pos) 87 throws IOException { 88 // We've already got enough data cached 89 if (pos < length) { 90 return pos; 91 } 92 93 int offset = (int)(length % BUFFER_LENGTH); 94 byte [] buf = null; 95 96 long len = pos - length; 97 if (offset != 0) { 98 buf = getCacheBlock(length/BUFFER_LENGTH); 99 } 100 101 while (len > 0) { 102 if (buf == null) { 103 try { 104 buf = new byte[BUFFER_LENGTH]; 105 } catch (OutOfMemoryError e) { 106 throw new IOException("No memory left for cache!"); 107 } 108 offset = 0; 109 } 110 111 int left = BUFFER_LENGTH - offset; 112 int nbytes = (int)Math.min(len, (long)left); 113 nbytes = stream.read(buf, offset, nbytes); 114 if (nbytes == -1) { 115 return length; // EOF 116 } 117 118 if (offset == 0) { 119 cache.add(buf); 120 } 121 122 len -= nbytes; 123 length += nbytes; 124 offset += nbytes; 125 126 if (offset >= BUFFER_LENGTH) { 127 // we've filled the current buffer, so a new one will be 128 // allocated next time around (and offset will be reset to 0) 129 buf = null; 130 } 131 } 132 133 return pos; 134 } 135 136 /** 137 * Writes out a portion of the cache to an <code>OutputStream</code>. 138 * This method preserves no state about the output stream, and does 139 * not dispose of any blocks containing bytes written. To dispose 140 * blocks, use {@link #disposeBefore <code>disposeBefore()</code>}. 141 * 142 * @exception IndexOutOfBoundsException if any portion of 143 * the requested data is not in the cache (including if <code>pos</code> 144 * is in a block already disposed), or if either <code>pos</code> or 145 * <code>len</code> is < 0. 146 */ 147 public void writeToStream(OutputStream stream, long pos, long len) 148 throws IOException { 149 if (pos + len > length) { 150 throw new IndexOutOfBoundsException("Argument out of cache"); 151 } 152 if ((pos < 0) || (len < 0)) { 153 throw new IndexOutOfBoundsException("Negative pos or len"); 154 } 155 if (len == 0) { 156 return; 157 } 158 159 long bufIndex = pos/BUFFER_LENGTH; 160 if (bufIndex < cacheStart) { 161 throw new IndexOutOfBoundsException("pos already disposed"); 162 } 163 int offset = (int)(pos % BUFFER_LENGTH); 164 165 byte[] buf = getCacheBlock(bufIndex++); 166 while (len > 0) { 167 if (buf == null) { 168 buf = getCacheBlock(bufIndex++); 169 offset = 0; 170 } 171 int nbytes = (int)Math.min(len, (long)(BUFFER_LENGTH - offset)); 172 stream.write(buf, offset, nbytes); 173 buf = null; 174 len -= nbytes; 175 } 176 } 177 178 /** 179 * Ensure that there is space to write a byte at the given position. 180 */ 181 private void pad(long pos) throws IOException { 182 long currIndex = cacheStart + cache.size() - 1; 183 long lastIndex = pos/BUFFER_LENGTH; 184 long numNewBuffers = lastIndex - currIndex; 185 for (long i = 0; i < numNewBuffers; i++) { 186 try { 187 cache.add(new byte[BUFFER_LENGTH]); 188 } catch (OutOfMemoryError e) { 189 throw new IOException("No memory left for cache!"); 190 } 191 } 192 } 193 194 /** 195 * Overwrites and/or appends the cache from a byte array. 196 * The length of the cache will be extended as needed to hold 197 * the incoming data. 198 * 199 * @param b an array of bytes containing data to be written. 200 * @param off the starting offset withing the data array. 201 * @param len the number of bytes to be written. 202 * @param pos the cache position at which to begin writing. 203 * 204 * @exception NullPointerException if <code>b</code> is <code>null</code>. 205 * @exception IndexOutOfBoundsException if <code>off</code>, 206 * <code>len</code>, or <code>pos</code> are negative, 207 * or if <code>off+len > b.length</code>. 208 */ 209 public void write(byte[] b, int off, int len, long pos) 210 throws IOException { 211 if (b == null) { 212 throw new NullPointerException("b == null!"); 213 } 214 // Fix 4430357 - if off + len < 0, overflow occurred 215 if ((off < 0) || (len < 0) || (pos < 0) || 216 (off + len > b.length) || (off + len < 0)) { 217 throw new IndexOutOfBoundsException(); 218 } 219 220 // Ensure there is space for the incoming data 221 long lastPos = pos + len - 1; 222 if (lastPos >= length) { 223 pad(lastPos); 224 length = lastPos + 1; 225 } 226 227 // Copy the data into the cache, block by block 228 int offset = (int)(pos % BUFFER_LENGTH); 229 while (len > 0) { 230 byte[] buf = getCacheBlock(pos/BUFFER_LENGTH); 231 int nbytes = Math.min(len, BUFFER_LENGTH - offset); 232 System.arraycopy(b, off, buf, offset, nbytes); 233 234 pos += nbytes; 235 off += nbytes; 236 len -= nbytes; 237 offset = 0; // Always after the first time 238 } 239 } 240 241 /** 242 * Overwrites or appends a single byte to the cache. 243 * The length of the cache will be extended as needed to hold 244 * the incoming data. 245 * 246 * @param b an <code>int</code> whose 8 least significant bits 247 * will be written. 248 * @param pos the cache position at which to begin writing. 249 * 250 * @exception IndexOutOfBoundsException if <code>pos</code> is negative. 251 */ 252 public void write(int b, long pos) throws IOException { 253 if (pos < 0) { 254 throw new ArrayIndexOutOfBoundsException("pos < 0"); 255 } 256 257 // Ensure there is space for the incoming data 258 if (pos >= length) { 259 pad(pos); 260 length = pos + 1; 261 } 262 263 // Insert the data. 264 byte[] buf = getCacheBlock(pos/BUFFER_LENGTH); 265 int offset = (int)(pos % BUFFER_LENGTH); 266 buf[offset] = (byte)b; 267 } 268 269 /** 270 * Returns the total length of data that has been cached, 271 * regardless of whether any early blocks have been disposed. 272 * This value will only ever increase. 273 */ 274 public long getLength() { 275 return length; 276 } 277 278 /** 279 * Returns the single byte at the given position, as an 280 * <code>int</code>. Returns -1 if this position has 281 * not been cached or has been disposed. 282 */ 283 public int read(long pos) throws IOException { 284 if (pos >= length) { 285 return -1; 286 } 287 288 byte[] buf = getCacheBlock(pos/BUFFER_LENGTH); 289 if (buf == null) { 290 return -1; 291 } 292 293 return buf[(int)(pos % BUFFER_LENGTH)] & 0xff; 294 } 295 296 /** 297 * Copy <code>len</code> bytes from the cache, starting 298 * at cache position <code>pos</code>, into the array 299 * <code>b</code> at offset <code>off</code>. 300 * 301 * @exception NullPointerException if b is <code>null</code> 302 * @exception IndexOutOfBoundsException if <code>off</code>, 303 * <code>len</code> or <code>pos</code> are negative or if 304 * <code>off + len > b.length</code> or if any portion of the 305 * requested data is not in the cache (including if 306 * <code>pos</code> is in a block that has already been disposed). 307 */ 308 public void read(byte[] b, int off, int len, long pos) 309 throws IOException { 310 if (b == null) { 311 throw new NullPointerException("b == null!"); 312 } 313 // Fix 4430357 - if off + len < 0, overflow occurred 314 if ((off < 0) || (len < 0) || (pos < 0) || 315 (off + len > b.length) || (off + len < 0)) { 316 throw new IndexOutOfBoundsException(); 317 } 318 if (pos + len > length) { 319 throw new IndexOutOfBoundsException(); 320 } 321 322 long index = pos/BUFFER_LENGTH; 323 int offset = (int)pos % BUFFER_LENGTH; 324 while (len > 0) { 325 int nbytes = Math.min(len, BUFFER_LENGTH - offset); 326 byte[] buf = getCacheBlock(index++); 327 System.arraycopy(buf, offset, b, off, nbytes); 328 329 len -= nbytes; 330 off += nbytes; 331 offset = 0; // Always after the first time 332 } 333 } 334 335 /** 336 * Free the blocks up to the position <code>pos</code>. 337 * The byte at <code>pos</code> remains available. 338 * 339 * @exception IndexOutOfBoundsException if <code>pos</code> 340 * is in a block that has already been disposed. 341 */ 342 public void disposeBefore(long pos) { 343 long index = pos/BUFFER_LENGTH; 344 if (index < cacheStart) { 345 throw new IndexOutOfBoundsException("pos already disposed"); 346 } 347 long numBlocks = Math.min(index - cacheStart, cache.size()); 348 for (long i = 0; i < numBlocks; i++) { 349 cache.remove(0); 350 } 351 this.cacheStart = index; 352 } 353 354 /** 355 * Erase the entire cache contents and reset the length to 0. 356 * The cache object may subsequently be reused as though it had just 357 * been allocated. 358 */ 359 public void reset() { 360 cache.clear(); 361 cacheStart = 0; 362 length = 0L; 363 } 364 }