• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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