Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/javax/imageio/stream/MemoryCache.java
38918 views
/*1* Copyright (c) 2000, 2003, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package javax.imageio.stream;2627import java.util.ArrayList;28import java.io.InputStream;29import java.io.OutputStream;30import java.io.IOException;3132/**33* Package-visible class consolidating common code for34* <code>MemoryCacheImageInputStream</code> and35* <code>MemoryCacheImageOutputStream</code>.36* This class keeps an <code>ArrayList</code> of 8K blocks,37* loaded sequentially. Blocks may only be disposed of38* from the index 0 forward. As blocks are freed, the39* corresponding entries in the array list are set to40* <code>null</code>, but no compacting is performed.41* This allows the index for each block to never change,42* and the length of the cache is always the same as the43* total amount of data ever cached. Cached data is44* therefore always contiguous from the point of last45* disposal to the current length.46*47* <p> The total number of blocks resident in the cache must not48* exceed <code>Integer.MAX_VALUE</code>. In practice, the limit of49* available memory will be exceeded long before this becomes an50* issue, since a full cache would contain 8192*2^31 = 16 terabytes of51* data.52*53* A <code>MemoryCache</code> may be reused after a call54* to <code>reset()</code>.55*/56class MemoryCache {5758private static final int BUFFER_LENGTH = 8192;5960private ArrayList cache = new ArrayList();6162private long cacheStart = 0L;6364/**65* The largest position ever written to the cache.66*/67private long length = 0L;6869private byte[] getCacheBlock(long blockNum) throws IOException {70long blockOffset = blockNum - cacheStart;71if (blockOffset > Integer.MAX_VALUE) {72// This can only happen when the cache hits 16 terabytes of73// contiguous data...74throw new IOException("Cache addressing limit exceeded!");75}76return (byte[])cache.get((int)blockOffset);77}7879/**80* Ensures that at least <code>pos</code> bytes are cached,81* or the end of the source is reached. The return value82* is equal to the smaller of <code>pos</code> and the83* length of the source.84*/85public long loadFromStream(InputStream stream, long pos)86throws IOException {87// We've already got enough data cached88if (pos < length) {89return pos;90}9192int offset = (int)(length % BUFFER_LENGTH);93byte [] buf = null;9495long len = pos - length;96if (offset != 0) {97buf = getCacheBlock(length/BUFFER_LENGTH);98}99100while (len > 0) {101if (buf == null) {102try {103buf = new byte[BUFFER_LENGTH];104} catch (OutOfMemoryError e) {105throw new IOException("No memory left for cache!");106}107offset = 0;108}109110int left = BUFFER_LENGTH - offset;111int nbytes = (int)Math.min(len, (long)left);112nbytes = stream.read(buf, offset, nbytes);113if (nbytes == -1) {114return length; // EOF115}116117if (offset == 0) {118cache.add(buf);119}120121len -= nbytes;122length += nbytes;123offset += nbytes;124125if (offset >= BUFFER_LENGTH) {126// we've filled the current buffer, so a new one will be127// allocated next time around (and offset will be reset to 0)128buf = null;129}130}131132return pos;133}134135/**136* Writes out a portion of the cache to an <code>OutputStream</code>.137* This method preserves no state about the output stream, and does138* not dispose of any blocks containing bytes written. To dispose139* blocks, use {@link #disposeBefore <code>disposeBefore()</code>}.140*141* @exception IndexOutOfBoundsException if any portion of142* the requested data is not in the cache (including if <code>pos</code>143* is in a block already disposed), or if either <code>pos</code> or144* <code>len</code> is < 0.145*/146public void writeToStream(OutputStream stream, long pos, long len)147throws IOException {148if (pos + len > length) {149throw new IndexOutOfBoundsException("Argument out of cache");150}151if ((pos < 0) || (len < 0)) {152throw new IndexOutOfBoundsException("Negative pos or len");153}154if (len == 0) {155return;156}157158long bufIndex = pos/BUFFER_LENGTH;159if (bufIndex < cacheStart) {160throw new IndexOutOfBoundsException("pos already disposed");161}162int offset = (int)(pos % BUFFER_LENGTH);163164byte[] buf = getCacheBlock(bufIndex++);165while (len > 0) {166if (buf == null) {167buf = getCacheBlock(bufIndex++);168offset = 0;169}170int nbytes = (int)Math.min(len, (long)(BUFFER_LENGTH - offset));171stream.write(buf, offset, nbytes);172buf = null;173len -= nbytes;174}175}176177/**178* Ensure that there is space to write a byte at the given position.179*/180private void pad(long pos) throws IOException {181long currIndex = cacheStart + cache.size() - 1;182long lastIndex = pos/BUFFER_LENGTH;183long numNewBuffers = lastIndex - currIndex;184for (long i = 0; i < numNewBuffers; i++) {185try {186cache.add(new byte[BUFFER_LENGTH]);187} catch (OutOfMemoryError e) {188throw new IOException("No memory left for cache!");189}190}191}192193/**194* Overwrites and/or appends the cache from a byte array.195* The length of the cache will be extended as needed to hold196* the incoming data.197*198* @param b an array of bytes containing data to be written.199* @param off the starting offset withing the data array.200* @param len the number of bytes to be written.201* @param pos the cache position at which to begin writing.202*203* @exception NullPointerException if <code>b</code> is <code>null</code>.204* @exception IndexOutOfBoundsException if <code>off</code>,205* <code>len</code>, or <code>pos</code> are negative,206* or if <code>off+len > b.length</code>.207*/208public void write(byte[] b, int off, int len, long pos)209throws IOException {210if (b == null) {211throw new NullPointerException("b == null!");212}213// Fix 4430357 - if off + len < 0, overflow occurred214if ((off < 0) || (len < 0) || (pos < 0) ||215(off + len > b.length) || (off + len < 0)) {216throw new IndexOutOfBoundsException();217}218219// Ensure there is space for the incoming data220long lastPos = pos + len - 1;221if (lastPos >= length) {222pad(lastPos);223length = lastPos + 1;224}225226// Copy the data into the cache, block by block227int offset = (int)(pos % BUFFER_LENGTH);228while (len > 0) {229byte[] buf = getCacheBlock(pos/BUFFER_LENGTH);230int nbytes = Math.min(len, BUFFER_LENGTH - offset);231System.arraycopy(b, off, buf, offset, nbytes);232233pos += nbytes;234off += nbytes;235len -= nbytes;236offset = 0; // Always after the first time237}238}239240/**241* Overwrites or appends a single byte to the cache.242* The length of the cache will be extended as needed to hold243* the incoming data.244*245* @param b an <code>int</code> whose 8 least significant bits246* will be written.247* @param pos the cache position at which to begin writing.248*249* @exception IndexOutOfBoundsException if <code>pos</code> is negative.250*/251public void write(int b, long pos) throws IOException {252if (pos < 0) {253throw new ArrayIndexOutOfBoundsException("pos < 0");254}255256// Ensure there is space for the incoming data257if (pos >= length) {258pad(pos);259length = pos + 1;260}261262// Insert the data.263byte[] buf = getCacheBlock(pos/BUFFER_LENGTH);264int offset = (int)(pos % BUFFER_LENGTH);265buf[offset] = (byte)b;266}267268/**269* Returns the total length of data that has been cached,270* regardless of whether any early blocks have been disposed.271* This value will only ever increase.272*/273public long getLength() {274return length;275}276277/**278* Returns the single byte at the given position, as an279* <code>int</code>. Returns -1 if this position has280* not been cached or has been disposed.281*/282public int read(long pos) throws IOException {283if (pos >= length) {284return -1;285}286287byte[] buf = getCacheBlock(pos/BUFFER_LENGTH);288if (buf == null) {289return -1;290}291292return buf[(int)(pos % BUFFER_LENGTH)] & 0xff;293}294295/**296* Copy <code>len</code> bytes from the cache, starting297* at cache position <code>pos</code>, into the array298* <code>b</code> at offset <code>off</code>.299*300* @exception NullPointerException if b is <code>null</code>301* @exception IndexOutOfBoundsException if <code>off</code>,302* <code>len</code> or <code>pos</code> are negative or if303* <code>off + len > b.length</code> or if any portion of the304* requested data is not in the cache (including if305* <code>pos</code> is in a block that has already been disposed).306*/307public void read(byte[] b, int off, int len, long pos)308throws IOException {309if (b == null) {310throw new NullPointerException("b == null!");311}312// Fix 4430357 - if off + len < 0, overflow occurred313if ((off < 0) || (len < 0) || (pos < 0) ||314(off + len > b.length) || (off + len < 0)) {315throw new IndexOutOfBoundsException();316}317if (pos + len > length) {318throw new IndexOutOfBoundsException();319}320321long index = pos/BUFFER_LENGTH;322int offset = (int)pos % BUFFER_LENGTH;323while (len > 0) {324int nbytes = Math.min(len, BUFFER_LENGTH - offset);325byte[] buf = getCacheBlock(index++);326System.arraycopy(buf, offset, b, off, nbytes);327328len -= nbytes;329off += nbytes;330offset = 0; // Always after the first time331}332}333334/**335* Free the blocks up to the position <code>pos</code>.336* The byte at <code>pos</code> remains available.337*338* @exception IndexOutOfBoundsException if <code>pos</code>339* is in a block that has already been disposed.340*/341public void disposeBefore(long pos) {342long index = pos/BUFFER_LENGTH;343if (index < cacheStart) {344throw new IndexOutOfBoundsException("pos already disposed");345}346long numBlocks = Math.min(index - cacheStart, cache.size());347for (long i = 0; i < numBlocks; i++) {348cache.remove(0);349}350this.cacheStart = index;351}352353/**354* Erase the entire cache contents and reset the length to 0.355* The cache object may subsequently be reused as though it had just356* been allocated.357*/358public void reset() {359cache.clear();360cacheStart = 0;361length = 0L;362}363}364365366