blob: 5871834c232e4ed6b3d9c9bbedc42381b9c49608 [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(";->")
Paul Duffin19255f12022-03-08 16:35:52 +0000113 # If there is no member then this will be an empty list.
Paul Duffinb5cd5222022-02-28 19:06:49 +0000114 member = parts[1:]
115 # Split the qualified class name into packages, and class name.
116 # 0 - java
117 # 1 - lang
118 # 2 - Character$UnicodeScript
119 elements = parts[0].split("/")
Paul Duffin19255f12022-03-08 16:35:52 +0000120 last_element = elements[-1]
121 wildcard = []
122 classes = []
123 if "*" in last_element:
124 if last_element not in ("*", "**"):
125 raise Exception(f"Invalid signature '{signature}': invalid "
126 f"wildcard '{last_element}'")
127 packages = elements[0:-1]
Paul Duffinb5cd5222022-02-28 19:06:49 +0000128 # Cannot specify a wildcard and target a specific member
Paul Duffin19255f12022-03-08 16:35:52 +0000129 if member:
130 raise Exception(f"Invalid signature '{signature}': contains "
131 f"wildcard '{last_element}' and "
132 f"member signature '{member[0]}'")
133 wildcard = [last_element]
134 elif last_element.islower():
135 raise Exception(f"Invalid signature '{signature}': last element "
136 f"'{last_element}' is lower case but should be an "
137 f"upper case class name or wildcard")
Paul Duffinb5cd5222022-02-28 19:06:49 +0000138 else:
Paul Duffin19255f12022-03-08 16:35:52 +0000139 packages = elements[0:-1]
Paul Duffinb5cd5222022-02-28 19:06:49 +0000140 # Split the class name into outer / inner classes
141 # 0 - Character
142 # 1 - UnicodeScript
Paul Duffin19255f12022-03-08 16:35:52 +0000143 classes = last_element.removesuffix(";").split("$")
144
145 # Assemble the parts into a single list, adding prefixes to identify
146 # the different parts. If a wildcard is provided then it looks something
147 # like this:
148 # 0 - package:java
149 # 1 - package:lang
150 # 2 - *
151 #
152 # Otherwise, it looks something like this:
153 # 0 - package:java
154 # 1 - package:lang
155 # 2 - class:Character
156 # 3 - class:UnicodeScript
157 # 4 - member:of(I)Ljava/lang/Character$UnicodeScript;
158 return list(
Paul Duffinea935422022-03-09 14:51:17 +0000159 chain([("package", x) for x in packages],
160 [("class", x) for x in classes],
161 [("member", x) for x in member],
162 [("wildcard", x) for x in wildcard]))
Paul Duffinb5cd5222022-02-28 19:06:49 +0000163
164 # pylint: enable=line-too-long
165
Paul Duffin19255f12022-03-08 16:35:52 +0000166 @staticmethod
167 def split_element(element):
Paul Duffinea935422022-03-09 14:51:17 +0000168 element_type, element_value = element
Paul Duffin19255f12022-03-08 16:35:52 +0000169 return element_type, element_value
170
171 @staticmethod
172 def element_type(element):
173 element_type, _ = InteriorNode.split_element(element)
174 return element_type
175
Paul Duffinb5cd5222022-02-28 19:06:49 +0000176 def add(self, signature, value):
177 """Associate the value with the specific signature.
178
179 :param signature: the member signature
180 :param value: the value to associated with the signature
181 :return: n/a
182 """
183 # Split the signature into elements.
184 elements = self.signature_to_elements(signature)
185 # Find the Node associated with the deepest class.
186 node = self
187 for element in elements[:-1]:
188 if element in node.nodes:
189 node = node.nodes[element]
190 else:
191 next_node = InteriorNode()
192 node.nodes[element] = next_node
193 node = next_node
194 # Add a Leaf containing the value and associate it with the member
195 # signature within the class.
196 last_element = elements[-1]
Paul Duffin19255f12022-03-08 16:35:52 +0000197 last_element_type = self.element_type(last_element)
198 if last_element_type != "member":
Paul Duffinb5cd5222022-02-28 19:06:49 +0000199 raise Exception(
200 f"Invalid signature: {signature}, does not identify a "
201 "specific member")
202 if last_element in node.nodes:
203 raise Exception(f"Duplicate signature: {signature}")
204 node.nodes[last_element] = Leaf(value)
205
206 def get_matching_rows(self, pattern):
207 """Get the values (plural) associated with the pattern.
208
209 e.g. If the pattern is a full signature then this will return a list
210 containing the value associated with that signature.
211
212 If the pattern is a class then this will return a list containing the
213 values associated with all members of that class.
214
215 If the pattern is a package then this will return a list containing the
216 values associated with all the members of all the classes in that
217 package and sub-packages.
218
219 If the pattern ends with "*" then the preceding part is treated as a
220 package and this will return a list containing the values associated
221 with all the members of all the classes in that package.
222
223 If the pattern ends with "**" then the preceding part is treated
224 as a package and this will return a list containing the values
225 associated with all the members of all the classes in that package and
226 all sub-packages.
227
228 :param pattern: the pattern which could be a complete signature or a
229 class, or package wildcard.
230 :return: an iterable containing all the values associated with the
231 pattern.
232 """
233 elements = self.signature_to_elements(pattern)
234 node = self
235
236 # Include all values from this node and all its children.
237 selector = lambda x: True
238
239 last_element = elements[-1]
Paul Duffin19255f12022-03-08 16:35:52 +0000240 last_element_type, last_element_value = self.split_element(last_element)
241 if last_element_type == "wildcard":
Paul Duffinb5cd5222022-02-28 19:06:49 +0000242 elements = elements[:-1]
Paul Duffin19255f12022-03-08 16:35:52 +0000243 if last_element_value == "*":
Paul Duffinb5cd5222022-02-28 19:06:49 +0000244 # Do not include values from sub-packages.
Paul Duffin19255f12022-03-08 16:35:52 +0000245 selector = lambda x: InteriorNode.element_type(x) != "package"
Paul Duffinb5cd5222022-02-28 19:06:49 +0000246
247 for element in elements:
248 if element in node.nodes:
249 node = node.nodes[element]
250 else:
251 return []
252 return chain.from_iterable(node.values(selector))
253
254 def values(self, selector):
Paul Duffinb5cd5222022-02-28 19:06:49 +0000255 values = []
256 self.append_values(values, selector)
257 return values
258
259 def append_values(self, values, selector):
Paul Duffinb5cd5222022-02-28 19:06:49 +0000260 for key, node in self.nodes.items():
261 if selector(key):
262 node.append_values(values, lambda x: True)
263
264
Paul Duffin1f8a6b22022-03-08 16:31:55 +0000265@dataclasses.dataclass()
266class Leaf(Node):
267 """A leaf of the trie"""
Paul Duffinb5cd5222022-02-28 19:06:49 +0000268
Paul Duffin1f8a6b22022-03-08 16:31:55 +0000269 # The value associated with this leaf.
270 value: typing.Any
Paul Duffinb5cd5222022-02-28 19:06:49 +0000271
Paul Duffin1f8a6b22022-03-08 16:31:55 +0000272 def values(self, selector):
Paul Duffinb5cd5222022-02-28 19:06:49 +0000273 return [[self.value]]
274
Paul Duffin1f8a6b22022-03-08 16:31:55 +0000275 def append_values(self, values, selector):
Paul Duffinb5cd5222022-02-28 19:06:49 +0000276 values.append([self.value])
277
278
279def signature_trie():
280 return InteriorNode()