    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.OutputStream;
    023import java.io.RandomAccessFile;
    024import java.util.Objects;
    025
    026import org.apache.commons.io.IOUtils;
    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 AbstractInputStream {
    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 boolean propagateClose;
    062
    063        /**
    064         * Constructs a new builder of {@link RandomAccessFileInputStream}.
    065         */
    066        public Builder() {
    067            // empty
    068        }
    069
    070        /**
    071         * Builds a new {@link RandomAccessFileInputStream}.
    072         * <p>
    073         * You must set an aspect that supports {@link RandomAccessFile} or {@link File}, otherwise, this method throws an exception. Only set one of
    074         * RandomAccessFile or an origin that can be converted to a File.
    075         * </p>
    076         * <p>
    077         * This builder uses the following aspects:
    078         * </p>
    079         * <ul>
    080         * <li>{@link RandomAccessFile} gets the target aspect.</li>
    081         * <li>{@link File}</li>
    082         * <li>closeOnClose</li>
    083         * </ul>
    084         *
    085         * @return a new instance.
    086         * @throws IllegalStateException         if the {@code origin} is {@code null}.
    087         * @throws IllegalStateException         if both RandomAccessFile and origin are set.
    088         * @throws UnsupportedOperationException if the origin cannot be converted to a {@link RandomAccessFile}.
    089         * @throws IOException                   if an I/O error occurs converting to an {@link RandomAccessFile} using {@link #getRandomAccessFile()}.
    090         * @see AbstractOrigin#getFile()
    091         * @see #getUnchecked()
    092         */
    093        @Override
    094        public RandomAccessFileInputStream get() throws IOException {
    095            return new RandomAccessFileInputStream(getRandomAccessFile(), propagateClose);
    096        }
    097
    098        /**
    099         * Sets whether to close the underlying file when this stream is closed, defaults to false for compatibility.
    100         *
    101         * @param propagateClose Whether to close the underlying file when this stream is closed.
    102         * @return {@code this} instance.
    103         */
    104        public Builder setCloseOnClose(final boolean propagateClose) {
    105            this.propagateClose = propagateClose;
    106            return this;
    107        }
    108
    109        /**
    110         * Sets the RandomAccessFile to stream.
    111         *
    112         * @param randomAccessFile the RandomAccessFile to stream.
    113         * @return {@code this} instance.
    114         */
    115        @Override // MUST keep this method for binary compatibility since the super version of this method uses a generic which compiles to Object.
    116        public Builder setRandomAccessFile(final RandomAccessFile randomAccessFile) { // NOPMD see above.
    117            return super.setRandomAccessFile(randomAccessFile);
    118        }
    119
    120    }
    121
    122    /**
    123     * Constructs a new {@link Builder}.
    124     *
    125     * @return a new {@link Builder}.
    126     * @since 2.12.0
    127     */
    128    public static Builder builder() {
    129        return new Builder();
    130    }
    131
    132    private final boolean propagateClose;
    133    private final RandomAccessFile randomAccessFile;
    134
    135    /**
    136     * Constructs a new instance configured to leave the underlying file open when this stream is closed.
    137     *
    138     * @param file The file to stream.
    139     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
    140     */
    141    @Deprecated
    142    public RandomAccessFileInputStream(final RandomAccessFile file) {
    143        this(file, false);
    144    }
    145
    146    /**
    147     * Constructs a new instance.
    148     *
    149     * @param file         The file to stream.
    150     * @param propagateClose Whether to close the underlying file when this stream is closed.
    151     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
    152     */
    153    @Deprecated
    154    public RandomAccessFileInputStream(final RandomAccessFile file, final boolean propagateClose) {
    155        this.randomAccessFile = Objects.requireNonNull(file, "file");
    156        this.propagateClose = propagateClose;
    157    }
    158
    159    /**
    160     * Gets an estimate of the number of bytes that can be read (or skipped over) from this input stream.
    161     * <p>
    162     * If there are more than {@link Integer#MAX_VALUE} bytes available, return {@link Integer#MAX_VALUE}.
    163     * </p>
    164     *
    165     * @return An estimate of the number of bytes that can be read.
    166     * @throws IOException If an I/O error occurs.
    167     */
    168    @Override
    169    public int available() throws IOException {
    170        return Math.toIntExact(Math.min(availableLong(), Integer.MAX_VALUE));
    171    }
    172
    173    /**
    174     * Gets the number of bytes that can be read (or skipped over) from this input stream.
    175     *
    176     * @return The number of bytes that can be read.
    177     * @throws IOException If an I/O error occurs.
    178     */
    179    public long availableLong() throws IOException {
    180        return isClosed() ? 0 : randomAccessFile.length() - randomAccessFile.getFilePointer();
    181    }
    182
    183    @Override
    184    public void close() throws IOException {
    185        super.close();
    186        if (propagateClose) {
    187            randomAccessFile.close();
    188        }
    189    }
    190
    191    /**
    192     * Copies our bytes from the given starting position for a size to the output stream.
    193     *
    194     * @param pos  Where to start copying. The offset position, measured in bytes from the beginning of the file, at which to set the file pointer.
    195     * @param size The number of bytes to copy.
    196     * @param os   Where to copy.
    197     * @return The number of bytes copied..
    198     * @throws IOException if {@code pos} is less than {@code 0} or if an I/O error occurs.
    199     * @since 2.19.0
    200     */
    201    public long copy(final long pos, final long size, final OutputStream os) throws IOException {
    202        randomAccessFile.seek(pos);
    203        return IOUtils.copyLarge(this, os, 0, size);
    204    }
    205
    206    /**
    207     * Gets the underlying file.
    208     *
    209     * @return the underlying file.
    210     */
    211    public RandomAccessFile getRandomAccessFile() {
    212        return randomAccessFile;
    213    }
    214
    215    /**
    216     * Tests whether to close the underlying file when this stream is closed, defaults to false for compatibility.
    217     *
    218     * @return Whether to close the underlying file when this stream is closed.
    219     */
    220    public boolean isCloseOnClose() {
    221        return propagateClose;
    222    }
    223
    224    @Override
    225    public int read() throws IOException {
    226        return randomAccessFile.read();
    227    }
    228
    229    @Override
    230    public int read(final byte[] bytes) throws IOException {
    231        return randomAccessFile.read(bytes);
    232    }
    233
    234    @Override
    235    public int read(final byte[] bytes, final int offset, final int length) throws IOException {
    236        return randomAccessFile.read(bytes, offset, length);
    237    }
    238
    239    @Override
    240    public long skip(final long skipCount) throws IOException {
    241        if (skipCount <= 0) {
    242            return 0;
    243        }
    244        final long filePointer = randomAccessFile.getFilePointer();
    245        final long fileLength = randomAccessFile.length();
    246        if (filePointer >= fileLength) {
    247            return 0;
    248        }
    249        final long targetPos = filePointer + skipCount;
    250        final long newPos = targetPos > fileLength ? fileLength - 1 : targetPos;
    251        if (newPos > 0) {
    252            randomAccessFile.seek(newPos);
    253        }
    254        return randomAccessFile.getFilePointer() - filePointer;
    255    }
    256}