| /* |
| * Copyright 2012-present Facebook, Inc. |
| * |
| * 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.facebook.buck.json; |
| |
| import com.fasterxml.jackson.core.JsonFactory; |
| import com.fasterxml.jackson.core.JsonParseException; |
| import com.fasterxml.jackson.core.JsonParser; |
| import com.fasterxml.jackson.core.JsonToken; |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Maps; |
| |
| import java.io.EOFException; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.Reader; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * This is a special JSON parser that is customized to consume the JSON output of buck.py. |
| * Object values may be one of: null, a string, or an array of strings. This means that no |
| * sort of nested arrays or objects are allowed in the output as Parser is implemented |
| * today. This simplification makes it easier to leverage Jackson's streaming JSON API. |
| */ |
| public class BuildFileToJsonParser { |
| |
| private final JsonParser parser; |
| |
| public BuildFileToJsonParser(String json) throws IOException { |
| JsonFactory jsonFactory = new JsonFactory(); |
| this.parser = jsonFactory.createJsonParser(json); |
| } |
| |
| public BuildFileToJsonParser(InputStream json) throws IOException { |
| JsonFactory jsonFactory = new JsonFactory(); |
| this.parser = jsonFactory.createJsonParser(json); |
| } |
| |
| public BuildFileToJsonParser(Reader json) throws IOException { |
| JsonFactory jsonFactory = new JsonFactory(); |
| this.parser = jsonFactory.createJsonParser(json); |
| } |
| |
| /** |
| * Access the next set of rules from the build file processor. Note that for non-server |
| * invocations, this will collect all of the rules into one enormous list. |
| * |
| * @return List of rules expressed as a <em>very</em> simple mapping of JSON field names |
| * to Java primitives. |
| */ |
| public List<Map<String, Object>> nextRules() throws IOException { |
| // The parser below uses these objects for stateful purposes with the ultimate goal |
| // of populating the parsed rules into `currentObjects`. |
| // |
| // The parser is expecting two different styles of output: |
| // 1. Server mode: [{"key": "value"}, {"key": "value"}, ...] |
| // 2. Regular mode: {"key": "value"}, {"key": "value"}, ... |
| // |
| // Server mode output is a necessary short-term step to keep logic in the main Parser |
| // consistent (expecting to be able to correlate a set of rules with the specific BUCK file |
| // that generated them). This requirement creates an unnecessary performance weakness |
| // in this design where we cannot parallelize buck.py's parsing of BUCK files with buck's |
| // processing of the result into a DAG. Once this can be addressed, server mode should be |
| // eliminated. |
| List<Map<String, Object>> currentObjects = null; |
| String currentFieldName = null; |
| List<String> currentArray = null; |
| Map<String, Object> currentObject = null; |
| |
| while (true) { |
| JsonToken token = parser.nextToken(); |
| if (token == null) { |
| if (currentObject != null) { |
| throw new EOFException("unexpected end-of-stream"); |
| } else if (currentObjects == null) { |
| // This happens when buck.py failed to produce any output for this build rule (python |
| // parse error or raised exception, I bet). |
| throw new EOFException("missing build rules"); |
| } else { |
| return currentObjects; |
| } |
| } |
| |
| switch (token) { |
| case START_OBJECT: |
| // The syntax differs very slightly between server mode and not. If we encounter |
| // an object not inside of an array, we aren't in server mode and so we can just read |
| // all the objects until exhaustion. |
| if (currentObjects == null) { |
| currentObjects = Lists.newArrayList(); |
| } |
| currentObject = Maps.newHashMap(); |
| break; |
| |
| case END_OBJECT: |
| currentObjects.add(currentObject); |
| currentObject = null; |
| break; |
| |
| case START_ARRAY: |
| // Heuristic to detect whether we are in the above server mode or regular mode. If we |
| // encounter START_ARRAY before START_OBJECT, it must be server mode so we should build |
| // `currentObjects` now. Otherwise, this is the start of a new sub-array within |
| // an object. |
| if (currentObjects == null) { |
| currentObjects = Lists.newArrayList(); |
| } else { |
| currentArray = Lists.newArrayList(); |
| } |
| break; |
| |
| case END_ARRAY: |
| if (currentArray != null) { |
| currentObject.put(currentFieldName, currentArray); |
| currentArray = null; |
| currentFieldName = null; |
| break; |
| } else { |
| // Must be in server mode and we finished parsing a single BUCK file. |
| return currentObjects; |
| } |
| |
| case FIELD_NAME: |
| currentFieldName = parser.getText(); |
| break; |
| |
| case VALUE_STRING: |
| if (currentArray == null) { |
| currentObject.put(currentFieldName, parser.getText()); |
| currentFieldName = null; |
| } else { |
| currentArray.add(parser.getText()); |
| } |
| break; |
| |
| case VALUE_TRUE: |
| case VALUE_FALSE: |
| Preconditions.checkState(currentArray == null, "Unexpected boolean in JSON array"); |
| currentObject.put(currentFieldName, token == JsonToken.VALUE_TRUE); |
| currentFieldName = null; |
| break; |
| |
| case VALUE_NULL: |
| if (currentArray == null) { |
| currentObject.put(currentFieldName, null); |
| currentFieldName = null; |
| } else { |
| currentArray.add(null); |
| } |
| break; |
| |
| default: |
| throw new JsonParseException("Unexpected token: " + token, parser.getCurrentLocation()); |
| } |
| } |
| } |
| } |