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.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(getRandomAccessFile(), propagateClose); 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 /** 136 * Constructs a new instance configured to leave the underlying file open when this stream is closed. 137 * 138 * @param file The file to stream. 139 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} 140 */ 141 @Deprecated 142 public RandomAccessFileInputStream(final RandomAccessFile file) { 143 this(file, false); 144 } 145 146 /** 147 * Constructs a new instance. 148 * 149 * @param file The file to stream. 150 * @param propagateClose Whether to close the underlying file when this stream is closed. 151 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} 152 */ 153 @Deprecated 154 public RandomAccessFileInputStream(final RandomAccessFile file, final boolean propagateClose) { 155 this.randomAccessFile = Objects.requireNonNull(file, "file"); 156 this.propagateClose = propagateClose; 157 } 158 159 /** 160 * Gets an estimate of the number of bytes that can be read (or skipped over) from this input stream. 161 * <p> 162 * If there are more than {@link Integer#MAX_VALUE} bytes available, return {@link Integer#MAX_VALUE}. 163 * </p> 164 * 165 * @return An estimate of the number of bytes that can be read. 166 * @throws IOException If an I/O error occurs. 167 */ 168 @Override 169 public int available() throws IOException { 170 return Math.toIntExact(Math.min(availableLong(), Integer.MAX_VALUE)); 171 } 172 173 /** 174 * Gets the number of bytes that can be read (or skipped over) from this input stream. 175 * 176 * @return The number of bytes that can be read. 177 * @throws IOException If an I/O error occurs. 178 */ 179 public long availableLong() throws IOException { 180 return isClosed() ? 0 : randomAccessFile.length() - randomAccessFile.getFilePointer(); 181 } 182 183 @Override 184 public void close() throws IOException { 185 super.close(); 186 if (propagateClose) { 187 randomAccessFile.close(); 188 } 189 } 190 191 /** 192 * Copies our bytes from the given starting position for a size to the output stream. 193 * 194 * @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. 195 * @param size The number of bytes to copy. 196 * @param os Where to copy. 197 * @return The number of bytes copied.. 198 * @throws IOException if {@code pos} is less than {@code 0} or if an I/O error occurs. 199 * @since 2.19.0 200 */ 201 public long copy(final long pos, final long size, final OutputStream os) throws IOException { 202 randomAccessFile.seek(pos); 203 return IOUtils.copyLarge(this, os, 0, size); 204 } 205 206 /** 207 * Gets the underlying file. 208 * 209 * @return the underlying file. 210 */ 211 public RandomAccessFile getRandomAccessFile() { 212 return randomAccessFile; 213 } 214 215 /** 216 * Tests whether to close the underlying file when this stream is closed, defaults to false for compatibility. 217 * 218 * @return Whether to close the underlying file when this stream is closed. 219 */ 220 public boolean isCloseOnClose() { 221 return propagateClose; 222 } 223 224 @Override 225 public int read() throws IOException { 226 return randomAccessFile.read(); 227 } 228 229 @Override 230 public int read(final byte[] bytes) throws IOException { 231 return randomAccessFile.read(bytes); 232 } 233 234 @Override 235 public int read(final byte[] bytes, final int offset, final int length) throws IOException { 236 return randomAccessFile.read(bytes, offset, length); 237 } 238 239 @Override 240 public long skip(final long skipCount) throws IOException { 241 if (skipCount <= 0) { 242 return 0; 243 } 244 final long filePointer = randomAccessFile.getFilePointer(); 245 final long fileLength = randomAccessFile.length(); 246 if (filePointer >= fileLength) { 247 return 0; 248 } 249 final long targetPos = filePointer + skipCount; 250 final long newPos = targetPos > fileLength ? fileLength - 1 : targetPos; 251 if (newPos > 0) { 252 randomAccessFile.seek(newPos); 253 } 254 return randomAccessFile.getFilePointer() - filePointer; 255 } 256}