• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2022 Google LLC
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.google.android.libraries.mobiledatadownload.file.openers;
17 
18 import com.google.android.libraries.mobiledatadownload.file.OpenContext;
19 import com.google.android.libraries.mobiledatadownload.file.Opener;
20 import com.google.android.libraries.mobiledatadownload.file.common.Sizable;
21 import com.google.android.libraries.mobiledatadownload.file.common.UnsupportedFileStorageOperation;
22 import com.google.common.io.ByteStreams;
23 import com.google.common.primitives.Ints;
24 import java.io.IOException;
25 import java.io.InputStream;
26 
27 /**
28  * An opener that returns a byte[] for a Uri. Attempts to get the file size so it can perform a
29  * single allocation, but falls back to a dynamic allocation when the size is unknown.
30  *
31  * <p>Warning: Large memory allocations can OOM. We recommend evaluating reliability and performance
32  * on target platforms when allocating > 1M.
33  *
34  * <p>Usage: <code>
35  * byte[] bytes = storage.open(uri, ReadByteArrayOpener.create());
36  * </code>
37  */
38 public final class ReadByteArrayOpener implements Opener<byte[]> {
39 
ReadByteArrayOpener()40   private ReadByteArrayOpener() {}
41 
42   /** Creates a new opener instance to read a byte array. */
create()43   public static ReadByteArrayOpener create() {
44     return new ReadByteArrayOpener();
45   }
46 
47   @Override
open(OpenContext openContext)48   public byte[] open(OpenContext openContext) throws IOException {
49     try (InputStream in = ReadStreamOpener.create().open(openContext)) {
50       Long size = null;
51       // Try to get the length from the Sizable interface. This can potentially work with
52       // monitors and transforms that do not change the file size, or do so in a way that
53       // supports efficient calculation of the logical size (eg file size - header size = logical
54       // size).
55       if (in instanceof Sizable) {
56         size = ((Sizable) in).size();
57       }
58 
59       // If Sizable failed and there are not transforms that could manipulate the file size,
60       // then try calling fileSize().
61       if (size == null && !openContext.hasTransforms()) {
62         try {
63           long fileSize = openContext.storage().fileSize(openContext.originalUri());
64           if (fileSize > 0) {
65             // Treat 0 as "unknown file size".
66             size = fileSize;
67           }
68         } catch (UnsupportedFileStorageOperation ex) {
69           // Ignore.
70         }
71       }
72 
73       if (size == null) {
74         // Bummer. Read stream of unknown length. Inefficient but always works.
75         return ByteStreams.toByteArray(in);
76       }
77 
78       byte[] bytes = new byte[Ints.checkedCast(size)];
79       ByteStreams.readFully(in, bytes);
80       return bytes;
81     }
82   }
83 }
84