• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 
17 package com.android.modules.utils;
18 
19 import android.annotation.NonNull;
20 
21 import java.io.BufferedInputStream;
22 import java.io.Closeable;
23 import java.io.DataInput;
24 import java.io.DataInputStream;
25 import java.io.EOFException;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.util.Arrays;
29 import java.util.Objects;
30 
31 /**
32  * Optimized implementation of {@link DataInput} which buffers data in memory
33  * from the underlying {@link InputStream}.
34  * <p>
35  * Benchmarks have demonstrated this class is 3x more efficient than using a
36  * {@link DataInputStream} with a {@link BufferedInputStream}.
37  */
38 public class FastDataInput implements DataInput, Closeable {
39     protected static final int MAX_UNSIGNED_SHORT = 65_535;
40 
41     protected static final int DEFAULT_BUFFER_SIZE = 32_768;
42 
43     protected final byte[] mBuffer;
44     protected final int mBufferCap;
45 
46     private InputStream mIn;
47     protected int mBufferPos;
48     protected int mBufferLim;
49 
50     /**
51      * Values that have been "interned" by {@link #readInternedUTF()}.
52      */
53     private int mStringRefCount = 0;
54     private String[] mStringRefs = new String[32];
55 
FastDataInput(@onNull InputStream in, int bufferSize)56     public FastDataInput(@NonNull InputStream in, int bufferSize) {
57         mIn = Objects.requireNonNull(in);
58         if (bufferSize < 8) {
59             throw new IllegalArgumentException();
60         }
61 
62         mBuffer = newByteArray(bufferSize);
63         mBufferCap = mBuffer.length;
64     }
65 
66     /**
67      * Obtain a {@link FastDataInput} configured with the given
68      * {@link InputStream} and which encodes large code-points using 3-byte
69      * sequences.
70      * <p>
71      * This <em>is</em> compatible with the {@link DataInput} API contract,
72      * which specifies that large code-points must be encoded with 3-byte
73      * sequences.
74      */
obtain(@onNull InputStream in)75     public static FastDataInput obtain(@NonNull InputStream in) {
76         return new FastDataInput(in, DEFAULT_BUFFER_SIZE);
77     }
78 
79     /**
80      * Release a {@link FastDataInput} to potentially be recycled. You must not
81      * interact with the object after releasing it.
82      */
release()83     public void release() {
84         mIn = null;
85         mBufferPos = 0;
86         mBufferLim = 0;
87         mStringRefCount = 0;
88     }
89 
newByteArray(int bufferSize)90     public byte[] newByteArray(int bufferSize) {
91         return new byte[bufferSize];
92     }
93 
94     /**
95      * Re-initializes the object for the new input.
96      */
setInput(@onNull InputStream in)97     protected void setInput(@NonNull InputStream in) {
98         if (mIn != null) {
99             throw new IllegalStateException("setInput() called before calling release()");
100         }
101 
102         mIn = Objects.requireNonNull(in);
103         mBufferPos = 0;
104         mBufferLim = 0;
105         mStringRefCount = 0;
106     }
107 
fill(int need)108     protected void fill(int need) throws IOException {
109         final int remain = mBufferLim - mBufferPos;
110         System.arraycopy(mBuffer, mBufferPos, mBuffer, 0, remain);
111         mBufferPos = 0;
112         mBufferLim = remain;
113         need -= remain;
114 
115         while (need > 0) {
116             int c = mIn.read(mBuffer, mBufferLim, mBufferCap - mBufferLim);
117             if (c == -1) {
118                 throw new EOFException();
119             } else {
120                 mBufferLim += c;
121                 need -= c;
122             }
123         }
124     }
125 
126     @Override
close()127     public void close() throws IOException {
128         mIn.close();
129         release();
130     }
131 
132     @Override
readFully(byte[] b)133     public void readFully(byte[] b) throws IOException {
134         readFully(b, 0, b.length);
135     }
136 
137     @Override
readFully(byte[] b, int off, int len)138     public void readFully(byte[] b, int off, int len) throws IOException {
139         // Attempt to read directly from buffer space if there's enough room,
140         // otherwise fall back to chunking into place
141         if (mBufferCap >= len) {
142             if (mBufferLim - mBufferPos < len) fill(len);
143             System.arraycopy(mBuffer, mBufferPos, b, off, len);
144             mBufferPos += len;
145         } else {
146             final int remain = mBufferLim - mBufferPos;
147             System.arraycopy(mBuffer, mBufferPos, b, off, remain);
148             mBufferPos += remain;
149             off += remain;
150             len -= remain;
151 
152             while (len > 0) {
153                 int c = mIn.read(b, off, len);
154                 if (c == -1) {
155                     throw new EOFException();
156                 } else {
157                     off += c;
158                     len -= c;
159                 }
160             }
161         }
162     }
163 
164     @Override
readUTF()165     public String readUTF() throws IOException {
166         // Attempt to read directly from buffer space if there's enough room,
167         // otherwise fall back to chunking into place
168         final int len = readUnsignedShort();
169         if (mBufferCap > len) {
170             if (mBufferLim - mBufferPos < len) fill(len);
171             final String res = ModifiedUtf8.decode(mBuffer, new char[len], mBufferPos, len);
172             mBufferPos += len;
173             return res;
174         } else {
175             final byte[] tmp = newByteArray(len + 1);
176             readFully(tmp, 0, len);
177             return ModifiedUtf8.decode(tmp, new char[len], 0, len);
178         }
179     }
180 
181     /**
182      * Read a {@link String} value with the additional signal that the given
183      * value is a candidate for being canonicalized, similar to
184      * {@link String#intern()}.
185      * <p>
186      * Canonicalization is implemented by writing each unique string value once
187      * the first time it appears, and then writing a lightweight {@code short}
188      * reference when that string is written again in the future.
189      *
190      * @see FastDataOutput#writeInternedUTF(String)
191      */
readInternedUTF()192     public @NonNull String readInternedUTF() throws IOException {
193         final int ref = readUnsignedShort();
194         if (ref == MAX_UNSIGNED_SHORT) {
195             final String s = readUTF();
196 
197             // We can only safely intern when we have remaining values; if we're
198             // full we at least sent the string value above
199             if (mStringRefCount < MAX_UNSIGNED_SHORT) {
200                 if (mStringRefCount == mStringRefs.length) {
201                     mStringRefs = Arrays.copyOf(mStringRefs,
202                             mStringRefCount + (mStringRefCount >> 1));
203                 }
204                 mStringRefs[mStringRefCount++] = s;
205             }
206 
207             return s;
208         } else {
209             if (ref >= mStringRefs.length) {
210                 throw new IOException("Invalid interned string reference " + ref + " for "
211                         + mStringRefs.length + " interned strings");
212             }
213             return mStringRefs[ref];
214         }
215     }
216 
217     @Override
readBoolean()218     public boolean readBoolean() throws IOException {
219         return readByte() != 0;
220     }
221 
222     /**
223      * Returns the same decoded value as {@link #readByte()} but without
224      * actually consuming the underlying data.
225      */
peekByte()226     public byte peekByte() throws IOException {
227         if (mBufferLim - mBufferPos < 1) fill(1);
228         return mBuffer[mBufferPos];
229     }
230 
231     @Override
readByte()232     public byte readByte() throws IOException {
233         if (mBufferLim - mBufferPos < 1) fill(1);
234         return mBuffer[mBufferPos++];
235     }
236 
237     @Override
readUnsignedByte()238     public int readUnsignedByte() throws IOException {
239         return Byte.toUnsignedInt(readByte());
240     }
241 
242     @Override
readShort()243     public short readShort() throws IOException {
244         if (mBufferLim - mBufferPos < 2) fill(2);
245         return (short) (((mBuffer[mBufferPos++] & 0xff) <<  8) |
246                         ((mBuffer[mBufferPos++] & 0xff) <<  0));
247     }
248 
249     @Override
readUnsignedShort()250     public int readUnsignedShort() throws IOException {
251         return Short.toUnsignedInt((short) readShort());
252     }
253 
254     @Override
readChar()255     public char readChar() throws IOException {
256         return (char) readShort();
257     }
258 
259     @Override
readInt()260     public int readInt() throws IOException {
261         if (mBufferLim - mBufferPos < 4) fill(4);
262         return (((mBuffer[mBufferPos++] & 0xff) << 24) |
263                 ((mBuffer[mBufferPos++] & 0xff) << 16) |
264                 ((mBuffer[mBufferPos++] & 0xff) <<  8) |
265                 ((mBuffer[mBufferPos++] & 0xff) <<  0));
266     }
267 
268     @Override
readLong()269     public long readLong() throws IOException {
270         if (mBufferLim - mBufferPos < 8) fill(8);
271         int h = ((mBuffer[mBufferPos++] & 0xff) << 24) |
272                 ((mBuffer[mBufferPos++] & 0xff) << 16) |
273                 ((mBuffer[mBufferPos++] & 0xff) <<  8) |
274                 ((mBuffer[mBufferPos++] & 0xff) <<  0);
275         int l = ((mBuffer[mBufferPos++] & 0xff) << 24) |
276                 ((mBuffer[mBufferPos++] & 0xff) << 16) |
277                 ((mBuffer[mBufferPos++] & 0xff) <<  8) |
278                 ((mBuffer[mBufferPos++] & 0xff) <<  0);
279         return (((long) h) << 32L) | ((long) l) & 0xffffffffL;
280     }
281 
282     @Override
readFloat()283     public float readFloat() throws IOException {
284         return Float.intBitsToFloat(readInt());
285     }
286 
287     @Override
readDouble()288     public double readDouble() throws IOException {
289         return Double.longBitsToDouble(readLong());
290     }
291 
292     @Override
skipBytes(int n)293     public int skipBytes(int n) throws IOException {
294         // Callers should read data piecemeal
295         throw new UnsupportedOperationException();
296     }
297 
298     @Override
readLine()299     public String readLine() throws IOException {
300         // Callers should read data piecemeal
301         throw new UnsupportedOperationException();
302     }
303 }
304