0N/A/*
2362N/A * Copyright (c) 2000, 2003, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
2362N/A * published by the Free Software Foundation. Oracle designates this
0N/A * particular file as subject to the "Classpath" exception as provided
2362N/A * by Oracle in the LICENSE file that accompanied this code.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
0N/A */
0N/A
0N/Apackage javax.imageio.stream;
0N/A
0N/Aimport java.util.ArrayList;
0N/Aimport java.io.InputStream;
0N/Aimport java.io.OutputStream;
0N/Aimport java.io.IOException;
0N/A
0N/A/**
0N/A * Package-visible class consolidating common code for
0N/A * <code>MemoryCacheImageInputStream</code> and
0N/A * <code>MemoryCacheImageOutputStream</code>.
0N/A * This class keeps an <code>ArrayList</code> of 8K blocks,
0N/A * loaded sequentially. Blocks may only be disposed of
0N/A * from the index 0 forward. As blocks are freed, the
0N/A * corresponding entries in the array list are set to
0N/A * <code>null</code>, but no compacting is performed.
0N/A * This allows the index for each block to never change,
0N/A * and the length of the cache is always the same as the
0N/A * total amount of data ever cached. Cached data is
0N/A * therefore always contiguous from the point of last
0N/A * disposal to the current length.
0N/A *
0N/A * <p> The total number of blocks resident in the cache must not
0N/A * exceed <code>Integer.MAX_VALUE</code>. In practice, the limit of
0N/A * available memory will be exceeded long before this becomes an
0N/A * issue, since a full cache would contain 8192*2^31 = 16 terabytes of
0N/A * data.
0N/A *
0N/A * A <code>MemoryCache</code> may be reused after a call
0N/A * to <code>reset()</code>.
0N/A */
0N/Aclass MemoryCache {
0N/A
0N/A private static final int BUFFER_LENGTH = 8192;
0N/A
0N/A private ArrayList cache = new ArrayList();
0N/A
0N/A private long cacheStart = 0L;
0N/A
0N/A /**
0N/A * The largest position ever written to the cache.
0N/A */
0N/A private long length = 0L;
0N/A
0N/A private byte[] getCacheBlock(long blockNum) throws IOException {
0N/A long blockOffset = blockNum - cacheStart;
0N/A if (blockOffset > Integer.MAX_VALUE) {
0N/A // This can only happen when the cache hits 16 terabytes of
0N/A // contiguous data...
0N/A throw new IOException("Cache addressing limit exceeded!");
0N/A }
0N/A return (byte[])cache.get((int)blockOffset);
0N/A }
0N/A
0N/A /**
0N/A * Ensures that at least <code>pos</code> bytes are cached,
0N/A * or the end of the source is reached. The return value
0N/A * is equal to the smaller of <code>pos</code> and the
0N/A * length of the source.
0N/A */
0N/A public long loadFromStream(InputStream stream, long pos)
0N/A throws IOException {
0N/A // We've already got enough data cached
0N/A if (pos < length) {
0N/A return pos;
0N/A }
0N/A
0N/A int offset = (int)(length % BUFFER_LENGTH);
0N/A byte [] buf = null;
0N/A
0N/A long len = pos - length;
0N/A if (offset != 0) {
0N/A buf = getCacheBlock(length/BUFFER_LENGTH);
0N/A }
0N/A
0N/A while (len > 0) {
0N/A if (buf == null) {
0N/A try {
0N/A buf = new byte[BUFFER_LENGTH];
0N/A } catch (OutOfMemoryError e) {
0N/A throw new IOException("No memory left for cache!");
0N/A }
0N/A offset = 0;
0N/A }
0N/A
0N/A int left = BUFFER_LENGTH - offset;
0N/A int nbytes = (int)Math.min(len, (long)left);
0N/A nbytes = stream.read(buf, offset, nbytes);
0N/A if (nbytes == -1) {
0N/A return length; // EOF
0N/A }
0N/A
0N/A if (offset == 0) {
0N/A cache.add(buf);
0N/A }
0N/A
0N/A len -= nbytes;
0N/A length += nbytes;
0N/A offset += nbytes;
0N/A
0N/A if (offset >= BUFFER_LENGTH) {
0N/A // we've filled the current buffer, so a new one will be
0N/A // allocated next time around (and offset will be reset to 0)
0N/A buf = null;
0N/A }
0N/A }
0N/A
0N/A return pos;
0N/A }
0N/A
0N/A /**
0N/A * Writes out a portion of the cache to an <code>OutputStream</code>.
0N/A * This method preserves no state about the output stream, and does
0N/A * not dispose of any blocks containing bytes written. To dispose
0N/A * blocks, use {@link #disposeBefore <code>disposeBefore()</code>}.
0N/A *
0N/A * @exception IndexOutOfBoundsException if any portion of
0N/A * the requested data is not in the cache (including if <code>pos</code>
0N/A * is in a block already disposed), or if either <code>pos</code> or
0N/A * <code>len</code> is < 0.
0N/A */
0N/A public void writeToStream(OutputStream stream, long pos, long len)
0N/A throws IOException {
0N/A if (pos + len > length) {
0N/A throw new IndexOutOfBoundsException("Argument out of cache");
0N/A }
0N/A if ((pos < 0) || (len < 0)) {
0N/A throw new IndexOutOfBoundsException("Negative pos or len");
0N/A }
0N/A if (len == 0) {
0N/A return;
0N/A }
0N/A
0N/A long bufIndex = pos/BUFFER_LENGTH;
0N/A if (bufIndex < cacheStart) {
0N/A throw new IndexOutOfBoundsException("pos already disposed");
0N/A }
0N/A int offset = (int)(pos % BUFFER_LENGTH);
0N/A
0N/A byte[] buf = getCacheBlock(bufIndex++);
0N/A while (len > 0) {
0N/A if (buf == null) {
0N/A buf = getCacheBlock(bufIndex++);
0N/A offset = 0;
0N/A }
0N/A int nbytes = (int)Math.min(len, (long)(BUFFER_LENGTH - offset));
0N/A stream.write(buf, offset, nbytes);
0N/A buf = null;
0N/A len -= nbytes;
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Ensure that there is space to write a byte at the given position.
0N/A */
0N/A private void pad(long pos) throws IOException {
0N/A long currIndex = cacheStart + cache.size() - 1;
0N/A long lastIndex = pos/BUFFER_LENGTH;
0N/A long numNewBuffers = lastIndex - currIndex;
0N/A for (long i = 0; i < numNewBuffers; i++) {
0N/A try {
0N/A cache.add(new byte[BUFFER_LENGTH]);
0N/A } catch (OutOfMemoryError e) {
0N/A throw new IOException("No memory left for cache!");
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Overwrites and/or appends the cache from a byte array.
0N/A * The length of the cache will be extended as needed to hold
0N/A * the incoming data.
0N/A *
0N/A * @param b an array of bytes containing data to be written.
0N/A * @param off the starting offset withing the data array.
0N/A * @param len the number of bytes to be written.
0N/A * @param pos the cache position at which to begin writing.
0N/A *
0N/A * @exception NullPointerException if <code>b</code> is <code>null</code>.
0N/A * @exception IndexOutOfBoundsException if <code>off</code>,
0N/A * <code>len</code>, or <code>pos</code> are negative,
0N/A * or if <code>off+len > b.length</code>.
0N/A */
0N/A public void write(byte[] b, int off, int len, long pos)
0N/A throws IOException {
0N/A if (b == null) {
0N/A throw new NullPointerException("b == null!");
0N/A }
0N/A // Fix 4430357 - if off + len < 0, overflow occurred
0N/A if ((off < 0) || (len < 0) || (pos < 0) ||
0N/A (off + len > b.length) || (off + len < 0)) {
0N/A throw new IndexOutOfBoundsException();
0N/A }
0N/A
0N/A // Ensure there is space for the incoming data
0N/A long lastPos = pos + len - 1;
0N/A if (lastPos >= length) {
0N/A pad(lastPos);
0N/A length = lastPos + 1;
0N/A }
0N/A
0N/A // Copy the data into the cache, block by block
0N/A int offset = (int)(pos % BUFFER_LENGTH);
0N/A while (len > 0) {
0N/A byte[] buf = getCacheBlock(pos/BUFFER_LENGTH);
0N/A int nbytes = Math.min(len, BUFFER_LENGTH - offset);
0N/A System.arraycopy(b, off, buf, offset, nbytes);
0N/A
0N/A pos += nbytes;
0N/A off += nbytes;
0N/A len -= nbytes;
0N/A offset = 0; // Always after the first time
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Overwrites or appends a single byte to the cache.
0N/A * The length of the cache will be extended as needed to hold
0N/A * the incoming data.
0N/A *
0N/A * @param b an <code>int</code> whose 8 least significant bits
0N/A * will be written.
0N/A * @param pos the cache position at which to begin writing.
0N/A *
0N/A * @exception IndexOutOfBoundsException if <code>pos</code> is negative.
0N/A */
0N/A public void write(int b, long pos) throws IOException {
0N/A if (pos < 0) {
0N/A throw new ArrayIndexOutOfBoundsException("pos < 0");
0N/A }
0N/A
0N/A // Ensure there is space for the incoming data
0N/A if (pos >= length) {
0N/A pad(pos);
0N/A length = pos + 1;
0N/A }
0N/A
0N/A // Insert the data.
0N/A byte[] buf = getCacheBlock(pos/BUFFER_LENGTH);
0N/A int offset = (int)(pos % BUFFER_LENGTH);
0N/A buf[offset] = (byte)b;
0N/A }
0N/A
0N/A /**
0N/A * Returns the total length of data that has been cached,
0N/A * regardless of whether any early blocks have been disposed.
0N/A * This value will only ever increase.
0N/A */
0N/A public long getLength() {
0N/A return length;
0N/A }
0N/A
0N/A /**
0N/A * Returns the single byte at the given position, as an
0N/A * <code>int</code>. Returns -1 if this position has
0N/A * not been cached or has been disposed.
0N/A */
0N/A public int read(long pos) throws IOException {
0N/A if (pos >= length) {
0N/A return -1;
0N/A }
0N/A
0N/A byte[] buf = getCacheBlock(pos/BUFFER_LENGTH);
0N/A if (buf == null) {
0N/A return -1;
0N/A }
0N/A
0N/A return buf[(int)(pos % BUFFER_LENGTH)] & 0xff;
0N/A }
0N/A
0N/A /**
0N/A * Copy <code>len</code> bytes from the cache, starting
0N/A * at cache position <code>pos</code>, into the array
0N/A * <code>b</code> at offset <code>off</code>.
0N/A *
0N/A * @exception NullPointerException if b is <code>null</code>
0N/A * @exception IndexOutOfBoundsException if <code>off</code>,
0N/A * <code>len</code> or <code>pos</code> are negative or if
0N/A * <code>off + len > b.length</code> or if any portion of the
0N/A * requested data is not in the cache (including if
0N/A * <code>pos</code> is in a block that has already been disposed).
0N/A */
0N/A public void read(byte[] b, int off, int len, long pos)
0N/A throws IOException {
0N/A if (b == null) {
0N/A throw new NullPointerException("b == null!");
0N/A }
0N/A // Fix 4430357 - if off + len < 0, overflow occurred
0N/A if ((off < 0) || (len < 0) || (pos < 0) ||
0N/A (off + len > b.length) || (off + len < 0)) {
0N/A throw new IndexOutOfBoundsException();
0N/A }
0N/A if (pos + len > length) {
0N/A throw new IndexOutOfBoundsException();
0N/A }
0N/A
0N/A long index = pos/BUFFER_LENGTH;
0N/A int offset = (int)pos % BUFFER_LENGTH;
0N/A while (len > 0) {
0N/A int nbytes = Math.min(len, BUFFER_LENGTH - offset);
0N/A byte[] buf = getCacheBlock(index++);
0N/A System.arraycopy(buf, offset, b, off, nbytes);
0N/A
0N/A len -= nbytes;
0N/A off += nbytes;
0N/A offset = 0; // Always after the first time
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Free the blocks up to the position <code>pos</code>.
0N/A * The byte at <code>pos</code> remains available.
0N/A *
0N/A * @exception IndexOutOfBoundsException if <code>pos</code>
0N/A * is in a block that has already been disposed.
0N/A */
0N/A public void disposeBefore(long pos) {
0N/A long index = pos/BUFFER_LENGTH;
0N/A if (index < cacheStart) {
0N/A throw new IndexOutOfBoundsException("pos already disposed");
0N/A }
0N/A long numBlocks = Math.min(index - cacheStart, cache.size());
0N/A for (long i = 0; i < numBlocks; i++) {
0N/A cache.remove(0);
0N/A }
0N/A this.cacheStart = index;
0N/A }
0N/A
0N/A /**
0N/A * Erase the entire cache contents and reset the length to 0.
0N/A * The cache object may subsequently be reused as though it had just
0N/A * been allocated.
0N/A */
0N/A public void reset() {
0N/A cache.clear();
0N/A cacheStart = 0;
0N/A length = 0L;
0N/A }
0N/A }