    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
    025import org.apache.commons.io.RandomAccessFileMode;
    026import org.apache.commons.io.build.AbstractOrigin;
    027import org.apache.commons.io.build.AbstractStreamBuilder;
    028
    029/**
    030 * Streams data from a {@link RandomAccessFile} starting at its current position.
    031 * <p>
    032 * To build an instance, see {@link Builder}.
    033 * </p>
    034 * @since 2.8.0
    035 */
    036public class RandomAccessFileInputStream extends InputStream {
    037
    038    /**
    039     * Builds a new {@link RandomAccessFileInputStream} instance.
    040     * <p>
    041     * For example:
    042     * </p>
    043     * <pre>{@code
    044     * RandomAccessFileInputStream s = RandomAccessFileInputStream.builder()
    045     *   .setPath(path)
    046     *   .setCloseOnClose(true)
    047     *   .get();}
    048     * </pre>
    049     *
    050     * @since 2.12.0
    051     */
    052    public static class Builder extends AbstractStreamBuilder<RandomAccessFileInputStream, Builder> {
    053
    054        private RandomAccessFile randomAccessFile;
    055        private boolean closeOnClose;
    056
    057        /**
    058         * Constructs a new instance.
    059         * <p>
    060         * This builder use the aspects RandomAccessFile or File, and closeOnClose. Only set one of RandomAccessFile or an origin that can be converted to a
    061         * File.
    062         * </p>
    063         * <p>
    064         * If RandomAccessFile is not set, then you must provide an origin that can be converted to a File by this builder, otherwise, this call will throw an
    065         * {@link UnsupportedOperationException}.
    066         * </p>
    067         *
    068         * @return a new instance.
    069         * @throws IllegalStateException if both RandomAccessFile and origin are set.
    070         * @throws UnsupportedOperationException if the origin cannot provide a File.
    071         * @see AbstractOrigin#getFile()
    072         */
    073        @SuppressWarnings("resource") // Caller closes depending on settings
    074        @Override
    075        public RandomAccessFileInputStream get() throws IOException {
    076            if (randomAccessFile != null) {
    077                if (getOrigin() != null) {
    078                    throw new IllegalStateException(String.format("Only set one of RandomAccessFile (%s) or origin (%s)", randomAccessFile, getOrigin()));
    079                }
    080                return new RandomAccessFileInputStream(randomAccessFile, closeOnClose);
    081            }
    082            return new RandomAccessFileInputStream(RandomAccessFileMode.READ_ONLY.create(getOrigin().getFile()), closeOnClose);
    083        }
    084
    085        /**
    086         * Sets whether to close the underlying file when this stream is closed.
    087         *
    088         * @param closeOnClose Whether to close the underlying file when this stream is closed.
    089         * @return this
    090         */
    091        public Builder setCloseOnClose(final boolean closeOnClose) {
    092            this.closeOnClose = closeOnClose;
    093            return this;
    094        }
    095
    096        /**
    097         * Sets the RandomAccessFile to stream.
    098         *
    099         * @param randomAccessFile the RandomAccessFile to stream.
    100         * @return this
    101         */
    102        public Builder setRandomAccessFile(final RandomAccessFile randomAccessFile) {
    103            this.randomAccessFile = randomAccessFile;
    104            return this;
    105        }
    106
    107    }
    108
    109    /**
    110     * Constructs a new {@link Builder}.
    111     *
    112     * @return a new {@link Builder}.
    113     * @since 2.12.0
    114     */
    115    public static Builder builder() {
    116        return new Builder();
    117    }
    118
    119    private final boolean closeOnClose;
    120    private final RandomAccessFile randomAccessFile;
    121
    122    /**
    123     * Constructs a new instance configured to leave the underlying file open when this stream is closed.
    124     *
    125     * @param file The file to stream.
    126     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
    127     */
    128    @Deprecated
    129    public RandomAccessFileInputStream(final RandomAccessFile file) {
    130        this(file, false);
    131    }
    132
    133    /**
    134     * Constructs a new instance.
    135     *
    136     * @param file         The file to stream.
    137     * @param closeOnClose Whether to close the underlying file when this stream is closed.
    138     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
    139     */
    140    @Deprecated
    141    public RandomAccessFileInputStream(final RandomAccessFile file, final boolean closeOnClose) {
    142        this.randomAccessFile = Objects.requireNonNull(file, "file");
    143        this.closeOnClose = closeOnClose;
    144    }
    145
    146    /**
    147     * Returns an estimate of the number of bytes that can be read (or skipped over) from this input stream.
    148     *
    149     * If there are more than {@link Integer#MAX_VALUE} bytes available, return {@link Integer#MAX_VALUE}.
    150     *
    151     * @return An estimate of the number of bytes that can be read.
    152     * @throws IOException If an I/O error occurs.
    153     */
    154    @Override
    155    public int available() throws IOException {
    156        final long avail = availableLong();
    157        if (avail > Integer.MAX_VALUE) {
    158            return Integer.MAX_VALUE;
    159        }
    160        return (int) avail;
    161    }
    162
    163    /**
    164     * Returns the number of bytes that can be read (or skipped over) from this input stream.
    165     *
    166     * @return The number of bytes that can be read.
    167     * @throws IOException If an I/O error occurs.
    168     */
    169    public long availableLong() throws IOException {
    170        return randomAccessFile.length() - randomAccessFile.getFilePointer();
    171    }
    172
    173    @Override
    174    public void close() throws IOException {
    175        super.close();
    176        if (closeOnClose) {
    177            randomAccessFile.close();
    178        }
    179    }
    180
    181    /**
    182     * Gets the underlying file.
    183     *
    184     * @return the underlying file.
    185     */
    186    public RandomAccessFile getRandomAccessFile() {
    187        return randomAccessFile;
    188    }
    189
    190    /**
    191     * Returns whether to close the underlying file when this stream is closed.
    192     *
    193     * @return Whether to close the underlying file when this stream is closed.
    194     */
    195    public boolean isCloseOnClose() {
    196        return closeOnClose;
    197    }
    198
    199    @Override
    200    public int read() throws IOException {
    201        return randomAccessFile.read();
    202    }
    203
    204    @Override
    205    public int read(final byte[] bytes) throws IOException {
    206        return randomAccessFile.read(bytes);
    207    }
    208
    209    @Override
    210    public int read(final byte[] bytes, final int offset, final int length) throws IOException {
    211        return randomAccessFile.read(bytes, offset, length);
    212    }
    213
    214    /**
    215     * Delegates to the underlying file.
    216     *
    217     * @param position See {@link RandomAccessFile#seek(long)}.
    218     * @throws IOException See {@link RandomAccessFile#seek(long)}.
    219     * @see RandomAccessFile#seek(long)
    220     */
    221    private void seek(final long position) throws IOException {
    222        randomAccessFile.seek(position);
    223    }
    224
    225    @Override
    226    public long skip(final long skipCount) throws IOException {
    227        if (skipCount <= 0) {
    228            return 0;
    229        }
    230        final long filePointer = randomAccessFile.getFilePointer();
    231        final long fileLength = randomAccessFile.length();
    232        if (filePointer >= fileLength) {
    233            return 0;
    234        }
    235        final long targetPos = filePointer + skipCount;
    236        final long newPos = targetPos > fileLength ? fileLength - 1 : targetPos;
    237        if (newPos > 0) {
    238            seek(newPos);
    239        }
    240        return randomAccessFile.getFilePointer() - filePointer;
    241    }
    242}