1 /*
2  * Copyright (C) 2020 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.statementservice.network.retriever
18 
19 import android.util.JsonReader
20 import com.android.statementservice.retriever.AbstractAsset
21 import com.android.statementservice.retriever.AssetFactory
22 import com.android.statementservice.retriever.JsonParser
23 import com.android.statementservice.retriever.Relation
24 import com.android.statementservice.retriever.Statement
25 import com.android.statementservice.utils.Result
26 import com.android.statementservice.utils.StatementUtils
27 import java.io.StringReader
28 import java.util.ArrayList
29 import com.android.statementservice.retriever.WebAsset
30 import com.android.statementservice.retriever.AndroidAppAsset
31 
32 /**
33  * Parses JSON from the Digital Asset Links specification. For examples, see [WebAsset],
34  * [AndroidAppAsset], and [Statement].
35  */
36 object StatementParser {
37 
38     private const val FIELD_NOT_STRING_FORMAT_STRING = "Expected %s to be string."
39     private const val FIELD_NOT_ARRAY_FORMAT_STRING = "Expected %s to be array."
40 
41     /**
42      * Parses a JSON array of statements.
43      */
44     fun parseStatementList(statementList: String, source: AbstractAsset): Result<ParsedStatement> {
45         val statements: MutableList<Statement> = ArrayList()
46         val delegates: MutableList<String> = ArrayList()
47         StringReader(statementList).use { stringReader ->
48             JsonReader(stringReader).use { reader ->
49                 reader.isLenient = false
50                 reader.beginArray()
51                 while (reader.hasNext()) {
52                     val result = parseOneStatement(reader, source)
53                     if (result is Result.Failure) {
54                         continue
55                     }
56                     result as Result.Success
57                     statements.addAll(result.value.statements)
58                     delegates.addAll(result.value.delegates)
59                 }
60                 reader.endArray()
61             }
62         }
63         return Result.Success(ParsedStatement(statements, delegates))
64     }
65 
66     /**
67      * Parses a single JSON statement.
68      */
69     fun parseStatement(statementString: String, source: AbstractAsset) =
70         StringReader(statementString).use { stringReader ->
71             JsonReader(stringReader).use { reader ->
72                 reader.isLenient = false
73                 parseOneStatement(reader, source)
74             }
75         }
76 
77     /**
78      * Parses a single JSON statement. This method guarantees that exactly one JSON object
79      * will be consumed.
80      */
81     private fun parseOneStatement(
82         reader: JsonReader,
83         source: AbstractAsset
84     ): Result<ParsedStatement> {
85         val statement = JsonParser.parse(reader)
86         val delegate = statement.optString(StatementUtils.DELEGATE_FIELD_DELEGATE)
87         if (!delegate.isNullOrEmpty()) {
88             return Result.Success(ParsedStatement(emptyList(), listOfNotNull(delegate)))
89         }
90 
91         val targetObject = statement.optJSONObject(StatementUtils.ASSET_DESCRIPTOR_FIELD_TARGET)
92             ?: return Result.Failure(
93                 FIELD_NOT_STRING_FORMAT_STRING.format(StatementUtils.ASSET_DESCRIPTOR_FIELD_TARGET)
94             )
95         val relations = statement.optJSONArray(StatementUtils.ASSET_DESCRIPTOR_FIELD_RELATION)
96             ?: return Result.Failure(
97                 FIELD_NOT_ARRAY_FORMAT_STRING.format(StatementUtils.ASSET_DESCRIPTOR_FIELD_RELATION)
98             )
99         val target = AssetFactory.create(targetObject)
100 
101         val statements = (0 until relations.length())
102             .map { relations.getString(it) }
103             .map(Relation::create)
104             .map { Statement.create(source, target, it) }
105         return Result.Success(ParsedStatement(statements, listOfNotNull(delegate)))
106     }
107 
108     data class ParsedStatement(val statements: List<Statement>, val delegates: List<String>)
109 }
110