Home | History | Annotate | Download | only in common
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.android.car.apps.common;
     17 
     18 import java.io.FilterInputStream;
     19 import java.io.IOException;
     20 import java.io.InputStream;
     21 import java.util.ArrayList;
     22 import java.util.List;
     23 
     24 /**
     25  * A replacement of BufferedInputStream (no multiple thread): <p>
     26  * - use list of byte array (chunks) instead of keep growing a single byte array (more efficent)
     27  *   <br>
     28  * - support overriding the markLimit passed in mark() call (The value that BitmapFactory
     29  *   uses 1024 is too small for detecting bitmap bounds and reset()) <br>
     30  * @hide
     31  */
     32 public class CachedInputStream extends FilterInputStream {
     33 
     34     private static final int CHUNK_SIZE = ByteArrayPool.CHUNK16K;
     35 
     36     private ArrayList<byte[]> mBufs = new ArrayList<byte[]>();
     37     private int mPos = 0;  // current read position inside the chunk buffers
     38     private int mCount = 0; // total validate bytes in chunk buffers
     39     private int mMarkPos = -1; // marked read position in chunk buffers
     40     private int mOverrideMarkLimit; // to override readlimit of mark() call
     41     private int mMarkLimit; // effective marklimit
     42     private byte[] tmp = new byte[1]; // tmp buffer used in read()
     43 
     44     public CachedInputStream(InputStream in) {
     45         super(in);
     46     }
     47 
     48     @Override
     49     public boolean markSupported() {
     50         return true;
     51     }
     52 
     53     /**
     54      * set the value that will override small readlimit passed in mark() call.
     55      */
     56     public void setOverrideMarkLimit(int overrideMarkLimit) {
     57         mOverrideMarkLimit = overrideMarkLimit;
     58     }
     59 
     60     public int getOverrideMarkLimit() {
     61         return mOverrideMarkLimit;
     62     }
     63 
     64     @Override
     65     public void mark(int readlimit) {
     66         readlimit = readlimit < mOverrideMarkLimit ? mOverrideMarkLimit : readlimit;
     67         if (mMarkPos >= 0) {
     68             // for replacing existing mark(), discard anything before mPos
     69             // and move mMarkPos to mPos
     70             int chunks = mPos / CHUNK_SIZE;
     71             if (chunks > 0) {
     72                 // trim the header buffers
     73                 int removedBytes = chunks * CHUNK_SIZE;
     74                 List<byte[]> subList = mBufs.subList(0, chunks);
     75                 releaseChunks(subList);
     76                 subList.clear();
     77                 mPos = mPos - removedBytes;
     78                 mCount = mCount - removedBytes;
     79             }
     80         }
     81         mMarkPos = mPos;
     82         mMarkLimit = readlimit;
     83     }
     84 
     85     @Override
     86     public void reset() throws IOException {
     87         if (mMarkPos < 0) {
     88             throw new IOException("mark has been invalidated");
     89         }
     90         mPos = mMarkPos;
     91     }
     92 
     93     @Override
     94     public int read() throws IOException {
     95         // TODO, not efficient, but the function is not called by BitmapFactory
     96         int r = read(tmp, 0, 1);
     97         if (r <= 0) {
     98             return -1;
     99         }
    100         return tmp[0] & 0xFF;
    101     }
    102 
    103     @Override
    104     public void close() throws IOException {
    105         if (in!=null) {
    106             in.close();
    107             in = null;
    108         }
    109         releaseChunks(mBufs);
    110     }
    111 
    112     private static void releaseChunks(List<byte[]> bufs) {
    113         ByteArrayPool.get16KBPool().releaseChunks(bufs);
    114     }
    115 
    116     private byte[] allocateChunk() {
    117         return ByteArrayPool.get16KBPool().allocateChunk();
    118     }
    119 
    120     private boolean invalidate() {
    121         if (mCount - mMarkPos > mMarkLimit) {
    122             mMarkPos = -1;
    123             mCount = 0;
    124             mPos = 0;
    125             releaseChunks(mBufs);
    126             mBufs.clear();
    127             return true;
    128         }
    129         return false;
    130     }
    131 
    132     @Override
    133     public int read(byte[] buffer, int offset, int count) throws IOException {
    134         if (in == null) {
    135             throw streamClosed();
    136         }
    137         if (mMarkPos == -1) {
    138             int reads = in.read(buffer, offset, count);
    139             return reads;
    140         }
    141         if (count == 0) {
    142             return 0;
    143         }
    144         int copied = copyMarkedBuffer(buffer, offset, count);
    145         count -= copied;
    146         offset += copied;
    147         int totalReads = copied;
    148         while (count > 0) {
    149             if (mPos == mBufs.size() * CHUNK_SIZE) {
    150                 mBufs.add(allocateChunk());
    151             }
    152             int currentBuf = mPos / CHUNK_SIZE;
    153             int indexInBuf = mPos - currentBuf * CHUNK_SIZE;
    154             byte[] buf = mBufs.get(currentBuf);
    155             int end = (currentBuf + 1) * CHUNK_SIZE;
    156             int leftInBuffer = end - mPos;
    157             int toRead = count > leftInBuffer ? leftInBuffer : count;
    158             int reads = in.read(buf, indexInBuf, toRead);
    159             if (reads > 0) {
    160                 System.arraycopy(buf, indexInBuf, buffer, offset, reads);
    161                 mPos += reads;
    162                 mCount += reads;
    163                 totalReads += reads;
    164                 offset += reads;
    165                 count -= reads;
    166                 if (invalidate()) {
    167                     reads = in.read(buffer, offset, count);
    168                     if (reads >0 ) {
    169                         totalReads += reads;
    170                     }
    171                     break;
    172                 }
    173             } else {
    174                 break;
    175             }
    176         }
    177         if (totalReads == 0) {
    178             return -1;
    179         }
    180         return totalReads;
    181     }
    182 
    183     private int copyMarkedBuffer(byte[] buffer, int offset, int read) {
    184         int totalRead = 0;
    185         while (read > 0 && mPos < mCount) {
    186             int currentBuf = mPos / CHUNK_SIZE;
    187             int indexInBuf = mPos - currentBuf * CHUNK_SIZE;
    188             byte[] buf = mBufs.get(currentBuf);
    189             int end = (currentBuf + 1) * CHUNK_SIZE;
    190             if (end > mCount) {
    191                 end = mCount;
    192             }
    193             int leftInBuffer = end - mPos;
    194             int toRead = read > leftInBuffer ? leftInBuffer : read;
    195             System.arraycopy(buf, indexInBuf, buffer, offset, toRead);
    196             offset += toRead;
    197             read -= toRead;
    198             totalRead += toRead;
    199             mPos += toRead;
    200         }
    201         return totalRead;
    202     }
    203 
    204     @Override
    205     public int available() throws IOException {
    206         if (in == null) {
    207             throw streamClosed();
    208         }
    209         return mCount - mPos + in.available();
    210     }
    211 
    212     @Override
    213     public long skip(long byteCount) throws IOException {
    214         if (in == null) {
    215             throw streamClosed();
    216         }
    217         if (mMarkPos <0) {
    218             return in.skip(byteCount);
    219         }
    220         long totalSkip = 0;
    221         totalSkip = mCount - mPos;
    222         if (totalSkip > byteCount) {
    223             totalSkip = byteCount;
    224         }
    225         mPos += totalSkip;
    226         byteCount -= totalSkip;
    227         while (byteCount > 0) {
    228             if (mPos == mBufs.size() * CHUNK_SIZE) {
    229                 mBufs.add(allocateChunk());
    230             }
    231             int currentBuf = mPos / CHUNK_SIZE;
    232             int indexInBuf = mPos - currentBuf * CHUNK_SIZE;
    233             byte[] buf = mBufs.get(currentBuf);
    234             int end = (currentBuf + 1) * CHUNK_SIZE;
    235             int leftInBuffer = end - mPos;
    236             int toRead = (int) (byteCount > leftInBuffer ? leftInBuffer : byteCount);
    237             int reads = in.read(buf, indexInBuf, toRead);
    238             if (reads > 0) {
    239                 mPos += reads;
    240                 mCount += reads;
    241                 byteCount -= reads;
    242                 totalSkip += reads;
    243                 if (invalidate()) {
    244                     if (byteCount > 0) {
    245                         totalSkip += in.skip(byteCount);
    246                     }
    247                     break;
    248                 }
    249             } else {
    250                 break;
    251             }
    252         }
    253         return totalSkip;
    254     }
    255 
    256     private static IOException streamClosed() {
    257         return new IOException("stream closed");
    258     }
    259 
    260 }
    261