ProcFileReader.java revision 163e6443f27884a9bfcb9a48ef606dc635852c23
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
17package com.android.internal.util;
18
19import java.io.Closeable;
20import java.io.IOException;
21import java.io.InputStream;
22import java.nio.charset.Charsets;
23
24/**
25 * Reader that specializes in parsing {@code /proc/} files quickly. Walks
26 * through the stream using a single space {@code ' '} as token separator, and
27 * requires each line boundary to be explicitly acknowledged using
28 * {@link #finishLine()}. Assumes {@link Charsets#US_ASCII} encoding.
29 * <p>
30 * Currently doesn't support formats based on {@code \0}, tabs, or repeated
31 * delimiters.
32 */
33public class ProcFileReader implements Closeable {
34    private final InputStream mStream;
35    private final byte[] mBuffer;
36
37    /** Write pointer in {@link #mBuffer}. */
38    private int mTail;
39    /** Flag when last read token finished current line. */
40    private boolean mLineFinished;
41
42    public ProcFileReader(InputStream stream) throws IOException {
43        this(stream, 4096);
44    }
45
46    public ProcFileReader(InputStream stream, int bufferSize) throws IOException {
47        mStream = stream;
48        mBuffer = new byte[bufferSize];
49
50        // read enough to answer hasMoreData
51        fillBuf();
52    }
53
54    /**
55     * Read more data from {@link #mStream} into internal buffer.
56     */
57    private int fillBuf() throws IOException {
58        final int length = mBuffer.length - mTail;
59        if (length == 0) {
60            throw new IOException("attempting to fill already-full buffer");
61        }
62
63        final int read = mStream.read(mBuffer, mTail, length);
64        if (read != -1) {
65            mTail += read;
66        }
67        return read;
68    }
69
70    /**
71     * Consume number of bytes from beginning of internal buffer. If consuming
72     * all remaining bytes, will attempt to {@link #fillBuf()}.
73     */
74    private void consumeBuf(int count) throws IOException {
75        // TODO: consider moving to read pointer, but for now traceview says
76        // these copies aren't a bottleneck.
77        System.arraycopy(mBuffer, count, mBuffer, 0, mTail - count);
78        mTail -= count;
79        if (mTail == 0) {
80            fillBuf();
81        }
82    }
83
84    /**
85     * Find buffer index of next token delimiter, usually space or newline. Will
86     * fill buffer as needed.
87     */
88    private int nextTokenIndex() throws IOException {
89        if (mLineFinished) {
90            throw new IOException("no tokens remaining on current line");
91        }
92
93        int i = 0;
94        do {
95            // scan forward for token boundary
96            for (; i < mTail; i++) {
97                final byte b = mBuffer[i];
98                if (b == '\n') {
99                    mLineFinished = true;
100                    return i;
101                }
102                if (b == ' ') {
103                    return i;
104                }
105            }
106        } while (fillBuf() > 0);
107
108        throw new IOException("end of stream while looking for token boundary");
109    }
110
111    /**
112     * Check if stream has more data to be parsed.
113     */
114    public boolean hasMoreData() {
115        return mTail > 0;
116    }
117
118    /**
119     * Finish current line, skipping any remaining data.
120     */
121    public void finishLine() throws IOException {
122        // last token already finished line; reset silently
123        if (mLineFinished) {
124            mLineFinished = false;
125            return;
126        }
127
128        int i = 0;
129        do {
130            // scan forward for line boundary and consume
131            for (; i < mTail; i++) {
132                if (mBuffer[i] == '\n') {
133                    consumeBuf(i + 1);
134                    return;
135                }
136            }
137        } while (fillBuf() > 0);
138
139        throw new IOException("end of stream while looking for line boundary");
140    }
141
142    /**
143     * Parse and return next token as {@link String}.
144     */
145    public String nextString() throws IOException {
146        final int tokenIndex = nextTokenIndex();
147        final String s = new String(mBuffer, 0, tokenIndex, Charsets.US_ASCII);
148        consumeBuf(tokenIndex + 1);
149        return s;
150    }
151
152    /**
153     * Parse and return next token as base-10 encoded {@code long}.
154     */
155    public long nextLong() throws IOException {
156        final int tokenIndex = nextTokenIndex();
157        final boolean negative = mBuffer[0] == '-';
158
159        // TODO: refactor into something like IntegralToString
160        long result = 0;
161        for (int i = negative ? 1 : 0; i < tokenIndex; i++) {
162            final int digit = mBuffer[i] - '0';
163            if (digit < 0 || digit > 9) {
164                throw invalidLong(tokenIndex);
165            }
166
167            // always parse as negative number and apply sign later; this
168            // correctly handles MIN_VALUE which is "larger" than MAX_VALUE.
169            final long next = result * 10 - digit;
170            if (next > result) {
171                throw invalidLong(tokenIndex);
172            }
173            result = next;
174        }
175
176        consumeBuf(tokenIndex + 1);
177        return negative ? result : -result;
178    }
179
180    private NumberFormatException invalidLong(int tokenIndex) {
181        return new NumberFormatException(
182                "invalid long: " + new String(mBuffer, 0, tokenIndex, Charsets.US_ASCII));
183    }
184
185    /**
186     * Parse and return next token as base-10 encoded {@code int}.
187     */
188    public int nextInt() throws IOException {
189        final long value = nextLong();
190        if (value > Integer.MAX_VALUE || value < Integer.MIN_VALUE) {
191            throw new NumberFormatException("parsed value larger than integer");
192        }
193        return (int) value;
194    }
195
196    public void close() throws IOException {
197        mStream.close();
198    }
199}
200