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  }