| # Lint as: python3 |
| # Copyright 2020 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """Implementation of the graph module for a [Java class] dependency graph.""" |
| |
| from typing import Optional, Set, Tuple |
| |
| import graph |
| import class_json_consts |
| |
| |
| def java_class_params_to_key(package: str, class_name: str): |
| """Returns the unique key created from a package and class name.""" |
| return f'{package}.{class_name}' |
| |
| |
| def split_nested_class_from_key(key: str) -> Tuple[str, Optional[str]]: |
| """Splits a jdeps class name into its key and nested class, if any. |
| |
| E.g. package.class => 'package.class', None |
| package.class$nested => 'package.class', 'nested' |
| """ |
| first_dollar_sign = key.find('$') |
| if first_dollar_sign == -1: |
| return key, None |
| else: |
| return key[:first_dollar_sign], key[first_dollar_sign + 1:] |
| |
| |
| class JavaClass(graph.Node): |
| """A representation of a Java class. |
| |
| Some classes may have nested classes (eg. explicitly, or |
| implicitly through lambdas). We treat these nested classes as part of |
| the outer class, storing only their names as metadata. |
| """ |
| def __init__(self, package: str, class_name: str): |
| """Initializes a new Java class structure. |
| |
| The package and class_name are used to create a unique key per class. |
| |
| Args: |
| package: The package the class belongs to. |
| class_name: The name of the class. For nested classes, this is |
| the name of the class that contains them. |
| """ |
| super().__init__(java_class_params_to_key(package, class_name)) |
| |
| self._package = package |
| self._class_name = class_name |
| |
| self._nested_classes = set() |
| self._build_targets = set() |
| |
| @property |
| def package(self): |
| """The package the class belongs to.""" |
| return self._package |
| |
| @property |
| def class_name(self): |
| """The name of the class. |
| |
| For nested classes, this is the name of the class that contains them. |
| """ |
| return self._class_name |
| |
| @property |
| def nested_classes(self): |
| """A set of nested classes contained within this class.""" |
| return self._nested_classes |
| |
| @nested_classes.setter |
| def nested_classes(self, other): |
| self._nested_classes = other |
| |
| @property |
| def build_targets(self) -> Set[str]: |
| """Which build target(s) contain the class.""" |
| # TODO(crbug.com/40147556): Make this return a List, sorted by |
| # importance. |
| return self._build_targets |
| |
| @build_targets.setter |
| def build_targets(self, other): |
| self._build_targets = other |
| |
| def add_nested_class(self, nested: str): |
| self._nested_classes.add(nested) |
| |
| def add_build_target(self, build_target: str) -> None: |
| self._build_targets.add(build_target) |
| |
| def get_node_metadata(self): |
| """Generates JSON metadata for the current node. |
| |
| The list of nested classes is sorted in order to help with testing. |
| Structure: |
| { |
| 'package': str, |
| 'class': str, |
| 'build_targets' [ str, ... ] |
| 'nested_classes': [ class_key, ... ], |
| } |
| """ |
| return { |
| class_json_consts.PACKAGE: self.package, |
| class_json_consts.CLASS: self.class_name, |
| class_json_consts.BUILD_TARGETS: sorted(self.build_targets), |
| class_json_consts.NESTED_CLASSES: sorted(self.nested_classes), |
| } |
| |
| |
| class JavaClassDependencyGraph(graph.Graph[JavaClass]): |
| """A graph representation of the dependencies between Java classes. |
| |
| A directed edge A -> B indicates that A depends on B. |
| """ |
| def create_node_from_key(self, key: str): |
| """Splits the key into package and class_name.""" |
| key_without_nested_class, _ = split_nested_class_from_key(key) |
| last_period = key_without_nested_class.rfind('.') |
| return JavaClass(package=key_without_nested_class[:last_period], |
| class_name=key_without_nested_class[last_period + 1:]) |