1use std::sync::Arc;
21
22use crate::edge::GraphEdge;
23use crate::graph::CodeGraph;
24use crate::id::{GraphEdgeId, GraphNodeId, index_of, position};
25
26use super::betweenness::{self, BetweennessCentrality};
27use super::components::{self, CondensationGraph, GraphComponents};
28use super::degree::{self, DegreeCentrality};
29use super::divergence::{self, GraphDivergence};
30use super::error::GraphAnalysisError;
31use super::layout::{self, LayoutAssistMetadata};
32use super::limits::GraphAnalysisLimits;
33use super::partition::{self, DeclaredModulePartition};
34use super::selection::GraphEdgeSelection;
35use super::traversal::{self, GraphDirection, GraphNeighbor, GraphPath, GraphSubgraph};
36
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
42pub(crate) struct SelectedAdjacency {
43 edge: GraphEdgeId,
44 node: GraphNodeId,
45}
46
47impl SelectedAdjacency {
48 pub(crate) fn edge(self) -> GraphEdgeId {
50 self.edge
51 }
52
53 pub(crate) fn node(self) -> GraphNodeId {
55 self.node
56 }
57}
58
59pub(crate) struct SelectedIndexes {
61 outgoing: Box<[Box<[SelectedAdjacency]>]>,
62 incoming: Box<[Box<[SelectedAdjacency]>]>,
63 parents: Box<[Option<GraphNodeId>]>,
64 edges: u32,
65}
66
67impl SelectedIndexes {
68 fn build(graph: &CodeGraph, selection: GraphEdgeSelection, admitted: (u32, u32)) -> Self {
76 let (nodes, edges) = admitted;
77 let width = index_of(nodes);
78 let mut outgoing: Vec<Vec<SelectedAdjacency>> = vec![Vec::new(); width];
79 let mut incoming: Vec<Vec<SelectedAdjacency>> = vec![Vec::new(); width];
80 let mut parents: Vec<Option<GraphNodeId>> = vec![None; width];
81 for edge in selected(graph, selection) {
82 append(
83 &mut outgoing,
84 edge.source(),
85 entry(edge.id(), edge.target()),
86 );
87 append(
88 &mut incoming,
89 edge.target(),
90 entry(edge.id(), edge.source()),
91 );
92 }
93 for edge in graph.containment() {
94 claim(&mut parents, edge.child(), edge.parent());
95 }
96 Self {
97 outgoing: sealed(outgoing),
98 incoming: sealed(incoming),
99 parents: parents.into_boxed_slice(),
100 edges,
101 }
102 }
103
104 pub(crate) fn outgoing(&self, node: GraphNodeId) -> &[SelectedAdjacency] {
106 borrowed(&self.outgoing, node)
107 }
108
109 pub(crate) fn incoming(&self, node: GraphNodeId) -> &[SelectedAdjacency] {
111 borrowed(&self.incoming, node)
112 }
113
114 pub(crate) fn parent(&self, node: GraphNodeId) -> Option<GraphNodeId> {
116 self.parents.get(index_of(node.index())).copied().flatten()
117 }
118
119 pub(crate) fn node_count(&self) -> usize {
121 self.parents.len()
122 }
123
124 pub(crate) fn selected_edges(&self) -> u32 {
126 self.edges
127 }
128
129 pub(crate) fn holds(&self, node: GraphNodeId) -> bool {
131 index_of(node.index()) < self.parents.len()
132 }
133}
134
135#[derive(Clone)]
143pub(crate) struct SharedAnalysis {
144 indexes: Arc<SelectedIndexes>,
145 partition: Arc<DeclaredModulePartition>,
146}
147
148impl SharedAnalysis {
149 pub(crate) fn indexed(
151 graph: &CodeGraph,
152 selection: GraphEdgeSelection,
153 limits: GraphAnalysisLimits,
154 ) -> Result<Self, GraphAnalysisError> {
155 let counts = admitted_counts(graph, selection, limits)?;
156 Self::from_admitted(graph, selection, counts)
157 }
158
159 pub(crate) fn from_admitted(
166 graph: &CodeGraph,
167 selection: GraphEdgeSelection,
168 admitted: (u32, u32),
169 ) -> Result<Self, GraphAnalysisError> {
170 let indexes = SelectedIndexes::build(graph, selection, admitted);
171 let partition = partition::derive(graph, &indexes)?;
172 Ok(Self {
173 indexes: Arc::new(indexes),
174 partition: Arc::new(partition),
175 })
176 }
177
178 pub(crate) fn indexes(&self) -> &SelectedIndexes {
180 &self.indexes
181 }
182
183 pub(crate) fn partition(&self) -> &DeclaredModulePartition {
185 &self.partition
186 }
187
188 pub(crate) fn context<'analysis>(
191 &'analysis self,
192 graph: &'analysis CodeGraph,
193 limits: GraphAnalysisLimits,
194 ) -> AnalysisContext<'analysis> {
195 AnalysisContext {
196 graph,
197 limits,
198 indexes: &self.indexes,
199 partition: &self.partition,
200 }
201 }
202}
203
204pub(crate) struct AnalysisContext<'analysis> {
210 graph: &'analysis CodeGraph,
211 limits: GraphAnalysisLimits,
212 indexes: &'analysis SelectedIndexes,
213 partition: &'analysis DeclaredModulePartition,
214}
215
216impl<'analysis> AnalysisContext<'analysis> {
217 pub(crate) fn graph(&self) -> &'analysis CodeGraph {
219 self.graph
220 }
221
222 pub(crate) fn limits(&self) -> GraphAnalysisLimits {
224 self.limits
225 }
226
227 pub(crate) fn indexes(&self) -> &'analysis SelectedIndexes {
229 self.indexes
230 }
231
232 pub(crate) fn declared_partition(&self) -> &'analysis DeclaredModulePartition {
234 self.partition
235 }
236}
237
238pub struct GraphAnalysis<'graph> {
241 graph: &'graph CodeGraph,
242 selection: GraphEdgeSelection,
243 limits: GraphAnalysisLimits,
244 shared: SharedAnalysis,
245}
246
247impl<'graph> GraphAnalysis<'graph> {
248 pub fn new(
255 graph: &'graph CodeGraph,
256 selection: GraphEdgeSelection,
257 limits: GraphAnalysisLimits,
258 ) -> Result<GraphAnalysis<'graph>, GraphAnalysisError> {
259 Ok(Self {
260 graph,
261 selection,
262 limits,
263 shared: SharedAnalysis::indexed(graph, selection, limits)?,
264 })
265 }
266
267 pub fn graph(&self) -> &'graph CodeGraph {
270 self.graph
271 }
272
273 pub fn selection(&self) -> GraphEdgeSelection {
275 self.selection
276 }
277
278 pub fn limits(&self) -> GraphAnalysisLimits {
280 self.limits
281 }
282
283 pub fn declared_partition(&self) -> &DeclaredModulePartition {
285 self.shared.partition()
286 }
287
288 pub fn neighbors(
291 &self,
292 node: GraphNodeId,
293 depth: u32,
294 direction: GraphDirection,
295 ) -> Result<Box<[GraphNeighbor]>, GraphAnalysisError> {
296 traversal::neighbors(&self.context(), node, depth, direction)
297 }
298
299 pub fn path(
302 &self,
303 source: GraphNodeId,
304 target: GraphNodeId,
305 ) -> Result<Option<GraphPath>, GraphAnalysisError> {
306 traversal::path(&self.context(), (source, target))
307 }
308
309 pub fn subgraph(
312 &self,
313 seed: GraphNodeId,
314 depth: u32,
315 direction: GraphDirection,
316 ) -> Result<GraphSubgraph, GraphAnalysisError> {
317 traversal::subgraph(&self.context(), seed, depth, direction)
318 }
319
320 pub fn degree_centrality(&self) -> Box<[DegreeCentrality]> {
323 degree::degree(&self.context())
324 }
325
326 pub fn betweenness_centrality(
329 &self,
330 ) -> Result<Box<[BetweennessCentrality]>, GraphAnalysisError> {
331 betweenness::betweenness(&self.context())
332 }
333
334 pub fn strongly_connected_components(&self) -> GraphComponents {
336 components::discover(&self.context())
337 }
338
339 pub fn condensation(&self) -> CondensationGraph {
342 components::condense(&self.context())
343 }
344
345 pub fn divergence(&self) -> GraphDivergence {
348 divergence::divergence(&self.context())
349 }
350
351 pub fn layout_assist(&self) -> Result<LayoutAssistMetadata, GraphAnalysisError> {
354 layout::layout_assist(&self.context())
355 }
356
357 fn context(&self) -> AnalysisContext<'_> {
359 self.shared.context(self.graph, self.limits)
360 }
361}
362
363pub(crate) fn admitted_counts(
370 graph: &CodeGraph,
371 selection: GraphEdgeSelection,
372 limits: GraphAnalysisLimits,
373) -> Result<(u32, u32), GraphAnalysisError> {
374 let nodes = admitted_nodes(graph, limits)?;
375 let edges = admitted_edges(graph, selection, limits)?;
376 Ok((nodes, edges))
377}
378
379fn selected(graph: &CodeGraph, selection: GraphEdgeSelection) -> impl Iterator<Item = &GraphEdge> {
384 graph
385 .edges()
386 .iter()
387 .filter(move |edge| selection.allows(edge))
388}
389
390fn admitted_nodes(
392 graph: &CodeGraph,
393 limits: GraphAnalysisLimits,
394) -> Result<u32, GraphAnalysisError> {
395 let count = position(graph.nodes().len());
396 match count <= limits.max_nodes() {
397 true => Ok(count),
398 false => Err(GraphAnalysisError::NodeLimitExceeded {
399 count,
400 limit: limits.max_nodes(),
401 }),
402 }
403}
404
405fn admitted_edges(
407 graph: &CodeGraph,
408 selection: GraphEdgeSelection,
409 limits: GraphAnalysisLimits,
410) -> Result<u32, GraphAnalysisError> {
411 let count = position(selected(graph, selection).count());
412 match count <= limits.max_selected_edges() {
413 true => Ok(count),
414 false => Err(GraphAnalysisError::SelectedEdgeLimitExceeded {
415 count,
416 limit: limits.max_selected_edges(),
417 }),
418 }
419}
420
421fn entry(edge: GraphEdgeId, node: GraphNodeId) -> SelectedAdjacency {
423 SelectedAdjacency { edge, node }
424}
425
426fn append(lists: &mut [Vec<SelectedAdjacency>], node: GraphNodeId, entry: SelectedAdjacency) {
428 if let Some(list) = lists.get_mut(index_of(node.index())) {
429 list.push(entry);
430 }
431}
432
433fn claim(parents: &mut [Option<GraphNodeId>], child: GraphNodeId, parent: GraphNodeId) {
435 if let Some(slot) = parents.get_mut(index_of(child.index())) {
436 *slot = Some(parent);
437 }
438}
439
440fn sealed(lists: Vec<Vec<SelectedAdjacency>>) -> Box<[Box<[SelectedAdjacency]>]> {
442 lists
443 .into_iter()
444 .map(Vec::into_boxed_slice)
445 .collect::<Vec<Box<[SelectedAdjacency]>>>()
446 .into_boxed_slice()
447}
448
449fn borrowed(lists: &[Box<[SelectedAdjacency]>], node: GraphNodeId) -> &[SelectedAdjacency] {
452 match lists.get(index_of(node.index())) {
453 Some(list) => list,
454 None => &[],
455 }
456}