/* * Copyright (c) 2008, Harald Kuhr * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name "TwelveMonkeys" nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.twelvemonkeys.io; import java.io.IOException; import java.io.InputStream; import java.util.Stack; /** * Abstract base class for {@code InputStream}s implementing the {@code Seekable} interface. * <p/> * @see SeekableOutputStream * * @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a> * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/SeekableInputStream.java#4 $ */ public abstract class SeekableInputStream extends InputStream implements Seekable { // TODO: It's at the moment not possible to create subclasses outside this // package, as there's no access to position. position needs to be // updated from the read/read/read methods... /** The stream position in this stream */ long position; long flushedPosition; boolean closed; protected Stack<Long> markedPositions = new Stack<Long>(); /// InputStream overrides @Override public final int read(byte[] pBytes) throws IOException { return read(pBytes, 0, pBytes != null ? pBytes.length : 1); } /** * Implemented using {@code seek(currentPos + pLength)}. * * @param pLength the number of bytes to skip * @return the actual number of bytes skipped (may be equal to or less * than {@code pLength}) * * @throws IOException if an I/O exception occurs during skip */ @Override public final long skip(final long pLength) throws IOException { long pos = position; long wantedPosition = pos + pLength; if (wantedPosition < flushedPosition) { throw new IOException("position < flushedPosition"); } // Stop at stream length for compatibility, even though it might be allowed // to seek past end of stream int available = available(); if (available > 0) { seek(Math.min(wantedPosition, pos + available)); } // TODO: Add optimization for streams with known length! else { // Slow mode... int toSkip = (int) Math.max(Math.min(pLength, 512), -512); while (toSkip > 0 && read() >= 0) { toSkip--; } } return position - pos; } @Override public final void mark(int pLimit) { mark(); // TODO: We don't really need to do this.. Is it a good idea? try { flushBefore(Math.max(position - pLimit, flushedPosition)); } catch (IOException ignore) { // Ignore, as it's not really critical } } /** * Returns {@code true}, as marking is always supported. * * @return {@code true}. */ @Override public final boolean markSupported() { return true; } /// Seekable implementation public final void seek(long pPosition) throws IOException { checkOpen(); // NOTE: This is correct according to javax.imageio (IndexOutOfBoundsException), // but it's kind of inconsistent with reset that throws IOException... if (pPosition < flushedPosition) { throw new IndexOutOfBoundsException("position < flushedPosition"); } seekImpl(pPosition); position = pPosition; } protected abstract void seekImpl(long pPosition) throws IOException; public final void mark() { markedPositions.push(position); } @Override public final void reset() throws IOException { checkOpen(); if (!markedPositions.isEmpty()) { long newPos = markedPositions.pop(); // NOTE: This is correct according to javax.imageio (IOException), // but it's kind of inconsistent with seek that throws IndexOutOfBoundsException... if (newPos < flushedPosition) { throw new IOException("Previous marked position has been discarded"); } seek(newPos); } else { // TODO: To iron out some wrinkles due to conflicting contracts // (InputStream and Seekable both declare reset), // we might need to reset to the last marked position instead.. // However, that becomes REALLY confusing if that position is after // the current position... seek(0); } } public final void flushBefore(long pPosition) throws IOException { if (pPosition < flushedPosition) { throw new IndexOutOfBoundsException("position < flushedPosition"); } if (pPosition > getStreamPosition()) { throw new IndexOutOfBoundsException("position > stream position"); } checkOpen(); flushBeforeImpl(pPosition); flushedPosition = pPosition; } /** * Discards the initial portion of the stream prior to the indicated postion. * * @param pPosition the position to flush to * @throws IOException if an I/O exception occurs during the flush operation * * @see #flushBefore(long) */ protected abstract void flushBeforeImpl(long pPosition) throws IOException; public final void flush() throws IOException { flushBefore(flushedPosition); } public final long getFlushedPosition() throws IOException { checkOpen(); return flushedPosition; } public final long getStreamPosition() throws IOException { checkOpen(); return position; } protected final void checkOpen() throws IOException { if (closed) { throw new IOException("closed"); } } @Override public final void close() throws IOException { checkOpen(); closed = true; closeImpl(); } protected abstract void closeImpl() throws IOException; /** * Finalizes this object prior to garbage collection. The * {@code close} method is called to close any open input * source. This method should not be called from application * code. * * @exception Throwable if an error occurs during superclass * finalization. */ @Override protected void finalize() throws Throwable { if (!closed) { try { close(); } catch (IOException ignore) { // Ignroe } } super.finalize(); } }