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