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.InputStream;
023import java.io.RandomAccessFile;
024import java.util.Objects;
025
026import org.apache.commons.io.RandomAccessFileMode;
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 InputStream {
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 RandomAccessFile randomAccessFile;
062        private boolean closeOnClose;
063
064        /**
065         * Builds a new {@link RandomAccessFileInputStream}.
066         * <p>
067         * You must set input that supports {@link RandomAccessFile} or {@link File}, otherwise, this method throws an exception. Only set one of
068         * RandomAccessFile or an origin that can be converted to a File.
069         * </p>
070         * <p>
071         * This builder use the following aspects:
072         * </p>
073         * <ul>
074         * <li>{@link RandomAccessFile}</li>
075         * <li>{@link File}</li>
076         * <li>closeOnClose</li>
077         * </ul>
078         *
079         * @return a new instance.
080         * @throws IllegalStateException         if the {@code origin} is {@code null}.
081         * @throws IllegalStateException         if both RandomAccessFile and origin are set.
082         * @throws UnsupportedOperationException if the origin cannot be converted to a {@link File}.
083         * @see AbstractOrigin#getFile()
084         */
085        @SuppressWarnings("resource") // Caller closes depending on settings
086        @Override
087        public RandomAccessFileInputStream get() throws IOException {
088            if (randomAccessFile != null) {
089                if (getOrigin() != null) {
090                    throw new IllegalStateException(String.format("Only set one of RandomAccessFile (%s) or origin (%s)", randomAccessFile, getOrigin()));
091                }
092                return new RandomAccessFileInputStream(randomAccessFile, closeOnClose);
093            }
094            return new RandomAccessFileInputStream(RandomAccessFileMode.READ_ONLY.create(checkOrigin().getFile()), closeOnClose);
095        }
096
097        /**
098         * Sets whether to close the underlying file when this stream is closed.
099         *
100         * @param closeOnClose Whether to close the underlying file when this stream is closed.
101         * @return this
102         */
103        public Builder setCloseOnClose(final boolean closeOnClose) {
104            this.closeOnClose = closeOnClose;
105            return this;
106        }
107
108        /**
109         * Sets the RandomAccessFile to stream.
110         *
111         * @param randomAccessFile the RandomAccessFile to stream.
112         * @return this
113         */
114        public Builder setRandomAccessFile(final RandomAccessFile randomAccessFile) {
115            this.randomAccessFile = randomAccessFile;
116            return this;
117        }
118
119    }
120
121    /**
122     * Constructs a new {@link Builder}.
123     *
124     * @return a new {@link Builder}.
125     * @since 2.12.0
126     */
127    public static Builder builder() {
128        return new Builder();
129    }
130
131    private final boolean closeOnClose;
132    private final RandomAccessFile randomAccessFile;
133
134    /**
135     * Constructs a new instance configured to leave the underlying file open when this stream is closed.
136     *
137     * @param file The file to stream.
138     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
139     */
140    @Deprecated
141    public RandomAccessFileInputStream(final RandomAccessFile file) {
142        this(file, false);
143    }
144
145    /**
146     * Constructs a new instance.
147     *
148     * @param file         The file to stream.
149     * @param closeOnClose Whether to close the underlying file when this stream is closed.
150     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
151     */
152    @Deprecated
153    public RandomAccessFileInputStream(final RandomAccessFile file, final boolean closeOnClose) {
154        this.randomAccessFile = Objects.requireNonNull(file, "file");
155        this.closeOnClose = closeOnClose;
156    }
157
158    /**
159     * Returns an estimate of the number of bytes that can be read (or skipped over) from this input stream.
160     *
161     * If there are more than {@link Integer#MAX_VALUE} bytes available, return {@link Integer#MAX_VALUE}.
162     *
163     * @return An estimate of the number of bytes that can be read.
164     * @throws IOException If an I/O error occurs.
165     */
166    @Override
167    public int available() throws IOException {
168        final long avail = availableLong();
169        if (avail > Integer.MAX_VALUE) {
170            return Integer.MAX_VALUE;
171        }
172        return (int) avail;
173    }
174
175    /**
176     * Returns the number of bytes that can be read (or skipped over) from this input stream.
177     *
178     * @return The number of bytes that can be read.
179     * @throws IOException If an I/O error occurs.
180     */
181    public long availableLong() throws IOException {
182        return randomAccessFile.length() - randomAccessFile.getFilePointer();
183    }
184
185    @Override
186    public void close() throws IOException {
187        super.close();
188        if (closeOnClose) {
189            randomAccessFile.close();
190        }
191    }
192
193    /**
194     * Gets the underlying file.
195     *
196     * @return the underlying file.
197     */
198    public RandomAccessFile getRandomAccessFile() {
199        return randomAccessFile;
200    }
201
202    /**
203     * Returns whether to close the underlying file when this stream is closed.
204     *
205     * @return Whether to close the underlying file when this stream is closed.
206     */
207    public boolean isCloseOnClose() {
208        return closeOnClose;
209    }
210
211    @Override
212    public int read() throws IOException {
213        return randomAccessFile.read();
214    }
215
216    @Override
217    public int read(final byte[] bytes) throws IOException {
218        return randomAccessFile.read(bytes);
219    }
220
221    @Override
222    public int read(final byte[] bytes, final int offset, final int length) throws IOException {
223        return randomAccessFile.read(bytes, offset, length);
224    }
225
226    @Override
227    public long skip(final long skipCount) throws IOException {
228        if (skipCount <= 0) {
229            return 0;
230        }
231        final long filePointer = randomAccessFile.getFilePointer();
232        final long fileLength = randomAccessFile.length();
233        if (filePointer >= fileLength) {
234            return 0;
235        }
236        final long targetPos = filePointer + skipCount;
237        final long newPos = targetPos > fileLength ? fileLength - 1 : targetPos;
238        if (newPos > 0) {
239            randomAccessFile.seek(newPos);
240        }
241        return randomAccessFile.getFilePointer() - filePointer;
242    }
243}