use std::sync::Arc;
use crate::edge::GraphEdge;
use crate::graph::CodeGraph;
use crate::id::{GraphEdgeId, GraphNodeId, index_of, position};
use super::betweenness::{self, BetweennessCentrality};
use super::components::{self, CondensationGraph, GraphComponents};
use super::degree::{self, DegreeCentrality};
use super::divergence::{self, GraphDivergence};
use super::error::GraphAnalysisError;
use super::layout::{self, LayoutAssistMetadata};
use super::limits::GraphAnalysisLimits;
use super::partition::{self, DeclaredModulePartition};
use super::selection::GraphEdgeSelection;
use super::traversal::{self, GraphDirection, GraphNeighbor, GraphPath, GraphSubgraph};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct SelectedAdjacency {
edge: GraphEdgeId,
node: GraphNodeId,
}
impl SelectedAdjacency {
pub(crate) fn edge(self) -> GraphEdgeId {
self.edge
}
pub(crate) fn node(self) -> GraphNodeId {
self.node
}
}
pub(crate) struct SelectedIndexes {
outgoing: Box<[Box<[SelectedAdjacency]>]>,
incoming: Box<[Box<[SelectedAdjacency]>]>,
parents: Box<[Option<GraphNodeId>]>,
edges: u32,
}
impl SelectedIndexes {
fn build(graph: &CodeGraph, selection: GraphEdgeSelection, admitted: (u32, u32)) -> Self {
let (nodes, edges) = admitted;
let width = index_of(nodes);
let mut outgoing: Vec<Vec<SelectedAdjacency>> = vec![Vec::new(); width];
let mut incoming: Vec<Vec<SelectedAdjacency>> = vec![Vec::new(); width];
let mut parents: Vec<Option<GraphNodeId>> = vec![None; width];
for edge in selected(graph, selection) {
append(
&mut outgoing,
edge.source(),
entry(edge.id(), edge.target()),
);
append(
&mut incoming,
edge.target(),
entry(edge.id(), edge.source()),
);
}
for edge in graph.containment() {
claim(&mut parents, edge.child(), edge.parent());
}
Self {
outgoing: sealed(outgoing),
incoming: sealed(incoming),
parents: parents.into_boxed_slice(),
edges,
}
}
pub(crate) fn outgoing(&self, node: GraphNodeId) -> &[SelectedAdjacency] {
borrowed(&self.outgoing, node)
}
pub(crate) fn incoming(&self, node: GraphNodeId) -> &[SelectedAdjacency] {
borrowed(&self.incoming, node)
}
pub(crate) fn parent(&self, node: GraphNodeId) -> Option<GraphNodeId> {
self.parents.get(index_of(node.index())).copied().flatten()
}
pub(crate) fn node_count(&self) -> usize {
self.parents.len()
}
pub(crate) fn selected_edges(&self) -> u32 {
self.edges
}
pub(crate) fn holds(&self, node: GraphNodeId) -> bool {
index_of(node.index()) < self.parents.len()
}
}
#[derive(Clone)]
pub(crate) struct SharedAnalysis {
indexes: Arc<SelectedIndexes>,
partition: Arc<DeclaredModulePartition>,
}
impl SharedAnalysis {
pub(crate) fn indexed(
graph: &CodeGraph,
selection: GraphEdgeSelection,
limits: GraphAnalysisLimits,
) -> Result<Self, GraphAnalysisError> {
let counts = admitted_counts(graph, selection, limits)?;
Self::from_admitted(graph, selection, counts)
}
pub(crate) fn from_admitted(
graph: &CodeGraph,
selection: GraphEdgeSelection,
admitted: (u32, u32),
) -> Result<Self, GraphAnalysisError> {
let indexes = SelectedIndexes::build(graph, selection, admitted);
let partition = partition::derive(graph, &indexes)?;
Ok(Self {
indexes: Arc::new(indexes),
partition: Arc::new(partition),
})
}
pub(crate) fn indexes(&self) -> &SelectedIndexes {
&self.indexes
}
pub(crate) fn partition(&self) -> &DeclaredModulePartition {
&self.partition
}
pub(crate) fn context<'analysis>(
&'analysis self,
graph: &'analysis CodeGraph,
limits: GraphAnalysisLimits,
) -> AnalysisContext<'analysis> {
AnalysisContext {
graph,
limits,
indexes: &self.indexes,
partition: &self.partition,
}
}
}
pub(crate) struct AnalysisContext<'analysis> {
graph: &'analysis CodeGraph,
limits: GraphAnalysisLimits,
indexes: &'analysis SelectedIndexes,
partition: &'analysis DeclaredModulePartition,
}
impl<'analysis> AnalysisContext<'analysis> {
pub(crate) fn graph(&self) -> &'analysis CodeGraph {
self.graph
}
pub(crate) fn limits(&self) -> GraphAnalysisLimits {
self.limits
}
pub(crate) fn indexes(&self) -> &'analysis SelectedIndexes {
self.indexes
}
pub(crate) fn declared_partition(&self) -> &'analysis DeclaredModulePartition {
self.partition
}
}
pub struct GraphAnalysis<'graph> {
graph: &'graph CodeGraph,
selection: GraphEdgeSelection,
limits: GraphAnalysisLimits,
shared: SharedAnalysis,
}
impl<'graph> GraphAnalysis<'graph> {
pub fn new(
graph: &'graph CodeGraph,
selection: GraphEdgeSelection,
limits: GraphAnalysisLimits,
) -> Result<GraphAnalysis<'graph>, GraphAnalysisError> {
Ok(Self {
graph,
selection,
limits,
shared: SharedAnalysis::indexed(graph, selection, limits)?,
})
}
pub fn graph(&self) -> &'graph CodeGraph {
self.graph
}
pub fn selection(&self) -> GraphEdgeSelection {
self.selection
}
pub fn limits(&self) -> GraphAnalysisLimits {
self.limits
}
pub fn declared_partition(&self) -> &DeclaredModulePartition {
self.shared.partition()
}
pub fn neighbors(
&self,
node: GraphNodeId,
depth: u32,
direction: GraphDirection,
) -> Result<Box<[GraphNeighbor]>, GraphAnalysisError> {
traversal::neighbors(&self.context(), node, depth, direction)
}
pub fn path(
&self,
source: GraphNodeId,
target: GraphNodeId,
) -> Result<Option<GraphPath>, GraphAnalysisError> {
traversal::path(&self.context(), (source, target))
}
pub fn subgraph(
&self,
seed: GraphNodeId,
depth: u32,
direction: GraphDirection,
) -> Result<GraphSubgraph, GraphAnalysisError> {
traversal::subgraph(&self.context(), seed, depth, direction)
}
pub fn degree_centrality(&self) -> Box<[DegreeCentrality]> {
degree::degree(&self.context())
}
pub fn betweenness_centrality(
&self,
) -> Result<Box<[BetweennessCentrality]>, GraphAnalysisError> {
betweenness::betweenness(&self.context())
}
pub fn strongly_connected_components(&self) -> GraphComponents {
components::discover(&self.context())
}
pub fn condensation(&self) -> CondensationGraph {
components::condense(&self.context())
}
pub fn divergence(&self) -> GraphDivergence {
divergence::divergence(&self.context())
}
pub fn layout_assist(&self) -> Result<LayoutAssistMetadata, GraphAnalysisError> {
layout::layout_assist(&self.context())
}
fn context(&self) -> AnalysisContext<'_> {
self.shared.context(self.graph, self.limits)
}
}
pub(crate) fn admitted_counts(
graph: &CodeGraph,
selection: GraphEdgeSelection,
limits: GraphAnalysisLimits,
) -> Result<(u32, u32), GraphAnalysisError> {
let nodes = admitted_nodes(graph, limits)?;
let edges = admitted_edges(graph, selection, limits)?;
Ok((nodes, edges))
}
fn selected(graph: &CodeGraph, selection: GraphEdgeSelection) -> impl Iterator<Item = &GraphEdge> {
graph
.edges()
.iter()
.filter(move |edge| selection.allows(edge))
}
fn admitted_nodes(
graph: &CodeGraph,
limits: GraphAnalysisLimits,
) -> Result<u32, GraphAnalysisError> {
let count = position(graph.nodes().len());
match count <= limits.max_nodes() {
true => Ok(count),
false => Err(GraphAnalysisError::NodeLimitExceeded {
count,
limit: limits.max_nodes(),
}),
}
}
fn admitted_edges(
graph: &CodeGraph,
selection: GraphEdgeSelection,
limits: GraphAnalysisLimits,
) -> Result<u32, GraphAnalysisError> {
let count = position(selected(graph, selection).count());
match count <= limits.max_selected_edges() {
true => Ok(count),
false => Err(GraphAnalysisError::SelectedEdgeLimitExceeded {
count,
limit: limits.max_selected_edges(),
}),
}
}
fn entry(edge: GraphEdgeId, node: GraphNodeId) -> SelectedAdjacency {
SelectedAdjacency { edge, node }
}
fn append(lists: &mut [Vec<SelectedAdjacency>], node: GraphNodeId, entry: SelectedAdjacency) {
if let Some(list) = lists.get_mut(index_of(node.index())) {
list.push(entry);
}
}
fn claim(parents: &mut [Option<GraphNodeId>], child: GraphNodeId, parent: GraphNodeId) {
if let Some(slot) = parents.get_mut(index_of(child.index())) {
*slot = Some(parent);
}
}
fn sealed(lists: Vec<Vec<SelectedAdjacency>>) -> Box<[Box<[SelectedAdjacency]>]> {
lists
.into_iter()
.map(Vec::into_boxed_slice)
.collect::<Vec<Box<[SelectedAdjacency]>>>()
.into_boxed_slice()
}
fn borrowed(lists: &[Box<[SelectedAdjacency]>], node: GraphNodeId) -> &[SelectedAdjacency] {
match lists.get(index_of(node.index())) {
Some(list) => list,
None => &[],
}
}