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