|  | #!/usr/bin/env python | 
|  | # | 
|  | # Copyright (C) 2022 The Android Open Source Project | 
|  | # | 
|  | # Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | # you may not use this file except in compliance with the License. | 
|  | # You may obtain a copy of the License at | 
|  | # | 
|  | #      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | # | 
|  | # Unless required by applicable law or agreed to in writing, software | 
|  | # distributed under the License is distributed on an "AS IS" BASIS, | 
|  | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | # See the License for the specific language governing permissions and | 
|  | # limitations under the License. | 
|  | """Verify that one set of hidden API flags is a subset of another.""" | 
|  | import dataclasses | 
|  | import typing | 
|  |  | 
|  | from itertools import chain | 
|  |  | 
|  |  | 
|  | @dataclasses.dataclass() | 
|  | class Node: | 
|  | """A node in the signature trie.""" | 
|  |  | 
|  | # The type of the node. | 
|  | # | 
|  | # Leaf nodes are of type "member". | 
|  | # Interior nodes can be either "package", or "class". | 
|  | type: str | 
|  |  | 
|  | # The selector of the node. | 
|  | # | 
|  | # That is a string that can be used to select the node, e.g. in a pattern | 
|  | # that is passed to InteriorNode.get_matching_rows(). | 
|  | selector: str | 
|  |  | 
|  | def values(self, selector): | 
|  | """Get the values from a set of selected nodes. | 
|  |  | 
|  | :param selector: a function that can be applied to a key in the nodes | 
|  | attribute to determine whether to return its values. | 
|  |  | 
|  | :return: A list of iterables of all the values associated with | 
|  | this node and its children. | 
|  | """ | 
|  | values = [] | 
|  | self.append_values(values, selector) | 
|  | return values | 
|  |  | 
|  | def append_values(self, values, selector): | 
|  | """Append the values associated with this node and its children. | 
|  |  | 
|  | For each item (key, child) in nodes the child node's values are returned | 
|  | if and only if the selector returns True when called on its key. A child | 
|  | node's values are all the values associated with it and all its | 
|  | descendant nodes. | 
|  |  | 
|  | :param selector: a function that can be applied to a key in the nodes | 
|  | attribute to determine whether to return its values. | 
|  | :param values: a list of a iterables of values. | 
|  | """ | 
|  | raise NotImplementedError("Please Implement this method") | 
|  |  | 
|  | def child_nodes(self): | 
|  | """Get an iterable of the child nodes of this node.""" | 
|  | raise NotImplementedError("Please Implement this method") | 
|  |  | 
|  |  | 
|  | # pylint: disable=line-too-long | 
|  | @dataclasses.dataclass() | 
|  | class InteriorNode(Node): | 
|  | """An interior node in a trie. | 
|  |  | 
|  | Each interior node has a dict that maps from an element of a signature to | 
|  | either another interior node or a leaf. Each interior node represents either | 
|  | a package, class or nested class. Class members are represented by a Leaf. | 
|  |  | 
|  | Associating the set of flags [public-api] with the signature | 
|  | "Ljava/lang/Object;->String()Ljava/lang/String;" will cause the following | 
|  | nodes to be created: | 
|  | Node() | 
|  | ^- package:java -> Node() | 
|  | ^- package:lang -> Node() | 
|  | ^- class:Object -> Node() | 
|  | ^- member:String()Ljava/lang/String; -> Leaf([public-api]) | 
|  |  | 
|  | Associating the set of flags [blocked,core-platform-api] with the signature | 
|  | "Ljava/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;" | 
|  | will cause the following nodes to be created: | 
|  | Node() | 
|  | ^- package:java -> Node() | 
|  | ^- package:lang -> Node() | 
|  | ^- class:Character -> Node() | 
|  | ^- class:UnicodeScript -> Node() | 
|  | ^- member:of(I)Ljava/lang/Character$UnicodeScript; | 
|  | -> Leaf([blocked,core-platform-api]) | 
|  | """ | 
|  |  | 
|  | # pylint: enable=line-too-long | 
|  |  | 
|  | # A dict from an element of the signature to the Node/Leaf containing the | 
|  | # next element/value. | 
|  | nodes: typing.Dict[str, Node] = dataclasses.field(default_factory=dict) | 
|  |  | 
|  | # pylint: disable=line-too-long | 
|  | @staticmethod | 
|  | def signature_to_elements(signature): | 
|  | """Split a signature or a prefix into a number of elements: | 
|  |  | 
|  | 1. The packages (excluding the leading L preceding the first package). | 
|  | 2. The class names, from outermost to innermost. | 
|  | 3. The member signature. | 
|  | e.g. | 
|  | Ljava/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript; | 
|  | will be broken down into these elements: | 
|  | 1. package:java | 
|  | 2. package:lang | 
|  | 3. class:Character | 
|  | 4. class:UnicodeScript | 
|  | 5. member:of(I)Ljava/lang/Character$UnicodeScript; | 
|  | """ | 
|  | # Remove the leading L. | 
|  | #  - java/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript; | 
|  | text = signature.removeprefix("L") | 
|  | # Split the signature between qualified class name and the class member | 
|  | # signature. | 
|  | #  0 - java/lang/Character$UnicodeScript | 
|  | #  1 - of(I)Ljava/lang/Character$UnicodeScript; | 
|  | parts = text.split(";->") | 
|  | # If there is no member then this will be an empty list. | 
|  | member = parts[1:] | 
|  | # Split the qualified class name into packages, and class name. | 
|  | #  0 - java | 
|  | #  1 - lang | 
|  | #  2 - Character$UnicodeScript | 
|  | elements = parts[0].split("/") | 
|  | last_element = elements[-1] | 
|  | wildcard = [] | 
|  | classes = [] | 
|  | if "*" in last_element: | 
|  | if last_element not in ("*", "**"): | 
|  | raise Exception(f"Invalid signature '{signature}': invalid " | 
|  | f"wildcard '{last_element}'") | 
|  | packages = elements[0:-1] | 
|  | # Cannot specify a wildcard and target a specific member | 
|  | if member: | 
|  | raise Exception(f"Invalid signature '{signature}': contains " | 
|  | f"wildcard '{last_element}' and " | 
|  | f"member signature '{member[0]}'") | 
|  | wildcard = [last_element] | 
|  | else: | 
|  | packages = elements[0:-1] | 
|  | # Split the class name into outer / inner classes | 
|  | #  0 - Character | 
|  | #  1 - UnicodeScript | 
|  | classes = last_element.removesuffix(";").split("$") | 
|  |  | 
|  | # Assemble the parts into a single list, adding prefixes to identify | 
|  | # the different parts. If a wildcard is provided then it looks something | 
|  | # like this: | 
|  | #  0 - package:java | 
|  | #  1 - package:lang | 
|  | #  2 - * | 
|  | # | 
|  | # Otherwise, it looks something like this: | 
|  | #  0 - package:java | 
|  | #  1 - package:lang | 
|  | #  2 - class:Character | 
|  | #  3 - class:UnicodeScript | 
|  | #  4 - member:of(I)Ljava/lang/Character$UnicodeScript; | 
|  | return list( | 
|  | chain([("package", x) for x in packages], | 
|  | [("class", x) for x in classes], | 
|  | [("member", x) for x in member], | 
|  | [("wildcard", x) for x in wildcard])) | 
|  |  | 
|  | # pylint: enable=line-too-long | 
|  |  | 
|  | @staticmethod | 
|  | def split_element(element): | 
|  | element_type, element_value = element | 
|  | return element_type, element_value | 
|  |  | 
|  | @staticmethod | 
|  | def element_type(element): | 
|  | element_type, _ = InteriorNode.split_element(element) | 
|  | return element_type | 
|  |  | 
|  | @staticmethod | 
|  | def elements_to_selector(elements): | 
|  | """Compute a selector for a set of elements. | 
|  |  | 
|  | A selector uniquely identifies a specific Node in the trie. It is | 
|  | essentially a prefix of a signature (without the leading L). | 
|  |  | 
|  | e.g. a trie containing "Ljava/lang/Object;->String()Ljava/lang/String;" | 
|  | would contain nodes with the following selectors: | 
|  | * "java" | 
|  | * "java/lang" | 
|  | * "java/lang/Object" | 
|  | * "java/lang/Object;->String()Ljava/lang/String;" | 
|  | """ | 
|  | signature = "" | 
|  | preceding_type = "" | 
|  | for element in elements: | 
|  | element_type, element_value = InteriorNode.split_element(element) | 
|  | separator = "" | 
|  | if element_type == "package": | 
|  | separator = "/" | 
|  | elif element_type == "class": | 
|  | if preceding_type == "class": | 
|  | separator = "$" | 
|  | else: | 
|  | separator = "/" | 
|  | elif element_type == "wildcard": | 
|  | separator = "/" | 
|  | elif element_type == "member": | 
|  | separator += ";->" | 
|  |  | 
|  | if signature: | 
|  | signature += separator | 
|  |  | 
|  | signature += element_value | 
|  |  | 
|  | preceding_type = element_type | 
|  |  | 
|  | return signature | 
|  |  | 
|  | def add(self, signature, value, only_if_matches=False): | 
|  | """Associate the value with the specific signature. | 
|  |  | 
|  | :param signature: the member signature | 
|  | :param value: the value to associated with the signature | 
|  | :param only_if_matches: True if the value is added only if the signature | 
|  | matches at least one of the existing top level packages. | 
|  | :return: n/a | 
|  | """ | 
|  | # Split the signature into elements. | 
|  | elements = self.signature_to_elements(signature) | 
|  | # Find the Node associated with the deepest class. | 
|  | node = self | 
|  | for index, element in enumerate(elements[:-1]): | 
|  | if element in node.nodes: | 
|  | node = node.nodes[element] | 
|  | elif only_if_matches and index == 0: | 
|  | return | 
|  | else: | 
|  | selector = self.elements_to_selector(elements[0:index + 1]) | 
|  | next_node = InteriorNode( | 
|  | type=InteriorNode.element_type(element), selector=selector) | 
|  | node.nodes[element] = next_node | 
|  | node = next_node | 
|  | # Add a Leaf containing the value and associate it with the member | 
|  | # signature within the class. | 
|  | last_element = elements[-1] | 
|  | last_element_type = self.element_type(last_element) | 
|  | if last_element_type != "member": | 
|  | raise Exception( | 
|  | f"Invalid signature: {signature}, does not identify a " | 
|  | "specific member") | 
|  | if last_element in node.nodes: | 
|  | raise Exception(f"Duplicate signature: {signature}") | 
|  | leaf = Leaf( | 
|  | type=last_element_type, | 
|  | selector=signature, | 
|  | value=value, | 
|  | ) | 
|  | node.nodes[last_element] = leaf | 
|  |  | 
|  | def get_matching_rows(self, pattern): | 
|  | """Get the values (plural) associated with the pattern. | 
|  |  | 
|  | e.g. If the pattern is a full signature then this will return a list | 
|  | containing the value associated with that signature. | 
|  |  | 
|  | If the pattern is a class then this will return a list containing the | 
|  | values associated with all members of that class. | 
|  |  | 
|  | If the pattern ends with "*" then the preceding part is treated as a | 
|  | package and this will return a list containing the values associated | 
|  | with all the members of all the classes in that package. | 
|  |  | 
|  | If the pattern ends with "**" then the preceding part is treated | 
|  | as a package and this will return a list containing the values | 
|  | associated with all the members of all the classes in that package and | 
|  | all sub-packages. | 
|  |  | 
|  | :param pattern: the pattern which could be a complete signature or a | 
|  | class, or package wildcard. | 
|  | :return: an iterable containing all the values associated with the | 
|  | pattern. | 
|  | """ | 
|  | elements = self.signature_to_elements(pattern) | 
|  | node = self | 
|  |  | 
|  | # Include all values from this node and all its children. | 
|  | selector = lambda x: True | 
|  |  | 
|  | last_element = elements[-1] | 
|  | last_element_type, last_element_value = self.split_element(last_element) | 
|  | if last_element_type == "wildcard": | 
|  | elements = elements[:-1] | 
|  | if last_element_value == "*": | 
|  | # Do not include values from sub-packages. | 
|  | selector = lambda x: InteriorNode.element_type(x) != "package" | 
|  |  | 
|  | for element in elements: | 
|  | if element in node.nodes: | 
|  | node = node.nodes[element] | 
|  | else: | 
|  | return [] | 
|  |  | 
|  | return node.values(selector) | 
|  |  | 
|  | def append_values(self, values, selector): | 
|  | for key, node in self.nodes.items(): | 
|  | if selector(key): | 
|  | node.append_values(values, lambda x: True) | 
|  |  | 
|  | def child_nodes(self): | 
|  | return self.nodes.values() | 
|  |  | 
|  |  | 
|  | @dataclasses.dataclass() | 
|  | class Leaf(Node): | 
|  | """A leaf of the trie""" | 
|  |  | 
|  | # The value associated with this leaf. | 
|  | value: typing.Any | 
|  |  | 
|  | def append_values(self, values, selector): | 
|  | values.append(self.value) | 
|  |  | 
|  | def child_nodes(self): | 
|  | return [] | 
|  |  | 
|  |  | 
|  | def signature_trie(): | 
|  | return InteriorNode(type="root", selector="") |