1 // Copyright 2015 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.net.impl; 6 7 import androidx.annotation.NonNull; 8 9 import java.io.FileInputStream; 10 import java.io.IOException; 11 import java.io.InputStream; 12 import java.nio.ByteBuffer; 13 import java.nio.channels.ReadableByteChannel; 14 import java.util.concurrent.atomic.AtomicBoolean; 15 16 /** 17 * Adapts an {@link InputStream} into a {@link ReadableByteChannel}, exactly like 18 * {@link java.nio.channels.Channels#newChannel(InputStream)} does, but more efficiently, since it 19 * does not allocate a temporary buffer if it doesn't have to, and it freely takes advantage of 20 * {@link FileInputStream}'s trivial conversion to {@link java.nio.channels.FileChannel}. 21 */ 22 final class InputStreamChannel implements ReadableByteChannel { 23 private static final int MAX_TMP_BUFFER_SIZE = 16384; 24 private static final int MIN_TMP_BUFFER_SIZE = 4096; 25 private final InputStream mInputStream; 26 private final AtomicBoolean mIsOpen = new AtomicBoolean(true); 27 InputStreamChannel(@onNull InputStream inputStream)28 private InputStreamChannel(@NonNull InputStream inputStream) { 29 mInputStream = inputStream; 30 } 31 wrap(@onNull InputStream inputStream)32 static ReadableByteChannel wrap(@NonNull InputStream inputStream) { 33 if (inputStream instanceof FileInputStream) { 34 return ((FileInputStream) inputStream).getChannel(); 35 } 36 return new InputStreamChannel(inputStream); 37 } 38 39 @Override read(ByteBuffer dst)40 public int read(ByteBuffer dst) throws IOException { 41 final int read; 42 if (dst.hasArray()) { 43 read = 44 mInputStream.read( 45 dst.array(), dst.arrayOffset() + dst.position(), dst.remaining()); 46 if (read > 0) { 47 dst.position(dst.position() + read); 48 } 49 } else { 50 // Since we're allocating a buffer for every read, we want to choose a good size - on 51 // Android, the only case where a ByteBuffer won't have a backing byte[] is if it was 52 // created wrapping a void * in native code, or if it represents a memory-mapped file. 53 // Especially in the latter case, we want to avoid allocating a buffer that could be 54 // very large. 55 final int possibleToRead = 56 Math.min( 57 Math.max(mInputStream.available(), MIN_TMP_BUFFER_SIZE), 58 dst.remaining()); 59 final int reasonableToRead = Math.min(MAX_TMP_BUFFER_SIZE, possibleToRead); 60 byte[] tmpBuf = new byte[reasonableToRead]; 61 read = mInputStream.read(tmpBuf); 62 if (read > 0) { 63 dst.put(tmpBuf, 0, read); 64 } 65 } 66 return read; 67 } 68 69 @Override isOpen()70 public boolean isOpen() { 71 return mIsOpen.get(); 72 } 73 74 @Override close()75 public void close() throws IOException { 76 if (mIsOpen.compareAndSet(true, false)) { 77 mInputStream.close(); 78 } 79 } 80 } 81