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