1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * https://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package org.apache.commons.io.input; 19 20 import java.io.File; 21 import java.io.IOException; 22 import java.io.OutputStream; 23 import java.io.RandomAccessFile; 24 import java.util.Objects; 25 26 import org.apache.commons.io.IOUtils; 27 import org.apache.commons.io.build.AbstractOrigin; 28 import org.apache.commons.io.build.AbstractStreamBuilder; 29 30 /** 31 * Streams data from a {@link RandomAccessFile} starting at its current position. 32 * <p> 33 * To build an instance, use {@link Builder}. 34 * </p> 35 * 36 * @see Builder 37 * @since 2.8.0 38 */ 39 public class RandomAccessFileInputStream extends AbstractInputStream { 40 41 // @formatter:off 42 /** 43 * Builds a new {@link RandomAccessFileInputStream}. 44 * 45 * <p> 46 * For example: 47 * </p> 48 * <pre>{@code 49 * RandomAccessFileInputStream s = RandomAccessFileInputStream.builder() 50 * .setPath(path) 51 * .setCloseOnClose(true) 52 * .get();} 53 * </pre> 54 * 55 * @see #get() 56 * @since 2.12.0 57 */ 58 // @formatter:on 59 public static class Builder extends AbstractStreamBuilder<RandomAccessFileInputStream, Builder> { 60 61 private boolean propagateClose; 62 63 /** 64 * Constructs a new builder of {@link RandomAccessFileInputStream}. 65 */ 66 public Builder() { 67 // empty 68 } 69 70 /** 71 * Builds a new {@link RandomAccessFileInputStream}. 72 * <p> 73 * You must set an aspect that supports {@link RandomAccessFile} or {@link File}, otherwise, this method throws an exception. Only set one of 74 * RandomAccessFile or an origin that can be converted to a File. 75 * </p> 76 * <p> 77 * This builder uses the following aspects: 78 * </p> 79 * <ul> 80 * <li>{@link RandomAccessFile} gets the target aspect.</li> 81 * <li>{@link File}</li> 82 * <li>closeOnClose</li> 83 * </ul> 84 * 85 * @return a new instance. 86 * @throws IllegalStateException if the {@code origin} is {@code null}. 87 * @throws IllegalStateException if both RandomAccessFile and origin are set. 88 * @throws UnsupportedOperationException if the origin cannot be converted to a {@link RandomAccessFile}. 89 * @throws IOException if an I/O error occurs converting to an {@link RandomAccessFile} using {@link #getRandomAccessFile()}. 90 * @see AbstractOrigin#getFile() 91 * @see #getUnchecked() 92 */ 93 @Override 94 public RandomAccessFileInputStream get() throws IOException { 95 return new RandomAccessFileInputStream(this); 96 } 97 98 /** 99 * 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 }