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 * https://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(this); 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 @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}