• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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