Home | History | Annotate | Download | only in shared
      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