summaryrefslogtreecommitdiff
path: root/tools/powermodel/src
diff options
context:
space:
mode:
Diffstat (limited to 'tools/powermodel/src')
-rw-r--r--tools/powermodel/src/com/android/powermodel/CsvParser.java173
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);
+ }
+ }
+ }
+ }
+}