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