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}