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}