blob: 960cf4ed93bbe0e326e997471cd4c85b0b4e313e [file] [log] [blame]
/*
* Copyright (c) 2014-2021 by Wen Yu
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License, v. 2.0 are satisfied: GNU General Public License, version 2
* or any later version.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
*/
package pixy.io;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
/**
* Implements a memory cached random access input stream to ease the
* decoding of some types of images such as TIFF which may need random
* access to the underlying stream.
* <p>
* Based on com.sun.media.jai.codec.MemoryCacheSeekableStream.
* <p>
* This implementation has a major drawback: It has no knowledge
* of the length of the stream, it is supposed to move forward
* even though it is possible to put the pointer at anywhere
* before the end of the stream.
*
* @author Wen Yu, yuwen_66@yahoo.com
* @version 1.0 02/09/2014
*/
public class MemoryCacheRandomAccessInputStream extends RandomAccessInputStream {
//
private static final int BUFFER_SHIFT = 12;
private static final int BUFFER_SIZE = 1 << BUFFER_SHIFT;
private static final int BUFFER_MASK = BUFFER_SIZE - 1;
private long pointer;
private List<byte[]> cache;
private int length;
private boolean foundEOS;
public MemoryCacheRandomAccessInputStream(InputStream src) {
super(src);
pointer = 0L;
cache = new ArrayList<byte[]>(10);
length = 0;
foundEOS = false;
}
public void close() throws IOException {
if(closed) return;
super.close();
cache.clear();
cache = null;
src.close();
src = null;
closed = true;
}
public long getStreamPointer() {
return pointer;
}
public int read() throws IOException {
ensureOpen();
long l = pointer + 1L;
long pos = readUntil(l);
if(pos >= l) {
byte[] buf = cache.get((int)(pointer>>BUFFER_SHIFT));
return buf[(int)(pointer++ & BUFFER_MASK)] & 0xff;
}
return -1;
}
public int read(byte[] bytes, int off, int len) throws IOException {
ensureOpen();
if(bytes == null)
throw new NullPointerException();
if(off<0 || len<0 || off+len>bytes.length)
throw new IndexOutOfBoundsException();
if(len == 0)
return 0;
long l = readUntil(pointer+len);
if (l <= pointer)
return -1;
byte[] buf = cache.get((int)(pointer >> BUFFER_SHIFT));
int k = Math.min(len, BUFFER_SIZE - (int)(pointer & BUFFER_MASK));
System.arraycopy(buf, (int)(pointer & BUFFER_MASK), bytes, off, k);
pointer += k;
return k;
}
private long readUntil(long pos) throws IOException {
if(pos < length)
return pos;
if(foundEOS)
return length;
int slot = (int)(pos >> BUFFER_SHIFT);
int startSlot = length >> BUFFER_SHIFT;
for(int k = startSlot; k <= slot; k++)
{
byte[] buf = new byte[BUFFER_SIZE];
cache.add(buf);
int len = BUFFER_SIZE;
int off = 0;
while(len > 0) {
int nbytes = src.read(buf, off, len);
if(nbytes == -1) {
foundEOS = true;
return length;
}
off += nbytes;
len -= nbytes;
length += nbytes;
}
}
return length;
}
public void seek(long loc) throws IOException {
ensureOpen();
if (loc<0L)
throw new IOException("Negtive seek position.");
pointer = loc;
}
@Override
public void shallowClose() throws IOException {
if(closed) return;
super.close();
cache.clear();
cache = null;
src = null;
closed = true;
}
}