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