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 *      https://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(this);
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    @SuppressWarnings("resource") // caller closes.
136    private RandomAccessFileInputStream(final Builder builder) throws IOException {
137        this(builder.getRandomAccessFile(), builder.propagateClose);
138    }
139
140    /**
141     * Constructs a new instance configured to leave the underlying file open when this stream is closed.
142     *
143     * @param file The file to stream.
144     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
145     */
146    @Deprecated
147    public RandomAccessFileInputStream(final RandomAccessFile file) {
148        this(file, false);
149    }
150
151    /**
152     * Constructs a new instance.
153     *
154     * @param file         The file to stream.
155     * @param propagateClose Whether to close the underlying file when this stream is closed.
156     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
157     */
158    @Deprecated
159    public RandomAccessFileInputStream(final RandomAccessFile file, final boolean propagateClose) {
160        this.randomAccessFile = Objects.requireNonNull(file, "file");
161        this.propagateClose = propagateClose;
162    }
163
164    /**
165     * Gets an estimate of the number of bytes that can be read (or skipped over) from this input stream.
166     * <p>
167     * If there are more than {@link Integer#MAX_VALUE} bytes available, return {@link Integer#MAX_VALUE}.
168     * </p>
169     *
170     * @return An estimate of the number of bytes that can be read.
171     * @throws IOException If an I/O error occurs.
172     */
173    @Override
174    public int available() throws IOException {
175        return Math.toIntExact(Math.min(availableLong(), Integer.MAX_VALUE));
176    }
177
178    /**
179     * Gets the number of bytes that can be read (or skipped over) from this input stream.
180     *
181     * @return The number of bytes that can be read.
182     * @throws IOException If an I/O error occurs.
183     */
184    public long availableLong() throws IOException {
185        return isClosed() ? 0 : randomAccessFile.length() - randomAccessFile.getFilePointer();
186    }
187
188    @Override
189    public void close() throws IOException {
190        super.close();
191        if (propagateClose) {
192            randomAccessFile.close();
193        }
194    }
195
196    /**
197     * Copies our bytes from the given starting position for a size to the output stream.
198     *
199     * @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.
200     * @param size The number of bytes to copy.
201     * @param os   Where to copy.
202     * @return The number of bytes copied..
203     * @throws IOException if {@code pos} is less than {@code 0} or if an I/O error occurs.
204     * @since 2.19.0
205     */
206    public long copy(final long pos, final long size, final OutputStream os) throws IOException {
207        randomAccessFile.seek(pos);
208        return IOUtils.copyLarge(this, os, 0, size);
209    }
210
211    /**
212     * Gets the underlying file.
213     *
214     * @return the underlying file.
215     */
216    public RandomAccessFile getRandomAccessFile() {
217        return randomAccessFile;
218    }
219
220    /**
221     * Tests whether to close the underlying file when this stream is closed, defaults to false for compatibility.
222     *
223     * @return Whether to close the underlying file when this stream is closed.
224     */
225    public boolean isCloseOnClose() {
226        return propagateClose;
227    }
228
229    @Override
230    public int read() throws IOException {
231        return randomAccessFile.read();
232    }
233
234    @Override
235    public int read(final byte[] bytes) throws IOException {
236        return randomAccessFile.read(bytes);
237    }
238
239    @Override
240    public int read(final byte[] bytes, final int offset, final int length) throws IOException {
241        return randomAccessFile.read(bytes, offset, length);
242    }
243
244    @Override
245    public long skip(final long skipCount) throws IOException {
246        if (skipCount <= 0) {
247            return 0;
248        }
249        final long filePointer = randomAccessFile.getFilePointer();
250        final long fileLength = randomAccessFile.length();
251        if (filePointer >= fileLength) {
252            return 0;
253        }
254        final long targetPos = filePointer + skipCount;
255        final long newPos = targetPos > fileLength ? fileLength - 1 : targetPos;
256        if (newPos > 0) {
257            randomAccessFile.seek(newPos);
258        }
259        return randomAccessFile.getFilePointer() - filePointer;
260    }
261}