1/*
2 * Copyright (C) 2015 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
17package com.android.statementservice.retriever;
18
19import android.util.JsonReader;
20
21import org.json.JSONArray;
22import org.json.JSONException;
23import org.json.JSONObject;
24
25import java.io.IOException;
26import java.io.StringReader;
27import java.util.ArrayList;
28import java.util.List;
29
30/**
31 * Utility class that parses JSON-formatted statements.
32 */
33/* package private */ final class StatementParser {
34
35    private static final String FIELD_NOT_STRING_FORMAT_STRING = "Expected %s to be string.";
36    private static final String FIELD_NOT_ARRAY_FORMAT_STRING = "Expected %s to be array.";
37
38    /**
39     * Parses a JSON array of statements.
40     */
41    static ParsedStatement parseStatementList(String statementList, AbstractAsset source)
42            throws JSONException, IOException {
43        List<Statement> statements = new ArrayList<Statement>();
44        List<String> delegates = new ArrayList<String>();
45
46        JsonReader reader = new JsonReader(new StringReader(statementList));
47        reader.setLenient(false);
48
49        reader.beginArray();
50        while (reader.hasNext()) {
51            ParsedStatement result;
52            try {
53                result = parseStatement(reader, source);
54            } catch (AssociationServiceException e) {
55                // The element in the array is well formatted Json but not a well-formed Statement.
56                continue;
57            }
58            statements.addAll(result.getStatements());
59            delegates.addAll(result.getDelegates());
60        }
61        reader.endArray();
62
63        return new ParsedStatement(statements, delegates);
64    }
65
66    /**
67     * Parses a single JSON statement.
68     */
69    static ParsedStatement parseStatement(String statementString, AbstractAsset source)
70            throws AssociationServiceException, IOException, JSONException {
71        JsonReader reader = new JsonReader(new StringReader(statementString));
72        reader.setLenient(false);
73        return parseStatement(reader, source);
74    }
75
76    /**
77     * Parses a single JSON statement. This method guarantees that exactly one JSON object
78     * will be consumed.
79     */
80    static ParsedStatement parseStatement(JsonReader reader, AbstractAsset source)
81            throws JSONException, AssociationServiceException, IOException {
82        List<Statement> statements = new ArrayList<Statement>();
83        List<String> delegates = new ArrayList<String>();
84
85        JSONObject statement = JsonParser.parse(reader);
86
87        if (statement.optString(Utils.DELEGATE_FIELD_DELEGATE, null) != null) {
88            delegates.add(statement.optString(Utils.DELEGATE_FIELD_DELEGATE));
89        } else {
90            JSONObject targetObject = statement.optJSONObject(Utils.ASSET_DESCRIPTOR_FIELD_TARGET);
91            if (targetObject == null) {
92                throw new AssociationServiceException(String.format(
93                        FIELD_NOT_STRING_FORMAT_STRING, Utils.ASSET_DESCRIPTOR_FIELD_TARGET));
94            }
95
96            JSONArray relations = statement.optJSONArray(Utils.ASSET_DESCRIPTOR_FIELD_RELATION);
97            if (relations == null) {
98                throw new AssociationServiceException(String.format(
99                        FIELD_NOT_ARRAY_FORMAT_STRING, Utils.ASSET_DESCRIPTOR_FIELD_RELATION));
100            }
101
102            AbstractAsset target = AssetFactory.create(targetObject);
103            for (int i = 0; i < relations.length(); i++) {
104                statements.add(Statement
105                        .create(source, target, Relation.create(relations.getString(i))));
106            }
107        }
108
109        return new ParsedStatement(statements, delegates);
110    }
111}
112