1163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey/*
2163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey * Copyright (C) 2011 The Android Open Source Project
3163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey *
4163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey * Licensed under the Apache License, Version 2.0 (the "License");
5163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey * you may not use this file except in compliance with the License.
6163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey * You may obtain a copy of the License at
7163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey *
8163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey *      http://www.apache.org/licenses/LICENSE-2.0
9163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey *
10163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey * Unless required by applicable law or agreed to in writing, software
11163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey * distributed under the License is distributed on an "AS IS" BASIS,
12163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey * See the License for the specific language governing permissions and
14163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey * limitations under the License.
15163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey */
16163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey
17163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkeypackage com.android.internal.util;
18163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey
19163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkeyimport java.io.Closeable;
20163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkeyimport java.io.IOException;
21163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkeyimport java.io.InputStream;
22163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkeyimport java.nio.charset.Charsets;
23163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey
24163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey/**
25163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey * Reader that specializes in parsing {@code /proc/} files quickly. Walks
26163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey * through the stream using a single space {@code ' '} as token separator, and
27163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey * requires each line boundary to be explicitly acknowledged using
28163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey * {@link #finishLine()}. Assumes {@link Charsets#US_ASCII} encoding.
29163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey * <p>
30163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey * Currently doesn't support formats based on {@code \0}, tabs, or repeated
31163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey * delimiters.
32163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey */
33163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkeypublic class ProcFileReader implements Closeable {
34163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    private final InputStream mStream;
35163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    private final byte[] mBuffer;
36163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey
37163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    /** Write pointer in {@link #mBuffer}. */
38163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    private int mTail;
39163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    /** Flag when last read token finished current line. */
40163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    private boolean mLineFinished;
41163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey
42163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    public ProcFileReader(InputStream stream) throws IOException {
43163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        this(stream, 4096);
44163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    }
45163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey
46163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    public ProcFileReader(InputStream stream, int bufferSize) throws IOException {
47163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        mStream = stream;
48163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        mBuffer = new byte[bufferSize];
49163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey
50163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        // read enough to answer hasMoreData
51163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        fillBuf();
52163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    }
53163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey
54163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    /**
55163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey     * Read more data from {@link #mStream} into internal buffer.
56163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey     */
57163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    private int fillBuf() throws IOException {
58163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        final int length = mBuffer.length - mTail;
59163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        if (length == 0) {
60163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey            throw new IOException("attempting to fill already-full buffer");
61163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        }
62163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey
63163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        final int read = mStream.read(mBuffer, mTail, length);
64163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        if (read != -1) {
65163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey            mTail += read;
66163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        }
67163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        return read;
68163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    }
69163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey
70163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    /**
71163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey     * Consume number of bytes from beginning of internal buffer. If consuming
72163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey     * all remaining bytes, will attempt to {@link #fillBuf()}.
73163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey     */
74163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    private void consumeBuf(int count) throws IOException {
75163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        // TODO: consider moving to read pointer, but for now traceview says
76163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        // these copies aren't a bottleneck.
77163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        System.arraycopy(mBuffer, count, mBuffer, 0, mTail - count);
78163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        mTail -= count;
79163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        if (mTail == 0) {
80163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey            fillBuf();
81163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        }
82163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    }
83163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey
84163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    /**
85163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey     * Find buffer index of next token delimiter, usually space or newline. Will
86163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey     * fill buffer as needed.
87163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey     */
88163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    private int nextTokenIndex() throws IOException {
89163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        if (mLineFinished) {
90163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey            throw new IOException("no tokens remaining on current line");
91163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        }
92163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey
93163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        int i = 0;
94163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        do {
95163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey            // scan forward for token boundary
96163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey            for (; i < mTail; i++) {
97163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey                final byte b = mBuffer[i];
98163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey                if (b == '\n') {
99163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey                    mLineFinished = true;
100163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey                    return i;
101163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey                }
102163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey                if (b == ' ') {
103163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey                    return i;
104163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey                }
105163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey            }
106163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        } while (fillBuf() > 0);
107163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey
108163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        throw new IOException("end of stream while looking for token boundary");
109163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    }
110163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey
111163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    /**
112163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey     * Check if stream has more data to be parsed.
113163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey     */
114163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    public boolean hasMoreData() {
115163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        return mTail > 0;
116163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    }
117163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey
118163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    /**
119163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey     * Finish current line, skipping any remaining data.
120163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey     */
121163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    public void finishLine() throws IOException {
122163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        // last token already finished line; reset silently
123163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        if (mLineFinished) {
124163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey            mLineFinished = false;
125163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey            return;
126163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        }
127163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey
128163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        int i = 0;
129163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        do {
130163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey            // scan forward for line boundary and consume
131163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey            for (; i < mTail; i++) {
132163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey                if (mBuffer[i] == '\n') {
133163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey                    consumeBuf(i + 1);
134163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey                    return;
135163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey                }
136163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey            }
137163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        } while (fillBuf() > 0);
138163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey
139163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        throw new IOException("end of stream while looking for line boundary");
140163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    }
141163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey
142163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    /**
143163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey     * Parse and return next token as {@link String}.
144163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey     */
145163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    public String nextString() throws IOException {
146163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        final int tokenIndex = nextTokenIndex();
147163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        final String s = new String(mBuffer, 0, tokenIndex, Charsets.US_ASCII);
148163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        consumeBuf(tokenIndex + 1);
149163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        return s;
150163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    }
151163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey
152163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    /**
153163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey     * Parse and return next token as base-10 encoded {@code long}.
154163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey     */
155163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    public long nextLong() throws IOException {
156163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        final int tokenIndex = nextTokenIndex();
157163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        final boolean negative = mBuffer[0] == '-';
158163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey
159163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        // TODO: refactor into something like IntegralToString
160163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        long result = 0;
161163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        for (int i = negative ? 1 : 0; i < tokenIndex; i++) {
162163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey            final int digit = mBuffer[i] - '0';
163163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey            if (digit < 0 || digit > 9) {
164163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey                throw invalidLong(tokenIndex);
165163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey            }
166163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey
167163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey            // always parse as negative number and apply sign later; this
168163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey            // correctly handles MIN_VALUE which is "larger" than MAX_VALUE.
169163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey            final long next = result * 10 - digit;
170163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey            if (next > result) {
171163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey                throw invalidLong(tokenIndex);
172163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey            }
173163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey            result = next;
174163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        }
175163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey
176163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        consumeBuf(tokenIndex + 1);
177163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        return negative ? result : -result;
178163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    }
179163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey
180163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    private NumberFormatException invalidLong(int tokenIndex) {
181163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        return new NumberFormatException(
182163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey                "invalid long: " + new String(mBuffer, 0, tokenIndex, Charsets.US_ASCII));
183163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    }
184163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey
185163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    /**
186163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey     * Parse and return next token as base-10 encoded {@code int}.
187163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey     */
188163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    public int nextInt() throws IOException {
189163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        final long value = nextLong();
190163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        if (value > Integer.MAX_VALUE || value < Integer.MIN_VALUE) {
191163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey            throw new NumberFormatException("parsed value larger than integer");
192163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        }
193163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        return (int) value;
194163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    }
195163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey
196163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    public void close() throws IOException {
197163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey        mStream.close();
198163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey    }
199163e6443f27884a9bfcb9a48ef606dc635852c23Jeff Sharkey}
200