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.InputStream; 023import java.io.RandomAccessFile; 024import java.util.Objects; 025 026import org.apache.commons.io.RandomAccessFileMode; 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 InputStream { 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 RandomAccessFile randomAccessFile; 062 private boolean closeOnClose; 063 064 /** 065 * Builds a new {@link RandomAccessFileInputStream}. 066 * <p> 067 * You must set input that supports {@link RandomAccessFile} or {@link File}, otherwise, this method throws an exception. Only set one of 068 * RandomAccessFile or an origin that can be converted to a File. 069 * </p> 070 * <p> 071 * This builder use the following aspects: 072 * </p> 073 * <ul> 074 * <li>{@link RandomAccessFile}</li> 075 * <li>{@link File}</li> 076 * <li>closeOnClose</li> 077 * </ul> 078 * 079 * @return a new instance. 080 * @throws IllegalStateException if the {@code origin} is {@code null}. 081 * @throws IllegalStateException if both RandomAccessFile and origin are set. 082 * @throws UnsupportedOperationException if the origin cannot be converted to a {@link File}. 083 * @see AbstractOrigin#getFile() 084 */ 085 @SuppressWarnings("resource") // Caller closes depending on settings 086 @Override 087 public RandomAccessFileInputStream get() throws IOException { 088 if (randomAccessFile != null) { 089 if (getOrigin() != null) { 090 throw new IllegalStateException(String.format("Only set one of RandomAccessFile (%s) or origin (%s)", randomAccessFile, getOrigin())); 091 } 092 return new RandomAccessFileInputStream(randomAccessFile, closeOnClose); 093 } 094 return new RandomAccessFileInputStream(RandomAccessFileMode.READ_ONLY.create(checkOrigin().getFile()), closeOnClose); 095 } 096 097 /** 098 * Sets whether to close the underlying file when this stream is closed. 099 * 100 * @param closeOnClose Whether to close the underlying file when this stream is closed. 101 * @return this 102 */ 103 public Builder setCloseOnClose(final boolean closeOnClose) { 104 this.closeOnClose = closeOnClose; 105 return this; 106 } 107 108 /** 109 * Sets the RandomAccessFile to stream. 110 * 111 * @param randomAccessFile the RandomAccessFile to stream. 112 * @return this 113 */ 114 public Builder setRandomAccessFile(final RandomAccessFile randomAccessFile) { 115 this.randomAccessFile = randomAccessFile; 116 return this; 117 } 118 119 } 120 121 /** 122 * Constructs a new {@link Builder}. 123 * 124 * @return a new {@link Builder}. 125 * @since 2.12.0 126 */ 127 public static Builder builder() { 128 return new Builder(); 129 } 130 131 private final boolean closeOnClose; 132 private final RandomAccessFile randomAccessFile; 133 134 /** 135 * Constructs a new instance configured to leave the underlying file open when this stream is closed. 136 * 137 * @param file The file to stream. 138 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} 139 */ 140 @Deprecated 141 public RandomAccessFileInputStream(final RandomAccessFile file) { 142 this(file, false); 143 } 144 145 /** 146 * Constructs a new instance. 147 * 148 * @param file The file to stream. 149 * @param closeOnClose Whether to close the underlying file when this stream is closed. 150 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} 151 */ 152 @Deprecated 153 public RandomAccessFileInputStream(final RandomAccessFile file, final boolean closeOnClose) { 154 this.randomAccessFile = Objects.requireNonNull(file, "file"); 155 this.closeOnClose = closeOnClose; 156 } 157 158 /** 159 * Returns an estimate of the number of bytes that can be read (or skipped over) from this input stream. 160 * 161 * If there are more than {@link Integer#MAX_VALUE} bytes available, return {@link Integer#MAX_VALUE}. 162 * 163 * @return An estimate of the number of bytes that can be read. 164 * @throws IOException If an I/O error occurs. 165 */ 166 @Override 167 public int available() throws IOException { 168 final long avail = availableLong(); 169 if (avail > Integer.MAX_VALUE) { 170 return Integer.MAX_VALUE; 171 } 172 return (int) avail; 173 } 174 175 /** 176 * Returns the number of bytes that can be read (or skipped over) from this input stream. 177 * 178 * @return The number of bytes that can be read. 179 * @throws IOException If an I/O error occurs. 180 */ 181 public long availableLong() throws IOException { 182 return randomAccessFile.length() - randomAccessFile.getFilePointer(); 183 } 184 185 @Override 186 public void close() throws IOException { 187 super.close(); 188 if (closeOnClose) { 189 randomAccessFile.close(); 190 } 191 } 192 193 /** 194 * Gets the underlying file. 195 * 196 * @return the underlying file. 197 */ 198 public RandomAccessFile getRandomAccessFile() { 199 return randomAccessFile; 200 } 201 202 /** 203 * Returns whether to close the underlying file when this stream is closed. 204 * 205 * @return Whether to close the underlying file when this stream is closed. 206 */ 207 public boolean isCloseOnClose() { 208 return closeOnClose; 209 } 210 211 @Override 212 public int read() throws IOException { 213 return randomAccessFile.read(); 214 } 215 216 @Override 217 public int read(final byte[] bytes) throws IOException { 218 return randomAccessFile.read(bytes); 219 } 220 221 @Override 222 public int read(final byte[] bytes, final int offset, final int length) throws IOException { 223 return randomAccessFile.read(bytes, offset, length); 224 } 225 226 @Override 227 public long skip(final long skipCount) throws IOException { 228 if (skipCount <= 0) { 229 return 0; 230 } 231 final long filePointer = randomAccessFile.getFilePointer(); 232 final long fileLength = randomAccessFile.length(); 233 if (filePointer >= fileLength) { 234 return 0; 235 } 236 final long targetPos = filePointer + skipCount; 237 final long newPos = targetPos > fileLength ? fileLength - 1 : targetPos; 238 if (newPos > 0) { 239 randomAccessFile.seek(newPos); 240 } 241 return randomAccessFile.getFilePointer() - filePointer; 242 } 243}