2727import org .hjug .metrics .DisharmonyRanker ;
2828import org .hjug .metrics .rules .CBORule ;
2929import org .jgrapht .Graph ;
30+ import org .jgrapht .graph .AsSubgraph ;
3031import org .jgrapht .graph .DefaultWeightedEdge ;
3132
3233@ Slf4j
@@ -330,9 +331,15 @@ public List<RankedDisharmony> calculateRelationshipCostBenefitValues(
330331 Graph <String , DefaultWeightedEdge > classGraph ,
331332 Map <DefaultWeightedEdge , Integer > edgeToRemoveCycleCounts ,
332333 CodebaseGraphDTO dto ,
333- Set <String > vertexesToRemove ) {
334+ Set <String > vertexesToRemove ,
335+ Map <String , AsSubgraph <String , DefaultWeightedEdge >> packageCycles ,
336+ List <RankedDisharmony > packageRelationshipDisharmonies ) {
334337 List <RankedDisharmony > edgesThatNeedToBeRemoved = new ArrayList <>();
335338
339+ Set <DefaultWeightedEdge > packageEdgesToRemove = packageRelationshipDisharmonies .stream ()
340+ .map (RankedDisharmony ::getEdge )
341+ .collect (Collectors .toSet ());
342+
336343 for (DefaultWeightedEdge edge : classGraph .edgeSet ()) {
337344 // shouldn't have to check for null edges & counts :-(
338345 if (null == edge || null == edgeToRemoveCycleCounts .get (edge )) continue ;
@@ -350,8 +357,8 @@ public List<RankedDisharmony> calculateRelationshipCostBenefitValues(
350357 (int ) classGraph .getEdgeWeight (edge ),
351358 sourceNodeShouldBeRemoved ,
352359 targetNodeShouldBeRemoved ,
353- dto . getClassDisharmonyCountForClass (edgeSource ),
354- dto . getClassDisharmonyCountForClass ( edgeTarget ));
360+ getPackageCycleCount (edgeSource , edgeTarget , dto , packageCycles ),
361+ packageRelationshipShouldBeRemoved ( edgeSource , edgeTarget , dto , packageEdgesToRemove ));
355362
356363 edgesThatNeedToBeRemoved .add (edgeThatNeedsToBeRemoved );
357364 }
@@ -376,18 +383,69 @@ public List<RankedDisharmony> calculateRelationshipCostBenefitValues(
376383 return edgesThatNeedToBeRemoved ;
377384 }
378385
386+ /**
387+ * Counts how many package cycles contain the package-level relationship corresponding to the given class (or
388+ * package) edge - i.e. cycles where the edge between the source's and target's packages is itself part of the
389+ * cycle, not merely cycles that happen to contain one of the endpoints.
390+ */
391+ private static int getPackageCycleCount (
392+ String edgeSource ,
393+ String edgeTarget ,
394+ CodebaseGraphDTO dto ,
395+ Map <String , AsSubgraph <String , DefaultWeightedEdge >> packageCycles ) {
396+ String sourcePackage = toPackageName (edgeSource , dto );
397+ String targetPackage = toPackageName (edgeTarget , dto );
398+
399+ int packageCycleCount = 0 ;
400+ for (AsSubgraph <String , DefaultWeightedEdge > packageCycle : packageCycles .values ()) {
401+ if (packageCycle .containsEdge (sourcePackage , targetPackage )) {
402+ packageCycleCount ++;
403+ }
404+ }
405+ return packageCycleCount ;
406+ }
407+
408+ /**
409+ * Determines whether the package-level relationship corresponding to the given class (or package) edge is
410+ * itself one of the package edges selected for removal, based on membership in the already-computed package
411+ * relationship disharmonies.
412+ */
413+ private static boolean packageRelationshipShouldBeRemoved (
414+ String edgeSource , String edgeTarget , CodebaseGraphDTO dto , Set <DefaultWeightedEdge > packageEdgesToRemove ) {
415+ String sourcePackage = toPackageName (edgeSource , dto );
416+ String targetPackage = toPackageName (edgeTarget , dto );
417+ DefaultWeightedEdge packageEdge = dto .getPackageReferencesGraph ().getEdge (sourcePackage , targetPackage );
418+ return packageEdge != null && packageEdgesToRemove .contains (packageEdge );
419+ }
420+
421+ /**
422+ * The vertex may already be a package name (when classGraph is actually a package graph) or a fully-qualified
423+ * class name, in which case the containing package is derived from it.
424+ */
425+ private static String toPackageName (String vertex , CodebaseGraphDTO dto ) {
426+ if (dto .getPackageReferencesGraph ().containsVertex (vertex )) {
427+ return vertex ;
428+ } else if (vertex .contains ("." )) {
429+ return vertex .substring (0 , vertex .lastIndexOf ('.' ));
430+ } else {
431+ return "" ;
432+ }
433+ }
434+
379435 static void sortEdgesThatNeedToBeRemoved (List <RankedDisharmony > rankedDisharmonies ) {
380436 // Sort by impact value
381437 // Order by cycle count reversed (highest count bubbles to the top)
382438 rankedDisharmonies .sort (Comparator .comparingInt (RankedDisharmony ::getCycleCount )
383439 .reversed ()
384440 // then by weight, with lowest weight edges bubbling to the top
385441 .thenComparingInt (RankedDisharmony ::getEffortRank )
386- // then by disharmony count
387- .thenComparingInt (RankedDisharmony ::getEdgeSourceDisharmonyCount )
388- .thenComparingInt (RankedDisharmony ::getEdgeTargetDisharmonyCount )
389- // then if the source node is in the list of nodes to be removed
442+ // then by whether the underlying package relationship should also be removed, true before false
390443 // multiplying by -1 reverses the sort order (reverse doesn't work in chained comparators)
444+ .thenComparingInt (
445+ rankedDisharmony -> -1 * (rankedDisharmony .isPackageRelationshipShouldBeRemoved () ? 1 : 0 ))
446+ // then by package cycle count, with classes in more package cycles bubbling to the top
447+ .thenComparingInt (rankedDisharmony -> -1 * rankedDisharmony .getPackageCycleCount ())
448+ // then if the source node is in the list of nodes to be removed
391449 .thenComparingInt (rankedDisharmony -> -1 * rankedDisharmony .getSourceNodeShouldBeRemoved ())
392450 // then if the target node is in the list of nodes to be removed
393451 .thenComparingInt (rankedDisharmony -> -1 * rankedDisharmony .getTargetNodeShouldBeRemoved ()));
0 commit comments