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}