1 /*
2  * Copyright (C) 2023 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 android.tools.traces.parsers.perfetto
18 
19 class Args {
20     private var child: MutableMap<String, Args>? = null
21     private var children: MutableMap<String, MutableList<Args>>? = null
22 
23     private var value: Any? = null
24 
getChildnull25     fun getChild(key: String): Args? = child?.get(key)
26 
27     fun getChildren(key: String): List<Args>? = children?.get(key)
28 
29     fun getBoolean(): Boolean {
30         require(value !== null) { "Cannot access value of a non-leaf args" }
31         return value as Boolean
32     }
33 
getIntnull34     fun getInt(): Int {
35         require(value !== null) { "Cannot access value of a non-leaf args" }
36         return (value as Long).toInt()
37     }
38 
getLongnull39     fun getLong(): Long {
40         require(value !== null) { "Cannot access value of a non-leaf args" }
41         return value as Long
42     }
43 
getFloatnull44     fun getFloat(): Float {
45         require(value !== null) { "Cannot access value of a non-leaf args" }
46         return (value as Double).toFloat()
47     }
48 
isStringnull49     fun isString(): Boolean {
50         return value is String
51     }
52 
getStringnull53     fun getString(): String {
54         require(value !== null) { "Cannot access value of a non-leaf args" }
55         return value as String
56     }
57 
addnull58     fun add(key: String, value: String, valueType: String) {
59         val keyTokens = key.split(".")
60         val parsedValue = parseValue(value, valueType)
61         add(keyTokens, 0, parsedValue)
62     }
63 
addnull64     private fun add(keyTokens: List<String>, currentToken: Int, value: Any) {
65         if (currentToken == keyTokens.size) {
66             require(child == null && children == null) {
67                 "Cannot assign a value to a non-leaf args"
68             }
69             this.value = value
70             return
71         }
72 
73         require(this.value == null) { "Cannot add child/children to a leaf args" }
74 
75         val keyAndIndex = tryParseKeyAndIndex(keyTokens[currentToken])
76         if (keyAndIndex != null) {
77             addToChildrenMap(keyAndIndex, keyTokens, currentToken, value)
78             return
79         }
80 
81         addToChildMap(keyTokens, currentToken, value)
82     }
83 
addToChildrenMapnull84     private fun addToChildrenMap(
85         keyAndIndex: Pair<String, Int>,
86         keyTokens: List<String>,
87         currentToken: Int,
88         value: Any
89     ) {
90         val (key, index) = keyAndIndex
91         if (children == null) {
92             children = HashMap()
93         }
94         if (children!![key] == null) {
95             children!![key] = ArrayList()
96         }
97         while (index >= children!![key]!!.size) {
98             children!![key]!!.add(Args())
99         }
100         children!![key]!![index].add(keyTokens, currentToken + 1, value)
101     }
102 
addToChildMapnull103     private fun addToChildMap(keyTokens: List<String>, currentToken: Int, value: Any) {
104         val key = keyTokens[currentToken]
105         if (child == null) {
106             child = HashMap()
107         }
108         if (child!![key] != null) {
109             child!![key]!!.add(keyTokens, currentToken + 1, value)
110         } else {
111             child!![key] = Args().apply { add(keyTokens, currentToken + 1, value) }
112         }
113     }
114 
parseValuenull115     private fun parseValue(value: String, valueType: String): Any {
116         val result: Any? =
117             when (valueType) {
118                 "bool" -> value.toBooleanStrict()
119                 "int" -> value.toLong()
120                 "uint" -> value.toLong()
121                 "real" -> value.toDouble()
122                 "string" -> value
123                 else -> null
124             }
125         require(result != null) { "Unrecognized args value type '$valueType'" }
126         return result
127     }
128 
tryParseKeyAndIndexnull129     private fun tryParseKeyAndIndex(token: String): Pair<String, Int>? {
130         require(token.length > 0) { "Key token cannot have length 0" }
131 
132         if (token[token.length - 1] != ']') {
133             return null
134         }
135 
136         val indexOpeningBracket = token.indexOf('[')
137         require(indexOpeningBracket != -1) {
138             "Key token contains closing bracket ']' but not an opening one"
139         }
140 
141         val key = token.substring(0, indexOpeningBracket)
142         val index = token.substring(indexOpeningBracket + 1, token.length - 1).toInt()
143         return Pair(key, index)
144     }
145 
146     companion object {
buildnull147         fun build(rows: List<Row>): Args {
148             val args = Args()
149             rows.forEach {
150                 val key = it.get("key")
151                 val value = it.get("value")
152                 val valueType = it.get("value_type")
153                 if (valueType != "null" && value != null) {
154                     args.add(key as String, value as String, valueType as String)
155                 }
156             }
157             return args
158         }
159     }
160 }
161