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