• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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.internal.util;
18 
19 import java.io.Closeable;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.net.ProtocolException;
23 import java.nio.charset.StandardCharsets;
24 
25 /**
26  * Reader that specializes in parsing {@code /proc/} files quickly. Walks
27  * through the stream using a single space {@code ' '} as token separator, and
28  * requires each line boundary to be explicitly acknowledged using
29  * {@link #finishLine()}. Assumes {@link StandardCharsets#US_ASCII} encoding.
30  * <p>
31  * Currently doesn't support formats based on {@code \0}, tabs.
32  * Consecutive spaces are treated as a single delimiter.
33  */
34 public class ProcFileReader implements Closeable {
35     private final InputStream mStream;
36     private final byte[] mBuffer;
37 
38     /** Write pointer in {@link #mBuffer}. */
39     private int mTail;
40     /** Flag when last read token finished current line. */
41     private boolean mLineFinished;
42 
ProcFileReader(InputStream stream)43     public ProcFileReader(InputStream stream) throws IOException {
44         this(stream, 4096);
45     }
46 
ProcFileReader(InputStream stream, int bufferSize)47     public ProcFileReader(InputStream stream, int bufferSize) throws IOException {
48         mStream = stream;
49         mBuffer = new byte[bufferSize];
50 
51         // read enough to answer hasMoreData
52         fillBuf();
53     }
54 
55     /**
56      * Read more data from {@link #mStream} into internal buffer.
57      */
fillBuf()58     private int fillBuf() throws IOException {
59         final int length = mBuffer.length - mTail;
60         if (length == 0) {
61             throw new IOException("attempting to fill already-full buffer");
62         }
63 
64         final int read = mStream.read(mBuffer, mTail, length);
65         if (read != -1) {
66             mTail += read;
67         }
68         return read;
69     }
70 
71     /**
72      * Consume number of bytes from beginning of internal buffer. If consuming
73      * all remaining bytes, will attempt to {@link #fillBuf()}.
74      */
consumeBuf(int count)75     private void consumeBuf(int count) throws IOException {
76         // TODO: consider moving to read pointer, but for now traceview says
77         // these copies aren't a bottleneck.
78 
79         // skip all consecutive delimiters.
80         while (count < mTail && mBuffer[count] == ' ') {
81             count++;
82         }
83         System.arraycopy(mBuffer, count, mBuffer, 0, mTail - count);
84         mTail -= count;
85         if (mTail == 0) {
86             fillBuf();
87         }
88     }
89 
90     /**
91      * Find buffer index of next token delimiter, usually space or newline.
92      * Fills buffer as needed.
93      *
94      * @return Index of next delimeter, otherwise -1 if no tokens remain on
95      *         current line.
96      */
nextTokenIndex()97     private int nextTokenIndex() throws IOException {
98         if (mLineFinished) {
99             return -1;
100         }
101 
102         int i = 0;
103         do {
104             // scan forward for token boundary
105             for (; i < mTail; i++) {
106                 final byte b = mBuffer[i];
107                 if (b == '\n') {
108                     mLineFinished = true;
109                     return i;
110                 }
111                 if (b == ' ') {
112                     return i;
113                 }
114             }
115         } while (fillBuf() > 0);
116 
117         throw new ProtocolException("End of stream while looking for token boundary");
118     }
119 
120     /**
121      * Check if stream has more data to be parsed.
122      */
hasMoreData()123     public boolean hasMoreData() {
124         return mTail > 0;
125     }
126 
127     /**
128      * Finish current line, skipping any remaining data.
129      */
finishLine()130     public void finishLine() throws IOException {
131         // last token already finished line; reset silently
132         if (mLineFinished) {
133             mLineFinished = false;
134             return;
135         }
136 
137         int i = 0;
138         do {
139             // scan forward for line boundary and consume
140             for (; i < mTail; i++) {
141                 if (mBuffer[i] == '\n') {
142                     consumeBuf(i + 1);
143                     return;
144                 }
145             }
146         } while (fillBuf() > 0);
147 
148         throw new ProtocolException("End of stream while looking for line boundary");
149     }
150 
151     /**
152      * Parse and return next token as {@link String}.
153      */
nextString()154     public String nextString() throws IOException {
155         final int tokenIndex = nextTokenIndex();
156         if (tokenIndex == -1) {
157             throw new ProtocolException("Missing required string");
158         } else {
159             return parseAndConsumeString(tokenIndex);
160         }
161     }
162 
163     /**
164      * Parse and return next token as base-10 encoded {@code long}.
165      */
nextLong()166     public long nextLong() throws IOException {
167         return nextLong(false);
168     }
169 
170     /**
171      * Parse and return next token as base-10 encoded {@code long}.
172      */
nextLong(boolean stopAtInvalid)173     public long nextLong(boolean stopAtInvalid) throws IOException {
174         final int tokenIndex = nextTokenIndex();
175         if (tokenIndex == -1) {
176             throw new ProtocolException("Missing required long");
177         } else {
178             return parseAndConsumeLong(tokenIndex, stopAtInvalid);
179         }
180     }
181 
182     /**
183      * Parse and return next token as base-10 encoded {@code long}, or return
184      * the given default value if no remaining tokens on current line.
185      */
nextOptionalLong(long def)186     public long nextOptionalLong(long def) throws IOException {
187         final int tokenIndex = nextTokenIndex();
188         if (tokenIndex == -1) {
189             return def;
190         } else {
191             return parseAndConsumeLong(tokenIndex, false);
192         }
193     }
194 
parseAndConsumeString(int tokenIndex)195     private String parseAndConsumeString(int tokenIndex) throws IOException {
196         final String s = new String(mBuffer, 0, tokenIndex, StandardCharsets.US_ASCII);
197         consumeBuf(tokenIndex + 1);
198         return s;
199     }
200 
201     /**
202      * If stopAtInvalid is true, don't throw IOException but return whatever parsed so far.
203      */
parseAndConsumeLong(int tokenIndex, boolean stopAtInvalid)204     private long parseAndConsumeLong(int tokenIndex, boolean stopAtInvalid) throws IOException {
205         final boolean negative = mBuffer[0] == '-';
206 
207         // TODO: refactor into something like IntegralToString
208         long result = 0;
209         for (int i = negative ? 1 : 0; i < tokenIndex; i++) {
210             final int digit = mBuffer[i] - '0';
211             if (digit < 0 || digit > 9) {
212                 if (stopAtInvalid) {
213                     break;
214                 } else {
215                     throw invalidLong(tokenIndex);
216                 }
217             }
218 
219             // always parse as negative number and apply sign later; this
220             // correctly handles MIN_VALUE which is "larger" than MAX_VALUE.
221             final long next = result * 10 - digit;
222             if (next > result) {
223                 throw invalidLong(tokenIndex);
224             }
225             result = next;
226         }
227 
228         consumeBuf(tokenIndex + 1);
229         return negative ? result : -result;
230     }
231 
invalidLong(int tokenIndex)232     private NumberFormatException invalidLong(int tokenIndex) {
233         return new NumberFormatException(
234                 "invalid long: " + new String(mBuffer, 0, tokenIndex, StandardCharsets.US_ASCII));
235     }
236 
237     /**
238      * Parse and return next token as base-10 encoded {@code int}.
239      */
nextInt()240     public int nextInt() throws IOException {
241         final long value = nextLong();
242         if (value > Integer.MAX_VALUE || value < Integer.MIN_VALUE) {
243             throw new NumberFormatException("parsed value larger than integer");
244         }
245         return (int) value;
246     }
247 
248     /**
249      * Bypass the next token.
250      */
nextIgnored()251     public void nextIgnored() throws IOException {
252         final int tokenIndex = nextTokenIndex();
253         if (tokenIndex == -1) {
254             throw new ProtocolException("Missing required token");
255         } else {
256             consumeBuf(tokenIndex + 1);
257         }
258     }
259 
260     @Override
close()261     public void close() throws IOException {
262         mStream.close();
263     }
264 }
265