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