chromium/tools/android/dependency_analysis/java_group.py

# Copyright 2022 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Implementation for managing a group of JavaClass nodes."""

import collections
from typing import Set, Tuple

import class_dependency
import graph
import group_json_consts


class JavaGroup(graph.Node):
    """A representation of a group of classes."""

    def __init__(self, group_name: str):
        """Initializes a new Java group structure.

        Args:
            group_name: The name of this group.
        """
        super().__init__(group_name)

        self._classes = {}
        self._class_dependency_edges = collections.defaultdict(set)

    @property
    def classes(self):
        """A map { name -> JavaClass } of classes within this group."""
        return self._classes

    def add_class(self, java_class: class_dependency.JavaClass):
        """Adds a JavaClass to the group, if its key doesn't already exist.

        Notably, this does /not/ automatically update the inbound/outbound
        dependencies of the group with the dependencies of the class.
        """
        if java_class.name not in self._classes:
            self._classes[java_class.name] = java_class

    def add_class_dependency_edge(self, end_group: 'JavaGroup',
                                  begin_class: class_dependency.JavaClass,
                                  end_class: class_dependency.JavaClass):
        """Adds a class dependency edge as part of a group dependency.

        Each group dependency is comprised of one or more class dependencies,
        we manually update the nodes with this info when parsing class graphs.

        Args:
            end_group: the end node of the group dependency edge which starts
                from this node.
            begin_class: the start node of the class dependency edge.
            end_class: the end node of the class dependency edge.
        """
        class_edge = (begin_class, end_class)
        if class_edge not in self._class_dependency_edges[end_group]:
            self._class_dependency_edges[end_group].add(class_edge)

    def get_class_dependencies_in_outbound_edge(self, end_node: 'JavaGroup'
                                                ) -> Set[Tuple]:
        """Breaks down a group dependency edge into class dependencies.

        For group A to depend on another group B, there must exist at least one
        class in A depending on a class in B. This method, given a group
        dependency edge A -> B, returns a set of class dependency edges
        satisfying (class in A) -> (class in B).

        Args:
            end_node: The destination node. This method will return the class
                dependencies forming the edge from the current node to end_node.

        Returns:
            A set of tuples of `JavaClass` nodes, where a tuple (a, b)
            indicates a class dependency a -> b. If there are no relevant
            class dependencies, returns an empty set.
        """
        return self._class_dependency_edges[end_node]

    def get_node_metadata(self):
        """Generates JSON metadata for the current node.

        The list of classes is sorted in order to help with testing.
        Structure:
        {
            'classes': [ class_key, ... ],
        }
        """
        return {
            group_json_consts.CLASSES: sorted(self.classes.keys()),
        }