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}