1/* 2 * Copyright (C) 2024 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 17import {Timestamp} from 'common/time'; 18import {TimeDuration} from 'common/time_duration'; 19import { 20 PropertySource, 21 PropertyTreeNode, 22} from 'trace/tree_node/property_tree_node'; 23 24export class PropertyTreeNodeFactory { 25 constructor( 26 private denylistProperties: string[] = [], 27 private visitPrototype = true, 28 ) {} 29 30 makePropertyRoot( 31 rootId: string, 32 rootName: string, 33 source: PropertySource, 34 value: any, 35 ): PropertyTreeNode { 36 return new PropertyTreeNode(rootId, rootName, source, value); 37 } 38 39 makeProtoProperty( 40 rootId: string, 41 name: string, 42 value: any, 43 ): PropertyTreeNode { 44 return this.makeProperty(rootId, name, PropertySource.PROTO, value); 45 } 46 47 makeDefaultProperty( 48 rootId: string, 49 name: string, 50 defaultValue: any, 51 ): PropertyTreeNode { 52 return this.makeSimpleChildProperty( 53 rootId, 54 name, 55 defaultValue, 56 PropertySource.DEFAULT, 57 ); 58 } 59 60 makeCalculatedProperty( 61 rootId: string, 62 propertyName: string, 63 value: any, 64 ): PropertyTreeNode { 65 return this.makeProperty( 66 rootId, 67 propertyName, 68 PropertySource.CALCULATED, 69 value, 70 ); 71 } 72 73 private makeProperty( 74 rootId: string, 75 name: string, 76 source: PropertySource, 77 value: any, 78 ): PropertyTreeNode { 79 if (this.hasInnerProperties(value)) { 80 return this.makeNestedProperty(rootId, name, source, value); 81 } else { 82 return this.makeSimpleChildProperty(rootId, name, value, source); 83 } 84 } 85 86 private makeNestedProperty( 87 rootId: string, 88 name: string, 89 source: PropertySource, 90 value: object | any[], 91 ): PropertyTreeNode { 92 const rootName = rootId.split(' '); 93 94 const innerRoot = this.makePropertyRoot( 95 name.length > 0 ? `${rootId}.${name}` : rootId, 96 name.length > 0 ? name : rootName.slice(1, rootName.length).join(' '), 97 source, 98 undefined, 99 ); 100 this.addInnerProperties(innerRoot, value, source); 101 102 return innerRoot; 103 } 104 105 private makeSimpleChildProperty( 106 rootId: string, 107 key: string, 108 value: any, 109 source: PropertySource, 110 ): PropertyTreeNode { 111 return new PropertyTreeNode(`${rootId}.${key}`, key, source, value); 112 } 113 114 private hasInnerProperties(value: any): boolean { 115 if (!value) return false; 116 if (Array.isArray(value)) return value.length > 0; 117 if (this.isLongType(value)) return false; 118 if (value instanceof Timestamp) return false; 119 if (value instanceof TimeDuration) return false; 120 return typeof value === 'object' && Object.keys(value).length > 0; 121 } 122 123 private isLongType(value: any): boolean { 124 const typeOfVal = value.$type?.name ?? value.constructor?.name; 125 if (typeOfVal === 'Long' || typeOfVal === 'BigInt') return true; 126 return false; 127 } 128 129 private addInnerProperties( 130 root: PropertyTreeNode, 131 value: any, 132 source: PropertySource, 133 ): void { 134 if (Array.isArray(value)) { 135 this.addArrayProperties(root, value, source); 136 } else { 137 this.addObjectProperties(root, value, source); 138 } 139 } 140 141 private addArrayProperties( 142 root: PropertyTreeNode, 143 value: any, 144 source: PropertySource, 145 ) { 146 for (const [key, val] of Object.entries(value)) { 147 root.addOrReplaceChild(this.makeProperty(`${root.id}`, key, source, val)); 148 } 149 } 150 151 private addObjectProperties( 152 root: PropertyTreeNode, 153 value: any, 154 source: PropertySource, 155 ) { 156 const keys = this.getValidPropertyNames(value); 157 158 for (const key of keys) { 159 root.addOrReplaceChild( 160 this.makeProperty(`${root.id}`, key, source, value[key]), 161 ); 162 } 163 } 164 165 private getValidPropertyNames(objProto: any): string[] { 166 if (objProto === null || objProto === undefined) { 167 return []; 168 } 169 const props: string[] = []; 170 let obj = objProto; 171 172 do { 173 const properties = Object.getOwnPropertyNames(obj).filter((it) => { 174 if (typeof objProto[it] === 'function') return false; 175 if (it.includes(`$`)) return false; 176 if (it.startsWith(`_`)) return false; 177 if (this.denylistProperties.includes(it)) return false; 178 179 const value = objProto[it]; 180 if (Array.isArray(value) && value.length > 0) return !value[0].stableId; 181 182 return value !== undefined; 183 }); 184 185 properties.forEach((prop) => { 186 if ( 187 typeof objProto[prop] !== 'function' && 188 props.indexOf(prop) === -1 189 ) { 190 props.push(prop); 191 } 192 }); 193 obj = this.visitPrototype ? Object.getPrototypeOf(obj) : undefined; 194 } while (obj); 195 return props; 196 } 197} 198 199export const DEFAULT_PROPERTY_TREE_NODE_FACTORY = new PropertyTreeNodeFactory(); 200