001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.io.input;
019
020import java.io.IOException;
021import java.io.InputStream;
022import java.io.RandomAccessFile;
023import java.util.Objects;
024
025/**
026 * Streams data from a {@link RandomAccessFile} starting at its current position.
027 *
028 * @since 2.8.0
029 */
030public class RandomAccessFileInputStream extends InputStream {
031
032    private final boolean closeOnClose;
033    private final RandomAccessFile randomAccessFile;
034
035    /**
036     * Constructs a new instance configured to leave the underlying file open when this stream is closed.
037     *
038     * @param file The file to stream.
039     */
040    public RandomAccessFileInputStream(final RandomAccessFile file) {
041        this(file, false);
042    }
043
044    /**
045     * Constructs a new instance.
046     *
047     * @param file The file to stream.
048     * @param closeOnClose Whether to close the underlying file when this stream is closed.
049     */
050    public RandomAccessFileInputStream(final RandomAccessFile file, final boolean closeOnClose) {
051        this.randomAccessFile = Objects.requireNonNull(file, "file");
052        this.closeOnClose = closeOnClose;
053    }
054
055    /**
056     * Returns an estimate of the number of bytes that can be read (or skipped over) from this input stream.
057     *
058     * If there are more than {@link Integer#MAX_VALUE} bytes available, return {@link Integer#MAX_VALUE}.
059     *
060     * @return An estimate of the number of bytes that can be read.
061     * @throws IOException If an I/O error occurs.
062     */
063    @Override
064    public int available() throws IOException {
065        final long avail = availableLong();
066        if (avail > Integer.MAX_VALUE) {
067            return Integer.MAX_VALUE;
068        }
069        return (int) avail;
070    }
071
072    /**
073     * Returns the number of bytes that can be read (or skipped over) from this input stream.
074     *
075     * @return The number of bytes that can be read.
076     * @throws IOException If an I/O error occurs.
077     */
078    public long availableLong() throws IOException {
079        return randomAccessFile.length() - randomAccessFile.getFilePointer();
080    }
081
082    @Override
083    public void close() throws IOException {
084        super.close();
085        if (closeOnClose) {
086            randomAccessFile.close();
087        }
088    }
089
090    /**
091     * Gets the underlying file.
092     *
093     * @return the underlying file.
094     */
095    public RandomAccessFile getRandomAccessFile() {
096        return randomAccessFile;
097    }
098
099    /**
100     * Returns whether to close the underlying file when this stream is closed.
101     *
102     * @return Whether to close the underlying file when this stream is closed.
103     */
104    public boolean isCloseOnClose() {
105        return closeOnClose;
106    }
107
108    @Override
109    public int read() throws IOException {
110        return randomAccessFile.read();
111    }
112
113    @Override
114    public int read(final byte[] bytes) throws IOException {
115        return randomAccessFile.read(bytes);
116    }
117
118    @Override
119    public int read(final byte[] bytes, final int offset, final int length) throws IOException {
120        return randomAccessFile.read(bytes, offset, length);
121    }
122
123    /**
124     * Delegates to the underlying file.
125     *
126     * @param position See {@link RandomAccessFile#seek(long)}.
127     * @throws IOException See {@link RandomAccessFile#seek(long)}.
128     * @see RandomAccessFile#seek(long)
129     */
130    private void seek(final long position) throws IOException {
131        randomAccessFile.seek(position);
132    }
133
134    @Override
135    public long skip(final long skipCount) throws IOException {
136        if (skipCount <= 0) {
137            return 0;
138        }
139        final long filePointer = randomAccessFile.getFilePointer();
140        final long fileLength = randomAccessFile.length();
141        if (filePointer >= fileLength) {
142            return 0;
143        }
144        final long targetPos = filePointer + skipCount;
145        final long newPos = targetPos > fileLength ? fileLength - 1 : targetPos;
146        if (newPos > 0) {
147            seek(newPos);
148        }
149        return randomAccessFile.getFilePointer() - filePointer;
150    }
151}