Home | History | Annotate | Download | only in util
      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.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  */
     33 public 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