Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -342,17 +342,19 @@ private List<CBOClass> getCBOClasses() {
* Ranks candidate class edges using cycle impact, removal flags, and package relationships.
* Only edges present in {@code edgeToRemoveCycleCounts} are considered.
*
* @param classGraph the class dependency graph
* @param edgeToRemoveCycleCounts the candidate edges and their cycle counts
* @param dto the codebase graph containing corresponding package relationships
* @param vertexesToRemove the class vertices selected for removal
* @param packageCycles the package cycles used to count each relationship's participation
* @param classGraph the class dependency graph
* @param edgeToRemoveCycleCounts the candidate edges and their cycle counts
* @param vertexToRemoveCycleCounts
* @param dto the codebase graph containing corresponding package relationships
* @param vertexesToRemove the class vertices selected for removal
* @param packageCycles the package cycles used to count each relationship's participation
* @param packageRelationshipDisharmonies the package relationships selected for removal
* @return the ranked class relationships with assigned priorities
*/
public List<RankedDisharmony> calculateRelationshipCostBenefitValues(
Graph<VertexInfo, DefaultWeightedEdge> classGraph,
Map<DefaultWeightedEdge, Integer> edgeToRemoveCycleCounts,
Map<VertexInfo, Integer> vertexToRemoveCycleCounts,
CodebaseGraphDTO dto,
Set<VertexInfo> vertexesToRemove,
Map<VertexInfo, AsSubgraph<VertexInfo, DefaultWeightedEdge>> packageCycles,
Expand All @@ -372,16 +374,19 @@ public List<RankedDisharmony> calculateRelationshipCostBenefitValues(
VertexInfo edgeSource = classGraph.getEdgeSource(edge);
VertexInfo edgeTarget = classGraph.getEdgeTarget(edge);

boolean sourceNodeShouldBeRemoved = vertexesToRemove.contains(edgeSource);
boolean targetNodeShouldBeRemoved = vertexesToRemove.contains(edgeTarget);
int sourceNodeCycleCount =
vertexesToRemove.contains(edgeSource) ? vertexToRemoveCycleCounts.get(edgeSource) : 0;

int targetNodeCycleCount =
vertexesToRemove.contains(edgeTarget) ? vertexToRemoveCycleCounts.get(edgeTarget) : 0;

RankedDisharmony edgeThatNeedsToBeRemoved = new RankedDisharmony(
edgeSource.getName(),
edge,
edgeToRemoveCycleCounts.get(edge),
(int) classGraph.getEdgeWeight(edge),
sourceNodeShouldBeRemoved,
targetNodeShouldBeRemoved,
sourceNodeCycleCount,
targetNodeCycleCount,
getPackageCycleCount(edgeSource, edgeTarget, dto, packageCycles),
packageRelationshipShouldBeRemoved(edgeSource, edgeTarget, dto, packageEdgesToRemove));

Expand Down Expand Up @@ -482,9 +487,9 @@ static void sortEdgesThatNeedToBeRemoved(List<RankedDisharmony> rankedDisharmoni
// then by package cycle count, with classes in more package cycles bubbling to the top
.thenComparingInt(rankedDisharmony -> -1 * rankedDisharmony.getPackageCycleCount())
// then if the source node is in the list of nodes to be removed
.thenComparingInt(rankedDisharmony -> -1 * rankedDisharmony.getSourceNodeShouldBeRemoved())
.thenComparingInt(rankedDisharmony -> -1 * rankedDisharmony.getSourceNodeCycleCount())
// then if the target node is in the list of nodes to be removed
.thenComparingInt(rankedDisharmony -> -1 * rankedDisharmony.getTargetNodeShouldBeRemoved()));
.thenComparingInt(rankedDisharmony -> -1 * rankedDisharmony.getTargetNodeCycleCount()));
}

private String getFileName(RuleViolation violation) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ public class RankedDisharmony {

private DefaultWeightedEdge edge;
private Integer cycleCount;
private int sourceNodeShouldBeRemoved;
private int targetNodeShouldBeRemoved;
private int sourceNodeCycleCount;
private int targetNodeCycleCount;
private String edgeTargetClass;
private Integer packageCycleCount;
private boolean packageRelationshipShouldBeRemoved;
Expand Down Expand Up @@ -107,8 +107,8 @@ public RankedDisharmony(
DefaultWeightedEdge edge,
int cycleCount,
int weight,
boolean sourceNodeShouldBeRemoved,
boolean targetNodeShouldBeRemoved,
int sourceNodeCycleCount,
int targetNodeCycleCount,
int packageCycleCount,
boolean packageRelationshipShouldBeRemoved) {

Expand All @@ -118,7 +118,7 @@ public RankedDisharmony(
this.packageCycleCount = packageCycleCount;
this.packageRelationshipShouldBeRemoved = packageRelationshipShouldBeRemoved;
effortRank = weight;
this.sourceNodeShouldBeRemoved = sourceNodeShouldBeRemoved ? 1 : 0;
this.targetNodeShouldBeRemoved = targetNodeShouldBeRemoved ? 1 : 0;
this.sourceNodeCycleCount = sourceNodeCycleCount;
this.targetNodeCycleCount = targetNodeCycleCount;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,44 @@ void testCostBenefitCalculation() throws IOException, GitAPIException, Interrupt
Assertions.assertEquals(2, disharmonies.get(1).getPriority().intValue());
}

@Test
void sortEdgesThatNeedToBeRemoved_prioritizesHigherSourceNodeCycleCounts() {
RankedDisharmony higherSourceCycleCount =
new RankedDisharmony("HigherSource", new DefaultWeightedEdge(), 5, 1, 5, 0, 0, false);
RankedDisharmony higherTargetCycleCount =
new RankedDisharmony("HigherTarget", new DefaultWeightedEdge(), 5, 1, 1, 9, 0, false);
RankedDisharmony lowerSourceCycleCount =
new RankedDisharmony("LowerSource", new DefaultWeightedEdge(), 5, 1, 0, 7, 0, false);

List<RankedDisharmony> rankedDisharmonies =
new ArrayList<>(List.of(higherTargetCycleCount, higherSourceCycleCount, lowerSourceCycleCount));

CostBenefitCalculator.sortEdgesThatNeedToBeRemoved(rankedDisharmonies);

Assertions.assertEquals(
List.of("HigherSource", "HigherTarget", "LowerSource"),
rankedDisharmonies.stream().map(RankedDisharmony::getClassName).toList());
}

@Test
void sortEdgesThatNeedToBeRemoved_prioritizesHigherTargetNodeCycleCountsWhenSourceCountsTie() {
RankedDisharmony higherTargetCycleCount =
new RankedDisharmony("HigherTarget", new DefaultWeightedEdge(), 5, 1, 2, 6, 0, false);
RankedDisharmony lowerTargetCycleCount =
new RankedDisharmony("LowerTarget", new DefaultWeightedEdge(), 5, 1, 2, 1, 0, false);
RankedDisharmony lowerSourceCycleCount =
new RankedDisharmony("LowerSource", new DefaultWeightedEdge(), 5, 1, 0, 7, 0, false);

List<RankedDisharmony> rankedDisharmonies =
new ArrayList<>(List.of(lowerTargetCycleCount, higherTargetCycleCount, lowerSourceCycleCount));

CostBenefitCalculator.sortEdgesThatNeedToBeRemoved(rankedDisharmonies);

Assertions.assertEquals(
List.of("HigherTarget", "LowerTarget", "LowerSource"),
rankedDisharmonies.stream().map(RankedDisharmony::getClassName).toList());
}

/** Verifies candidate edge priorities, cycle counts, effort ranks, and endpoint removal flags. */
@Test
void calculateRelationshipCostBenefitValues_filtersMissingLogInfoAndAssignsPriority() throws Exception {
Expand Down Expand Up @@ -157,6 +195,9 @@ void calculateRelationshipCostBenefitValues_filtersMissingLogInfoAndAssignsPrior

Set<VertexInfo> vertexesToRemove =
new HashSet<>(Arrays.asList(VertexInfo.forName("ClassA"), VertexInfo.forName("ClassD")));
Map<VertexInfo, Integer> vertexToRemoveCycleCounts = new HashMap<>();
vertexToRemoveCycleCounts.put(VertexInfo.forName("ClassA"), 1);
vertexToRemoveCycleCounts.put(VertexInfo.forName("ClassD"), 1);

ScmLogInfo scmLogInfo1 = new ScmLogInfo(hudsonPath + "ClassA.java", null, 1, 2, 3);
scmLogInfo1.setChangePronenessRank(4);
Expand All @@ -174,24 +215,30 @@ void calculateRelationshipCostBenefitValues_filtersMissingLogInfoAndAssignsPrior
.thenReturn(new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class));

List<RankedDisharmony> disharmonies = costBenefitCalculator.calculateRelationshipCostBenefitValues(
classGraph, edgeToRemoveCycleCounts, dto, vertexesToRemove, Collections.emptyMap(), List.of());
classGraph,
edgeToRemoveCycleCounts,
vertexToRemoveCycleCounts,
dto,
vertexesToRemove,
Collections.emptyMap(),
List.of());

Assertions.assertEquals(2, disharmonies.size());

RankedDisharmony classA = disharmonies.get(0);
Assertions.assertEquals("ClassA", classA.getClassName());
Assertions.assertEquals(5, classA.getCycleCount().intValue());
Assertions.assertEquals(4, classA.getEffortRank().intValue());
Assertions.assertEquals(1, classA.getSourceNodeShouldBeRemoved());
Assertions.assertEquals(0, classA.getTargetNodeShouldBeRemoved());
Assertions.assertEquals(1, classA.getSourceNodeCycleCount());
Assertions.assertEquals(0, classA.getTargetNodeCycleCount());
Assertions.assertEquals(1, classA.getPriority().intValue());

RankedDisharmony classC = disharmonies.get(1);
Assertions.assertEquals("ClassC", classC.getClassName());
Assertions.assertEquals(3, classC.getCycleCount().intValue());
Assertions.assertEquals(2, classC.getEffortRank().intValue());
Assertions.assertEquals(0, classC.getSourceNodeShouldBeRemoved());
Assertions.assertEquals(1, classC.getTargetNodeShouldBeRemoved());
Assertions.assertEquals(0, classC.getSourceNodeCycleCount());
Assertions.assertEquals(1, classC.getTargetNodeCycleCount());
Assertions.assertEquals(2, classC.getPriority().intValue());
Assertions.assertEquals(0, classC.getPackageCycleCount());
}
Expand Down Expand Up @@ -232,6 +279,9 @@ void calculateRelationshipCostBenefitValues_prefersHigherChangePronenessRank() t

Set<VertexInfo> vertexesToRemove =
new HashSet<>(Arrays.asList(VertexInfo.forName("Alpha"), VertexInfo.forName("Gamma")));
Map<VertexInfo, Integer> vertexToRemoveCycleCounts = new HashMap<>();
vertexToRemoveCycleCounts.put(VertexInfo.forName("Alpha"), 2);
vertexToRemoveCycleCounts.put(VertexInfo.forName("Gamma"), 1);

ScmLogInfo scmLogInfo1 = new ScmLogInfo(faceletsPath + "Alpha.java", null, 2, 3, 1);
scmLogInfo1.setChangePronenessRank(2);
Expand All @@ -249,7 +299,13 @@ void calculateRelationshipCostBenefitValues_prefersHigherChangePronenessRank() t
.thenReturn(new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class));

List<RankedDisharmony> disharmonies = costBenefitCalculator.calculateRelationshipCostBenefitValues(
classGraph, edgeToRemoveCycleCounts, dto, vertexesToRemove, Collections.emptyMap(), List.of());
classGraph,
edgeToRemoveCycleCounts,
vertexToRemoveCycleCounts,
dto,
vertexesToRemove,
Collections.emptyMap(),
List.of());

Assertions.assertEquals(2, disharmonies.size());
Assertions.assertEquals(0, disharmonies.get(0).getPackageCycleCount());
Expand Down Expand Up @@ -316,7 +372,13 @@ void calculateRelationshipCostBenefitValues_packageCycleCountReflectsRelationshi
new CostBenefitCalculator(git.getRepository().getDirectory().getParent(), new HashMap<>())) {

List<RankedDisharmony> disharmonies = costBenefitCalculator.calculateRelationshipCostBenefitValues(
classGraph, edgeToRemoveCycleCounts, dto, Collections.emptySet(), packageCycles, List.of());
classGraph,
edgeToRemoveCycleCounts,
Map.of(),
dto,
Collections.emptySet(),
packageCycles,
List.of());

Map<DefaultWeightedEdge, RankedDisharmony> disharmoniesByEdge = new HashMap<>();
disharmonies.forEach(d -> disharmoniesByEdge.put(d.getEdge(), d));
Expand Down Expand Up @@ -349,7 +411,7 @@ void calculateRelationshipCostBenefitValues_flagsClassEdgesWhosePackageRelations
packageGraph.addEdge(VertexInfo.forName("pkgb"), VertexInfo.forName("pkgc"));

// Only pkga -> pkgb is flagged as a package relationship that needs to be removed
RankedDisharmony packageEdgeToRemove = new RankedDisharmony("pkga", pkgaToPkgb, 1, 1, false, false, 0, false);
RankedDisharmony packageEdgeToRemove = new RankedDisharmony("pkga", pkgaToPkgb, 1, 1, 0, 0, 0, false);
List<RankedDisharmony> packageRelationshipDisharmonies = List.of(packageEdgeToRemove);

SimpleDirectedWeightedGraph<VertexInfo, DefaultWeightedEdge> classGraph =
Expand Down Expand Up @@ -383,6 +445,7 @@ void calculateRelationshipCostBenefitValues_flagsClassEdgesWhosePackageRelations
List<RankedDisharmony> disharmonies = costBenefitCalculator.calculateRelationshipCostBenefitValues(
classGraph,
edgeToRemoveCycleCounts,
Map.of(),
dto,
Collections.emptySet(),
Collections.emptyMap(),
Expand Down Expand Up @@ -420,26 +483,21 @@ void sortEdgesThatNeedToBeRemoved_sortsByMultipleCriteria() {
// then packageRelationshipShouldBeRemoved desc (true before false), then packageCycleCount desc,
// then sourceRemoved desc, then targetRemoved desc
// cycle=5, source=0, target=0, packageCycleCount=6, packageRelationshipShouldBeRemoved=false
RankedDisharmony disharmony1 =
new RankedDisharmony("Class1", new DefaultWeightedEdge(), 5, 1, false, false, 6, false);
RankedDisharmony disharmony1 = new RankedDisharmony("Class1", new DefaultWeightedEdge(), 5, 1, 0, 0, 6, false);

// cycle=5, source=1, target=0, packageCycleCount=1, packageRelationshipShouldBeRemoved=false
RankedDisharmony disharmony2 =
new RankedDisharmony("Class2", new DefaultWeightedEdge(), 5, 1, true, false, 1, false);
RankedDisharmony disharmony2 = new RankedDisharmony("Class2", new DefaultWeightedEdge(), 5, 1, 1, 0, 1, false);

// cycle=3, source=0, target=1, packageCycleCount=5, packageRelationshipShouldBeRemoved=false
RankedDisharmony disharmony3 =
new RankedDisharmony("Class3", new DefaultWeightedEdge(), 3, 1, false, true, 5, false);
RankedDisharmony disharmony3 = new RankedDisharmony("Class3", new DefaultWeightedEdge(), 3, 1, 0, 1, 5, false);

// cycle=3, source=0, target=0, packageCycleCount=0, packageRelationshipShouldBeRemoved=false
RankedDisharmony disharmony4 =
new RankedDisharmony("Class4", new DefaultWeightedEdge(), 3, 1, false, false, 0, false);
RankedDisharmony disharmony4 = new RankedDisharmony("Class4", new DefaultWeightedEdge(), 3, 1, 0, 0, 0, false);

// cycle=3, source=0, target=0, packageCycleCount=2, packageRelationshipShouldBeRemoved=true
// lower packageCycleCount than disharmony3, but packageRelationshipShouldBeRemoved=true must still
// bubble it ahead of disharmony3, proving the new sort clause is applied before packageCycleCount
RankedDisharmony disharmony5 =
new RankedDisharmony("Class5", new DefaultWeightedEdge(), 3, 1, false, false, 2, true);
RankedDisharmony disharmony5 = new RankedDisharmony("Class5", new DefaultWeightedEdge(), 3, 1, 0, 0, 2, true);

List<RankedDisharmony> disharmonies =
Arrays.asList(disharmony4, disharmony2, disharmony1, disharmony3, disharmony5);
Expand All @@ -456,25 +514,25 @@ void sortEdgesThatNeedToBeRemoved_sortsByMultipleCriteria() {
System.out.println(disharmony.getClassName() + " "
+ disharmony.getCycleCount() + " "
+ disharmony.getEffortRank() + " "
+ disharmony.getSourceNodeShouldBeRemoved() + " "
+ disharmony.getTargetNodeShouldBeRemoved() + " "
+ disharmony.getSourceNodeCycleCount() + " "
+ disharmony.getTargetNodeCycleCount() + " "
+ disharmony.getPackageCycleCount());
}

RankedDisharmony orderedDisharmony0 = disharmonies.get(0);
Assertions.assertEquals("Class1", orderedDisharmony0.getClassName());
Assertions.assertEquals(5, orderedDisharmony0.getCycleCount().intValue());
Assertions.assertEquals(1, orderedDisharmony0.getEffortRank().intValue());
Assertions.assertEquals(0, orderedDisharmony0.getSourceNodeShouldBeRemoved());
Assertions.assertEquals(0, orderedDisharmony0.getTargetNodeShouldBeRemoved());
Assertions.assertEquals(0, orderedDisharmony0.getSourceNodeCycleCount());
Assertions.assertEquals(0, orderedDisharmony0.getTargetNodeCycleCount());
Assertions.assertEquals(6, orderedDisharmony0.getPackageCycleCount());

RankedDisharmony orderedDisharmony1 = disharmonies.get(1);
Assertions.assertEquals("Class2", orderedDisharmony1.getClassName());
Assertions.assertEquals(5, orderedDisharmony1.getCycleCount().intValue());
Assertions.assertEquals(1, orderedDisharmony1.getEffortRank().intValue());
Assertions.assertEquals(1, orderedDisharmony1.getSourceNodeShouldBeRemoved());
Assertions.assertEquals(0, orderedDisharmony1.getTargetNodeShouldBeRemoved());
Assertions.assertEquals(1, orderedDisharmony1.getSourceNodeCycleCount());
Assertions.assertEquals(0, orderedDisharmony1.getTargetNodeCycleCount());
Assertions.assertEquals(1, orderedDisharmony1.getPackageCycleCount());

// Class5 has a lower packageCycleCount than Class3, but packageRelationshipShouldBeRemoved=true
Expand All @@ -490,17 +548,17 @@ void sortEdgesThatNeedToBeRemoved_sortsByMultipleCriteria() {
Assertions.assertEquals("Class3", orderedDisharmony3.getClassName());
Assertions.assertEquals(3, orderedDisharmony3.getCycleCount().intValue());
Assertions.assertEquals(1, orderedDisharmony3.getEffortRank().intValue());
Assertions.assertEquals(0, orderedDisharmony3.getSourceNodeShouldBeRemoved());
Assertions.assertEquals(1, orderedDisharmony3.getTargetNodeShouldBeRemoved());
Assertions.assertEquals(0, orderedDisharmony3.getSourceNodeCycleCount());
Assertions.assertEquals(1, orderedDisharmony3.getTargetNodeCycleCount());
Assertions.assertFalse(orderedDisharmony3.isPackageRelationshipShouldBeRemoved());
Assertions.assertEquals(5, orderedDisharmony3.getPackageCycleCount());

RankedDisharmony orderedDisharmony4 = disharmonies.get(4);
Assertions.assertEquals("Class4", orderedDisharmony4.getClassName());
Assertions.assertEquals(1, orderedDisharmony4.getEffortRank().intValue());
Assertions.assertEquals(3, orderedDisharmony4.getCycleCount().intValue());
Assertions.assertEquals(0, orderedDisharmony4.getSourceNodeShouldBeRemoved());
Assertions.assertEquals(0, orderedDisharmony4.getTargetNodeShouldBeRemoved());
Assertions.assertEquals(0, orderedDisharmony4.getSourceNodeCycleCount());
Assertions.assertEquals(0, orderedDisharmony4.getTargetNodeCycleCount());
Assertions.assertEquals(0, orderedDisharmony4.getPackageCycleCount());
}

Expand Down
Loading
Loading