1 // Copyright 2016 Google Inc. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package com.google.archivepatcher.shared; 16 17 import java.io.File; 18 import java.io.IOException; 19 import java.io.InputStream; 20 import java.io.RandomAccessFile; 21 22 /** 23 * An {@link InputStream} backed by a file that is assumed to be unchanging, such that it is 24 * suitable for random read access. This allows efficient and trivial implementation of the 25 * {@link #mark(int)} and {@link #reset()} functions using file operations. 26 * <p> 27 * This class deliberately breaks from the {@link InputStream} contract because it is intended for 28 * use cases where some of that behavior doesn't make sense. Specifically: 29 * <ul> 30 * <li>There is no read limit, and the value passed to {@link #mark(int)} is ignored.</li> 31 * <li>The {@link #reset()} method will only throw an exception if the stream is closed or if 32 * {@link #mark(int)} has never been called. It does <em>not</em> have the concept of an 33 * error from an invalidated read limit, because there is no read limit.</li> 34 * </ul> 35 */ 36 public class RandomAccessFileInputStream extends InputStream { 37 /** 38 * The backing {@link RandomAccessFile}. 39 */ 40 private final RandomAccessFile raf; 41 42 /** 43 * The current mark in the file, if set; otherwise -1. 44 */ 45 private long mark = -1; 46 47 /** 48 * The offset at which the reading range starts. 49 */ 50 private long rangeOffset; 51 52 /** 53 * The number of bytes in the reading range. 54 */ 55 private long rangeLength; 56 57 /** 58 * The length of the file at the moment it was opened. 59 */ 60 private final long fileLength; 61 62 /** 63 * Constructs a new stream for the given file, which will be opened in read-only mode for random 64 * access cross the entire file. Equivalent to calling 65 * {@link #RandomAccessFileInputStream(File, long, long)} with 0 and {@link File#length()} as the 66 * range parameters. 67 * @param file the file to read 68 * @throws IOException if unable to open the file for read 69 */ 70 public RandomAccessFileInputStream(File file) throws IOException { 71 this(file, 0, file.length()); 72 } 73 74 /** 75 * Constructs a new stream for the given file, which will be opened in read-only mode for random 76 * access within a specific range. 77 * @param file the file to read 78 * @param rangeOffset the offset at which the valid range starts 79 * @param rangeLength the number of bytes in the range 80 * @throws IOException if unable to open the file for read 81 */ 82 public RandomAccessFileInputStream(File file, long rangeOffset, long rangeLength) 83 throws IOException { 84 raf = getRandomAccessFile(file); 85 fileLength = file.length(); 86 setRange(rangeOffset, rangeLength); 87 } 88 89 /** 90 * Given a {@link File}, get a read-only {@link RandomAccessFile} reference for it. 91 * @param file the file 92 * @return as described 93 * @throws IOException if unable to open the file 94 */ 95 protected RandomAccessFile getRandomAccessFile(File file) throws IOException { 96 return new RandomAccessFile(file, "r"); 97 } 98 99 /** 100 * Sets the range to the specified values and seeks to the beginning of that range immediately. 101 * Any previously-existing mark is discarded. Also calls {@link #reset()}. 102 * @param rangeOffset the offset at which the valid range starts, must be a non-negative value 103 * @param rangeLength the number of bytes in the range, must be a non-negative value 104 * @throws IOException if anything goes wrong 105 */ 106 public void setRange(long rangeOffset, long rangeLength) throws IOException { 107 if (rangeOffset < 0) { 108 throw new IllegalArgumentException("rangeOffset must be >= 0"); 109 } 110 if (rangeLength < 0) { 111 throw new IllegalArgumentException("rangeLength must be >= 0"); 112 } 113 if (rangeOffset + rangeLength > fileLength) { 114 throw new IllegalArgumentException("Read range exceeds file length"); 115 } 116 if (rangeOffset + rangeLength < 0) { 117 throw new IllegalArgumentException("Insane input size not supported"); 118 } 119 this.rangeOffset = rangeOffset; 120 this.rangeLength = rangeLength; 121 mark = rangeOffset; 122 reset(); 123 mark = -1; 124 } 125 126 @Override 127 public int available() throws IOException { 128 long rangeRelativePosition = raf.getFilePointer() - rangeOffset; 129 long result = rangeLength - rangeRelativePosition; 130 if (result > Integer.MAX_VALUE) { 131 return Integer.MAX_VALUE; 132 } 133 return (int) result; 134 } 135 136 /** 137 * Returns the current position in the stream. 138 * @return as described 139 * @throws IOException if something goes wrong 140 */ 141 public long getPosition() throws IOException { 142 return raf.getFilePointer(); 143 } 144 145 @Override 146 public void close() throws IOException { 147 raf.close(); 148 } 149 150 @Override 151 public int read() throws IOException { 152 if (available() <= 0) { 153 return -1; 154 } 155 return raf.read(); 156 } 157 158 @Override 159 public int read(byte[] b, int off, int len) throws IOException { 160 if (len <= 0) { 161 return 0; 162 } 163 int available = available(); 164 if (available <= 0) { 165 return -1; 166 } 167 int result = raf.read(b, off, Math.min(len, available)); 168 return result; 169 } 170 171 @Override 172 public int read(byte[] b) throws IOException { 173 return read(b, 0, b.length); 174 } 175 176 @Override 177 public long skip(long n) throws IOException { 178 if (n <= 0) { 179 return 0; 180 } 181 int available = available(); 182 if (available <= 0) { 183 return 0; 184 } 185 int skipAmount = (int) Math.min(available, n); 186 raf.seek(raf.getFilePointer() + skipAmount); 187 return skipAmount; 188 } 189 190 @Override 191 public boolean markSupported() { 192 return true; 193 } 194 195 /** 196 * The readlimit argument is ignored for this implementation, as there is no concept of a buffer 197 * to be limited. 198 */ 199 @Override 200 public void mark(int readlimit) { 201 try { 202 mark = raf.getFilePointer(); 203 } catch (IOException e) { 204 throw new RuntimeException(e); 205 } 206 } 207 208 @Override 209 public void reset() throws IOException { 210 if (mark < 0) { 211 throw new IOException("mark not set"); 212 } 213 raf.seek(mark); 214 } 215 216 /** 217 * Returns the total length of the underlying file at the moment it was opened. 218 * @return as described 219 */ 220 public long length() { 221 return fileLength; 222 } 223 } 224