1/* 2 * Copyright (c) 2024-2025 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import { COMPONENT_POP_FUNCTION } from '../core/common/EtsConst'; 17import { ViewTree, ViewTreeNode } from '../core/graph/ViewTree'; 18import { ClassSignature, MethodSignature } from '../core/model/ArkSignature'; 19import { Printer } from './Printer'; 20 21const DOT_FILE_HEADER = `digraph G { 22 graph [nodesep=0.1] 23 node [shape=box] 24 edge [arrowhead=vee] 25`; 26 27export class ViewTreePrinter extends Printer { 28 private viewTree: ViewTree; 29 private dupCnt: number; 30 31 constructor(viewTree: ViewTree) { 32 super(); 33 this.viewTree = viewTree; 34 this.dupCnt = 0; 35 } 36 37 public dump(): string { 38 this.printer.clear(); 39 40 let root = this.viewTree.getRoot(); 41 if (!root) { 42 return this.printer.toString(); 43 } 44 45 this.printer.write(DOT_FILE_HEADER); 46 this.walk(root, root.parent); 47 this.printer.write('}'); 48 49 return this.printer.toString(); 50 } 51 52 private walk(item: ViewTreeNode, parent: ViewTreeNode | null, map: Map<ViewTreeNode | ClassSignature | MethodSignature, string> = new Map()): void { 53 let skipChildren = this.writeNode(item, parent, map); 54 if (skipChildren) { 55 return; 56 } 57 for (const child of item.children) { 58 this.walk(child, item, map); 59 } 60 } 61 62 private escapeDotLabel(content: string[]): string { 63 const MAX_LABEL_LEN = 64; 64 const PRE_FIX_LEN = 5; 65 let label = content.join('|'); 66 if (label.length > MAX_LABEL_LEN) { 67 return label.substring(0, PRE_FIX_LEN) + '...' + label.substring(label.length - MAX_LABEL_LEN + PRE_FIX_LEN); 68 } 69 return label; 70 } 71 72 private writeNode(item: ViewTreeNode, parent: ViewTreeNode | null, map: Map<ViewTreeNode | ClassSignature | MethodSignature, string>): boolean { 73 let id = `Node${map.size}`; 74 let hasSameNode = map.has(item) || map.has(item.signature!); 75 76 if (hasSameNode) { 77 id = `${id}_${this.dupCnt++}`; 78 this.printer.write(` ${id} [label="${item.name}" style=filled color="green"]\n`); 79 } else { 80 this.printer.write(` ${id} [label="${item.name}"]\n`); 81 } 82 83 if (parent) { 84 this.printer.write(` ${map.get(parent)!} -> ${id}\n`); 85 } 86 87 this.writeNodeStateValues(item, id); 88 this.writeNodeAttributes(item, id); 89 this.writeNodeSignature(item, id); 90 91 if (map.get(item)) { 92 this.printer.write(` {rank="same"; ${id};${map.get(item)};}\n`); 93 this.printer.write(` ${id} -> ${map.get(item)}[style=dotted]\n`); 94 return true; 95 } else if (map.get(item.signature!)) { 96 this.printer.write(` {rank="same"; ${id};${map.get(item.signature!)};}\n`); 97 this.printer.write(` ${id} -> ${map.get(item.signature!)}[style=dotted]\n`); 98 return true; 99 } 100 101 map.set(item, id); 102 if (item.signature && !map.has(item.signature)) { 103 map.set(item.signature, id); 104 } 105 return false; 106 } 107 108 private writeNodeStateValues(item: ViewTreeNode, id: string): void { 109 if (item.stateValues.size > 0) { 110 let stateValuesId = `${id}val`; 111 let content: string[] = []; 112 item.stateValues.forEach(value => { 113 content.push(value.getName()); 114 }); 115 116 this.printer.write( 117 ` ${stateValuesId} [shape=ellipse label="StateValues\n ${this.escapeDotLabel( 118 content 119 )}" fontsize=10 height=.1 style=filled color=".7 .3 1.0" ]\n` 120 ); 121 this.printer.write(` ${id} -> ${stateValuesId}\n`); 122 } 123 } 124 125 private writeNodeAttributes(item: ViewTreeNode, id: string): void { 126 if (item.attributes.size > 0) { 127 let attributesId = `${id}attributes`; 128 let content: string[] = []; 129 for (const [key, _] of item.attributes) { 130 if (key !== COMPONENT_POP_FUNCTION) { 131 content.push(key); 132 } 133 } 134 if (content.length > 0) { 135 this.printer.write( 136 ` ${attributesId} [shape=ellipse label="property|Event\n${this.escapeDotLabel( 137 content 138 )}" fontsize=10 height=.1 style=filled color=".7 .3 1.0" ]\n` 139 ); 140 this.printer.write(` ${id} -> ${attributesId}\n`); 141 } 142 } 143 } 144 private writeNodeSignature(item: ViewTreeNode, id: string): void { 145 if (item.signature) { 146 let signatureId = `${id}signature`; 147 let content = [item.signature.toString()]; 148 this.printer.write( 149 ` ${signatureId} [shape=ellipse label="signature\n${this.escapeDotLabel(content)}" fontsize=10 height=.1 style=filled color=".7 .3 1.0" ]\n` 150 ); 151 this.printer.write(` ${id} -> ${signatureId}\n`); 152 } 153 } 154} 155