/* * This file is part of muCommander, http://www.mucommander.com * Copyright (C) 2002-2016 Maxence Bernard * * muCommander is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * muCommander is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.mucommander.commons.io; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStream; /** * <code>RandomAccessInputStream</code> is an <code>InputStream</code> with random access. * * The following <code>java.io.InputStream</code> methods are overridden to provide an improved implementation: * <ul> * <li>{@link #mark(int)}</li> * <li>{@link #reset()}</li> * <li>{@link #markSupported()}</li> * <li>{@link #skip(long)}</li> * <li>{@link #available()}</li> * </ul> * * <b>Important:</b> <code>BufferedInputStream</code> or any wrapper <code>InputStream</code> class that uses a read buffer * CANNOT be used with a <code>RandomAccessInputStream</code> if the {@link #seek(long)} method is to be used. Doing so * would corrupt the read buffer and yield to data inconsistencies. * * @author Maxence Bernard, Nicolas Rinaudo */ public abstract class RandomAccessInputStream extends InputStream implements RandomAccess { /** Logger used by this class. */ private static final Logger LOGGER = LoggerFactory.getLogger(RandomAccessInputStream.class); /** The last offset set by {@link #mark(int)} */ private long markOffset; /** * Creates a new RandomAccessInputStream. */ public RandomAccessInputStream() { } /** * Reads <code>b.length</code> bytes from this file into the byte array, starting at the current file pointer. * This method reads repeatedly from the file until the requested number of bytes are read. This method blocks until * the requested number of bytes are read, the end of the stream is detected, or an exception is thrown. * * @param b the buffer into which the data is read. * @throws java.io.EOFException if this file reaches the end before reading all the bytes. * @throws IOException if an I/O error occurs. */ public void readFully(byte b[]) throws IOException { StreamUtils.readFully(this, b, 0, b.length); } /** * Reads exactly <code>len</code> bytes from this file into the byte array, starting at the current file pointer. * This method reads repeatedly from the file until the requested number of bytes are read. This method blocks until * the requested number of bytes are read, the end of the stream is detected, or an exception is thrown. * * @param b the buffer into which the data is read. * @param off the start offset of the data. * @param len the number of bytes to read. * @throws java.io.EOFException if this file reaches the end before reading all the bytes. * @throws IOException if an I/O error occurs. */ public void readFully(byte b[], int off, int len) throws IOException { StreamUtils.readFully(this, b, off, len); } //////////////////////// // Overridden methods // //////////////////////// /** * Skips (up to) the specified number of bytes and returns the number of bytes effectively skipped. * The exact given number of bytes will be skipped as long as the current offset as returned by {@link #getOffset()} * plus the number of bytes to skip doesn't exceed the length of this stream as returned by {@link #getLength()}. * If it does, all the remaining bytes will be skipped so that the offset of this stream will be positionned to * {@link #getLength()}. * Returns <code>-1</code> if the offset is already positionned to the end of the stream when this method is called. * * @param n number of bytes to skip * @return the number of bytes that have effectively been skipped, -1 if the offset is already positionned to the * end of the stream when this method is called * @throws IOException if something went wrong */ @Override public long skip(long n) throws IOException { if(n<=0) return 0; long offset = getOffset(); long length = getLength(); // Return -1 if the offset is already at the end of the stream if(offset>=length) return -1; // Makes sure not to go beyond the end of the stream long newOffset = offset + n; if (newOffset > length) newOffset = length; // Seek to the new offset seek(newOffset); // Return the actual number of bytes skipped return (int) (newOffset - offset); } /** * Return the number of bytes that are available for reading, that is: {@link #getLength()} - {@link #getOffset()} - 1. * Since <code>InputStream.available()</code> returns an int and this method overrides it, a maximum of * <code>Integer.MAX_VALUE</code> can be returned, even if this stream has more bytes available. * * @return the number of bytes that are available for reading. * @throws IOException if something went wrong */ @Override public int available() throws IOException { return (int)(getLength() - getOffset() - 1); } /** * Overrides <code>InputStream.mark()</code> to provide a working implementation of the method. The given readLimit * is simply ignored, the stream can be repositionned using {@link #reset()} with no limit on the number of bytes * read after <code>mark()</code> has been called. * * @param readLimit this parameter has no effect and is simply ignored */ @Override public synchronized void mark(int readLimit) { try { this.markOffset = getOffset(); } catch (IOException e) { LOGGER.info("Caught exception", e); } } /** * Overrides <code>InputStream.mark()</code> to provide a working implementation of the method. * * @throws IOException if something went wrong */ @Override public synchronized void reset() throws IOException { seek(this.markOffset); } /** * Always returns <code>true</code>: {@link #mark(int)} and {@link #reset()} methods are supported. */ @Override public boolean markSupported() { return true; } ////////////////////// // Abstract methods // ////////////////////// /** * Reads up to <code>len</code> bytes of data from this file into an array of bytes. This method blocks until at * least one byte of input is available. * * @param b the buffer into which the data is read * @param off the start offset of the data * @param len the maximum number of bytes read * @return the total number of bytes read into the buffer, or -1 if there is no more data because the end of the * file has been reached. * @throws IOException if an I/O error occurs */ @Override public abstract int read(byte b[], int off, int len) throws IOException; /** * Closes this stream and releases any system resources associated with the stream. * A closed stream cannot perform input operations and cannot be reopened. * * @throws IOException if an I/O error occurs. */ @Override public abstract void close() throws IOException; }