16
16
17
17
import androidx .annotation .Nullable ;
18
18
import com .google .firebase .firestore .util .Logger ;
19
+ import java .io .ByteArrayOutputStream ;
19
20
import java .io .IOException ;
20
21
import java .io .InputStream ;
21
22
import java .io .InputStreamReader ;
22
- import java .nio .CharBuffer ;
23
+ import java .nio .ByteBuffer ;
23
24
import java .nio .charset .Charset ;
24
25
import org .json .JSONException ;
25
26
import org .json .JSONObject ;
@@ -34,18 +35,21 @@ public class BundleReader {
34
35
/** The capacity for the internal char buffer. */
35
36
protected static final int BUFFER_CAPACITY = 1024 ;
36
37
38
+ private static final Charset UTF8_CHARSET = Charset .forName ("UTF-8" );
39
+
37
40
private final BundleSerializer serializer ;
41
+ private final InputStream bundleInputStream ;
38
42
private final InputStreamReader dataReader ;
39
- private final Charset charset = Charset .forName ("UTF-8" );
40
43
41
44
@ Nullable BundleMetadata metadata ;
42
- private CharBuffer buffer ;
45
+ private ByteBuffer buffer ;
43
46
long bytesRead ;
44
47
45
- public BundleReader (BundleSerializer serializer , InputStream data ) {
48
+ public BundleReader (BundleSerializer serializer , InputStream bundleInputStream ) {
46
49
this .serializer = serializer ;
47
- dataReader = new InputStreamReader (data , charset );
48
- buffer = CharBuffer .allocate (BUFFER_CAPACITY );
50
+ this .bundleInputStream = bundleInputStream ;
51
+ this .dataReader = new InputStreamReader (this .bundleInputStream );
52
+ buffer = ByteBuffer .allocate (BUFFER_CAPACITY );
49
53
50
54
buffer .flip (); // Start the buffer in "reading mode"
51
55
}
@@ -83,7 +87,7 @@ public long getBytesRead() {
83
87
}
84
88
85
89
public void close () throws IOException {
86
- dataReader .close ();
90
+ bundleInputStream .close ();
87
91
}
88
92
89
93
/**
@@ -101,8 +105,9 @@ private BundleElement readNextElement() throws IOException, JSONException {
101
105
return null ;
102
106
}
103
107
104
- String json = readJsonString (Integer .parseInt (lengthPrefix ));
105
- bytesRead += lengthPrefix .length () + json .getBytes (charset ).length ;
108
+ int jsonStringByteCount = Integer .parseInt (lengthPrefix );
109
+ String json = readJsonString (jsonStringByteCount );
110
+ bytesRead += lengthPrefix .getBytes (UTF8_CHARSET ).length + jsonStringByteCount ;
106
111
return decodeBundleElement (json );
107
112
}
108
113
@@ -133,9 +138,9 @@ private BundleElement readNextElement() throws IOException, JSONException {
133
138
throw abort ("Reached the end of bundle when a length string is expected." );
134
139
}
135
140
136
- char [] c = new char [nextOpenBracket ];
137
- buffer .get (c );
138
- return new String ( c );
141
+ byte [] b = new byte [nextOpenBracket ];
142
+ buffer .get (b );
143
+ return UTF8_CHARSET . decode ( ByteBuffer . wrap ( b )). toString ( );
139
144
}
140
145
141
146
/** Returns the index of the first open bracket, or -1 if none is found. */
@@ -157,25 +162,30 @@ private int indexOfOpenBracket() {
157
162
* Reads from a specified position from the internal buffer, for a specified number of bytes,
158
163
* pulling more data from the underlying stream if needed.
159
164
*
160
- * <p>Returns a string decoded from the read bytes .
165
+ * <p>Returns an object containing the Json string and its UTF8 byte count .
161
166
*/
162
- private String readJsonString (int length ) throws IOException {
163
- StringBuilder json = new StringBuilder ( length );
167
+ private String readJsonString (int bytesToRead ) throws IOException {
168
+ ByteArrayOutputStream jsonBytes = new ByteArrayOutputStream ( );
164
169
165
- int remaining = length ;
170
+ // Read at least `bytesToRead` number of bytes from the bundle into `this.buffer`, pulling more
171
+ // data if necessary.
172
+ // Exactly `bytesToRead` number of bytes will be put in `jsonBytes` after the loop completes.
173
+ int remaining = bytesToRead ;
166
174
while (remaining > 0 ) {
167
175
if (buffer .remaining () == 0 && !pullMoreData ()) {
168
176
throw abort ("Reached the end of bundle when more data was expected." );
169
177
}
170
178
179
+ // `read` is the number of bytes guaranteed to exist in `this.buffer` after the above
180
+ // call to `pullMoreData`. Copy them to `jsonBytes` and advance `this.buffer`'s position.
171
181
int read = Math .min (remaining , buffer .remaining ());
172
- json . append (buffer , 0 , read );
182
+ jsonBytes . write (buffer . array (), buffer . arrayOffset () + buffer . position () , read );
173
183
buffer .position (buffer .position () + read );
174
184
175
185
remaining -= read ;
176
186
}
177
187
178
- return json .toString ();
188
+ return jsonBytes .toString (UTF8_CHARSET . name () );
179
189
}
180
190
181
191
/**
@@ -185,9 +195,17 @@ private String readJsonString(int length) throws IOException {
185
195
*/
186
196
private boolean pullMoreData () throws IOException {
187
197
buffer .compact ();
188
- int read = dataReader .read (buffer );
198
+
199
+ int bytesRead =
200
+ bundleInputStream .read (
201
+ buffer .array (), buffer .arrayOffset () + buffer .position (), buffer .remaining ());
202
+ boolean readSuccess = bytesRead > 0 ;
203
+ if (readSuccess ) {
204
+ buffer .position (buffer .position () + bytesRead );
205
+ }
206
+
189
207
buffer .flip ();
190
- return read > 0 ;
208
+ return readSuccess ;
191
209
}
192
210
193
211
/** Converts a JSON-encoded bundle element into its model class. */
0 commit comments