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 {HierarchyTreeNode} from 'trace/tree_node/hierarchy_tree_node';
18import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
19import {TreeNode} from 'trace/tree_node/tree_node';
20import {IsModifiedCallbackType} from './add_diffs';
21import {AddDiffsPropertiesTree} from './add_diffs_properties_tree';
22import {Filter} from './operations/filter';
23import {UiPropertyTreeNode} from './ui_property_tree_node';
24import {UiTreeFormatter} from './ui_tree_formatter';
25import {TreeNodeFilter, UiTreeUtils} from './ui_tree_utils';
26import {UserOptions} from './user_options';
27
28export class PropertiesPresenter {
29  private propertiesFilter: TreeNodeFilter = UiTreeUtils.makePropertyFilter('');
30  private highlightedProperty = '';
31  private propertiesTree: PropertyTreeNode | undefined;
32  private formattedTree: UiPropertyTreeNode | undefined;
33
34  constructor(
35    private userOptions: UserOptions,
36    private denylistProperties: string[],
37  ) {}
38
39  getUserOptions() {
40    return this.userOptions;
41  }
42
43  setPropertiesTree(tree: PropertyTreeNode) {
44    this.propertiesTree = tree;
45  }
46
47  getPropertiesTree() {
48    return this.propertiesTree;
49  }
50
51  getFormattedTree() {
52    return this.formattedTree;
53  }
54
55  getHighlightedProperty() {
56    return this.highlightedProperty;
57  }
58
59  applyHighlightedPropertyChange(id: string) {
60    if (this.highlightedProperty === id) {
61      this.highlightedProperty = '';
62    } else {
63      this.highlightedProperty = id;
64    }
65  }
66
67  applyPropertiesFilterChange(filterString: string) {
68    this.propertiesFilter = UiTreeUtils.makePropertyFilter(filterString);
69  }
70
71  applyPropertiesUserOptionsChange(userOptions: UserOptions) {
72    this.userOptions = userOptions;
73  }
74
75  async formatPropertiesTree(
76    previousHierarchyTree: HierarchyTreeNode | undefined,
77    displayName: string | undefined,
78    keepCalculated: boolean,
79  ): Promise<void> {
80    if (!this.propertiesTree) {
81      this.formattedTree = undefined;
82      return;
83    }
84    const uiTree = UiPropertyTreeNode.from(this.propertiesTree);
85
86    if (
87      this.userOptions['showDiff']?.enabled &&
88      !this.userOptions['showDiff']?.isUnavailable
89    ) {
90      const prevEntryNode = previousHierarchyTree?.findDfs(
91        UiTreeUtils.makeIdMatchFilter(this.propertiesTree.id),
92      );
93      const prevEntryUiTree = prevEntryNode
94        ? UiPropertyTreeNode.from(await prevEntryNode.getAllProperties())
95        : undefined;
96      await new AddDiffsPropertiesTree(
97        PropertiesPresenter.isPropertyNodeModified,
98        this.denylistProperties,
99      ).executeInPlace(uiTree, prevEntryUiTree);
100    }
101
102    if (displayName) {
103      uiTree.setDisplayName(displayName);
104    }
105
106    const predicatesKeepingChildren = [this.propertiesFilter];
107    const predicatesDiscardingChildren = [];
108
109    if (this.denylistProperties) {
110      predicatesDiscardingChildren.push(
111        UiTreeUtils.makeDenyListFilterByName(this.denylistProperties),
112      );
113    }
114
115    if (!this.userOptions['showDefaults']?.enabled) {
116      predicatesDiscardingChildren.push(UiTreeUtils.isNotDefault);
117      predicatesDiscardingChildren.push(
118        UiTreeUtils.makePropertyMatchFilter('IDENTITY'),
119      );
120    }
121
122    if (!keepCalculated) {
123      predicatesDiscardingChildren.push(UiTreeUtils.isNotCalculated);
124    }
125    this.formattedTree = new UiTreeFormatter<UiPropertyTreeNode>()
126      .setUiTree(uiTree)
127      .addOperation(new Filter(predicatesDiscardingChildren, false))
128      .addOperation(new Filter(predicatesKeepingChildren, true))
129      .format();
130  }
131
132  clear() {
133    this.propertiesTree = undefined;
134    this.formattedTree = undefined;
135  }
136
137  static isPropertyNodeModified: IsModifiedCallbackType = async (
138    newTree: TreeNode | undefined,
139    oldTree: TreeNode | undefined,
140  ) => {
141    if (!newTree && !oldTree) return false;
142    if (!newTree || !oldTree) return true;
143
144    const newValue = (newTree as UiPropertyTreeNode).formattedValue();
145    const oldValue = (oldTree as UiPropertyTreeNode).formattedValue();
146
147    return oldValue !== newValue;
148  };
149}
150