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