blob: 2ea8c49b15b1aef0f1ef09def9ca7b445a855032 [file] [log] [blame]
Paul Duffinb5cd5222022-02-28 19:06:49 +00001#!/usr/bin/env python
2#
3# Copyright (C) 2022 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16"""Verify that one set of hidden API flags is a subset of another."""
Paul Duffin1f8a6b22022-03-08 16:31:55 +000017import dataclasses
18import typing
Paul Duffinb5cd5222022-02-28 19:06:49 +000019
20from itertools import chain
21
22
Paul Duffin1f8a6b22022-03-08 16:31:55 +000023@dataclasses.dataclass()
24class Node:
25
26 def values(self, selector):
27 """Get the values from a set of selected nodes.
28
29 :param selector: a function that can be applied to a key in the nodes
30 attribute to determine whether to return its values.
31
32 :return: A list of iterables of all the values associated with
33 this node and its children.
34 """
35 raise NotImplementedError("Please Implement this method")
36
37 def append_values(self, values, selector):
38 """Append the values associated with this node and its children.
39
40 For each item (key, child) in nodes the child node's values are returned
41 if and only if the selector returns True when called on its key. A child
42 node's values are all the values associated with it and all its
43 descendant nodes.
44
45 :param selector: a function that can be applied to a key in the nodes
46 attribute to determine whether to return its values.
47 :param values: a list of a iterables of values.
48 """
49 raise NotImplementedError("Please Implement this method")
50
51
Paul Duffinb5cd5222022-02-28 19:06:49 +000052# pylint: disable=line-too-long
Paul Duffin1f8a6b22022-03-08 16:31:55 +000053@dataclasses.dataclass()
54class InteriorNode(Node):
Paul Duffinb5cd5222022-02-28 19:06:49 +000055 """An interior node in a trie.
56
57 Each interior node has a dict that maps from an element of a signature to
58 either another interior node or a leaf. Each interior node represents either
59 a package, class or nested class. Class members are represented by a Leaf.
60
61 Associating the set of flags [public-api] with the signature
62 "Ljava/lang/Object;->String()Ljava/lang/String;" will cause the following
63 nodes to be created:
64 Node()
65 ^- package:java -> Node()
66 ^- package:lang -> Node()
67 ^- class:Object -> Node()
68 ^- member:String()Ljava/lang/String; -> Leaf([public-api])
69
70 Associating the set of flags [blocked,core-platform-api] with the signature
71 "Ljava/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;"
72 will cause the following nodes to be created:
73 Node()
74 ^- package:java -> Node()
75 ^- package:lang -> Node()
76 ^- class:Character -> Node()
77 ^- class:UnicodeScript -> Node()
78 ^- member:of(I)Ljava/lang/Character$UnicodeScript;
79 -> Leaf([blocked,core-platform-api])
Paul Duffinb5cd5222022-02-28 19:06:49 +000080 """
81
82 # pylint: enable=line-too-long
83
Paul Duffin1f8a6b22022-03-08 16:31:55 +000084 # A dict from an element of the signature to the Node/Leaf containing the
85 # next element/value.
86 nodes: typing.Dict[str, Node] = dataclasses.field(default_factory=dict)
Paul Duffinb5cd5222022-02-28 19:06:49 +000087
88 # pylint: disable=line-too-long
89 @staticmethod
90 def signature_to_elements(signature):
91 """Split a signature or a prefix into a number of elements:
92
93 1. The packages (excluding the leading L preceding the first package).
94 2. The class names, from outermost to innermost.
95 3. The member signature.
96 e.g.
97 Ljava/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;
98 will be broken down into these elements:
99 1. package:java
100 2. package:lang
101 3. class:Character
102 4. class:UnicodeScript
103 5. member:of(I)Ljava/lang/Character$UnicodeScript;
104 """
105 # Remove the leading L.
106 # - java/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;
107 text = signature.removeprefix("L")
108 # Split the signature between qualified class name and the class member
109 # signature.
110 # 0 - java/lang/Character$UnicodeScript
111 # 1 - of(I)Ljava/lang/Character$UnicodeScript;
112 parts = text.split(";->")
113 member = parts[1:]
114 # Split the qualified class name into packages, and class name.
115 # 0 - java
116 # 1 - lang
117 # 2 - Character$UnicodeScript
118 elements = parts[0].split("/")
119 packages = elements[0:-1]
120 class_name = elements[-1]
121 if class_name in ("*", "**"): # pylint: disable=no-else-return
122 # Cannot specify a wildcard and target a specific member
123 if len(member) != 0:
124 raise Exception(f"Invalid signature {signature}: contains "
125 f"wildcard {class_name} and "
126 f"member signature {member[0]}")
127 wildcard = [class_name]
128 # Assemble the parts into a single list, adding prefixes to identify
129 # the different parts.
130 # 0 - package:java
131 # 1 - package:lang
132 # 2 - *
133 return list(chain(["package:" + x for x in packages], wildcard))
134 else:
135 # Split the class name into outer / inner classes
136 # 0 - Character
137 # 1 - UnicodeScript
138 classes = class_name.split("$")
139 # Assemble the parts into a single list, adding prefixes to identify
140 # the different parts.
141 # 0 - package:java
142 # 1 - package:lang
143 # 2 - class:Character
144 # 3 - class:UnicodeScript
145 # 4 - member:of(I)Ljava/lang/Character$UnicodeScript;
146 return list(
147 chain(["package:" + x for x in packages],
148 ["class:" + x for x in classes],
149 ["member:" + x for x in member]))
150
151 # pylint: enable=line-too-long
152
153 def add(self, signature, value):
154 """Associate the value with the specific signature.
155
156 :param signature: the member signature
157 :param value: the value to associated with the signature
158 :return: n/a
159 """
160 # Split the signature into elements.
161 elements = self.signature_to_elements(signature)
162 # Find the Node associated with the deepest class.
163 node = self
164 for element in elements[:-1]:
165 if element in node.nodes:
166 node = node.nodes[element]
167 else:
168 next_node = InteriorNode()
169 node.nodes[element] = next_node
170 node = next_node
171 # Add a Leaf containing the value and associate it with the member
172 # signature within the class.
173 last_element = elements[-1]
174 if not last_element.startswith("member:"):
175 raise Exception(
176 f"Invalid signature: {signature}, does not identify a "
177 "specific member")
178 if last_element in node.nodes:
179 raise Exception(f"Duplicate signature: {signature}")
180 node.nodes[last_element] = Leaf(value)
181
182 def get_matching_rows(self, pattern):
183 """Get the values (plural) associated with the pattern.
184
185 e.g. If the pattern is a full signature then this will return a list
186 containing the value associated with that signature.
187
188 If the pattern is a class then this will return a list containing the
189 values associated with all members of that class.
190
191 If the pattern is a package then this will return a list containing the
192 values associated with all the members of all the classes in that
193 package and sub-packages.
194
195 If the pattern ends with "*" then the preceding part is treated as a
196 package and this will return a list containing the values associated
197 with all the members of all the classes in that package.
198
199 If the pattern ends with "**" then the preceding part is treated
200 as a package and this will return a list containing the values
201 associated with all the members of all the classes in that package and
202 all sub-packages.
203
204 :param pattern: the pattern which could be a complete signature or a
205 class, or package wildcard.
206 :return: an iterable containing all the values associated with the
207 pattern.
208 """
209 elements = self.signature_to_elements(pattern)
210 node = self
211
212 # Include all values from this node and all its children.
213 selector = lambda x: True
214
215 last_element = elements[-1]
216 if last_element in ("*", "**"):
217 elements = elements[:-1]
218 if last_element == "*":
219 # Do not include values from sub-packages.
220 selector = lambda x: not x.startswith("package:")
221
222 for element in elements:
223 if element in node.nodes:
224 node = node.nodes[element]
225 else:
226 return []
227 return chain.from_iterable(node.values(selector))
228
229 def values(self, selector):
Paul Duffinb5cd5222022-02-28 19:06:49 +0000230 values = []
231 self.append_values(values, selector)
232 return values
233
234 def append_values(self, values, selector):
Paul Duffinb5cd5222022-02-28 19:06:49 +0000235 for key, node in self.nodes.items():
236 if selector(key):
237 node.append_values(values, lambda x: True)
238
239
Paul Duffinb5cd5222022-02-28 19:06:49 +0000240
Paul Duffin1f8a6b22022-03-08 16:31:55 +0000241@dataclasses.dataclass()
242class Leaf(Node):
243 """A leaf of the trie"""
Paul Duffinb5cd5222022-02-28 19:06:49 +0000244
Paul Duffin1f8a6b22022-03-08 16:31:55 +0000245 # The value associated with this leaf.
246 value: typing.Any
Paul Duffinb5cd5222022-02-28 19:06:49 +0000247
Paul Duffin1f8a6b22022-03-08 16:31:55 +0000248 def values(self, selector):
Paul Duffinb5cd5222022-02-28 19:06:49 +0000249 return [[self.value]]
250
Paul Duffin1f8a6b22022-03-08 16:31:55 +0000251 def append_values(self, values, selector):
Paul Duffinb5cd5222022-02-28 19:06:49 +0000252 values.append([self.value])
253
254
255def signature_trie():
256 return InteriorNode()