diff options
Diffstat (limited to 'tools/powermodel/src')
-rw-r--r-- | tools/powermodel/src/com/android/powermodel/CsvParser.java | 173 |
1 files changed, 173 insertions, 0 deletions
diff --git a/tools/powermodel/src/com/android/powermodel/CsvParser.java b/tools/powermodel/src/com/android/powermodel/CsvParser.java new file mode 100644 index 000000000000..78cd261306fc --- /dev/null +++ b/tools/powermodel/src/com/android/powermodel/CsvParser.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.powermodel; + +import java.io.InputStream; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; + +/** + * Parses CSV. + * <p> + * Call parse() with an InputStream. + * <p> + * CsvLineProcessor.onLine() will be called for each line in the source document. + * <p> + * To simplify parsing and to protect against using too much memory for bad + * data, the maximum field length is {@link #MAX_FIELD_SIZE}. + */ +class CsvParser { + /** + * The maximum size of a single field in bytes. + */ + public static final int MAX_FIELD_SIZE = (8*1024)-1; + + /** + * Callback interface for each line of CSV as it is parsed. + */ + interface LineProcessor { + /** + * A line of CSV was parsed. + * + * @param lineNumber the line number in the file, starting at 1 + * @param fields the comma separated fields for the line + */ + void onLine(int lineNumber, ArrayList<String> fields) throws ParseException; + } + + /** + * Parse the CSV text in input, calling onto processor for each row. + */ + public static void parse(InputStream input, LineProcessor processor) + throws IOException, ParseException { + final Charset utf8 = StandardCharsets.UTF_8; + final byte[] buf = new byte[MAX_FIELD_SIZE+1]; + int lineNumber = 1; + int readPos = 0; + int prev = 0; + ArrayList<String> fields = new ArrayList<String>(); + boolean finalBuffer = false; + boolean escaping = false; + boolean sawQuote = false; + + while (!finalBuffer) { + int amt = input.read(buf, readPos, buf.length-readPos); + if (amt < 0) { + // No more data. Process whatever's left from before. + amt = readPos; + finalBuffer = true; + } else { + // Process whatever's left from before, plus the new data. + amt += readPos; + finalBuffer = false; + } + + // Process as much of this buffer as we can. + int fieldStart = 0; + int index = readPos; + int escapeIndex = escaping ? readPos : -1; + while (index < amt) { + byte c = buf[index]; + if (c == '\r' || c == '\n') { + if (escaping) { + // TODO: Quotes do not escape newlines in our CSV dialect, + // but we actually see some data where it should. + fields.add(new String(buf, fieldStart, escapeIndex-fieldStart)); + escapeIndex = -1; + escaping = false; + sawQuote = false; + } else { + fields.add(new String(buf, fieldStart, index-fieldStart)); + } + // Don't report blank lines + if (fields.size() > 1 || (fields.size() == 1 && fields.get(0).length() > 0)) { + processor.onLine(lineNumber, fields); + } + fields = new ArrayList<String>(); + if (!(c == '\n' && prev == '\r')) { + // Don't double increment for dos line endings. + lineNumber++; + } + fieldStart = index = index + 1; + } else { + if (escaping) { + // Field started with a " so quotes are escaped with " and commas + // don't matter except when following a single quote. + if (c == '"') { + if (sawQuote) { + buf[escapeIndex] = buf[index]; + escapeIndex++; + sawQuote = false; + } else { + sawQuote = true; + } + index++; + } else if (sawQuote && c == ',') { + fields.add(new String(buf, fieldStart, escapeIndex-fieldStart)); + fieldStart = index = index + 1; + escapeIndex = -1; + escaping = false; + sawQuote = false; + } else { + buf[escapeIndex] = buf[index]; + escapeIndex++; + index++; + sawQuote = false; + } + } else { + if (c == ',') { + fields.add(new String(buf, fieldStart, index-fieldStart)); + fieldStart = index + 1; + } else if (c == '"' && fieldStart == index) { + // First character is a " + escaping = true; + fieldStart = escapeIndex = index + 1; + } + index++; + } + } + prev = c; + } + + // A single field is greater than buf.length, so fail. + if (fieldStart == 0 && index == buf.length) { + throw new ParseException(lineNumber, "Line is too long: " + + new String(buf, 0, 20, utf8) + "..."); + } + + // Move whatever we didn't process to the beginning of the buffer + // and try again. + if (fieldStart != amt) { + readPos = (escaping ? escapeIndex : index) - fieldStart; + System.arraycopy(buf, fieldStart, buf, 0, readPos); + } else { + readPos = 0; + } + + // Process whatever's left over + if (finalBuffer) { + fields.add(new String(buf, 0, readPos)); + // If there is any content, return the last line. + if (fields.size() > 1 || (fields.size() == 1 && fields.get(0).length() > 0)) { + processor.onLine(lineNumber, fields); + } + } + } + } +} |