Skip to content
Merged
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 @@ -62,18 +62,14 @@ public CodebaseGraphDTO buildGraph(String repositoryPath, String repositoryRoot,
new GraphMetricsCollector(classReferencesGraph, packageReferencesGraph);
MetricsCollectingVisitor metricsVisitor = new MetricsCollectingVisitor(metricsCollector);

String testDirPattern = config.getTestSourceDirectory() != null
? config.getTestSourceDirectory().replace('\\', '/')
: "";

try (Stream<Path> pathStream = Files.walk(Path.of(srcDirectory.getAbsolutePath()))) {
Stream<Path> filteredStream =
pathStream.filter(file -> file.toString().endsWith(".java"));
if (config.isExcludeTests()
&& config.getTestSourceDirectory() != null
&& !config.getTestSourceDirectory().isEmpty()) {
filteredStream = filteredStream.filter(
file -> !file.toString().replace('\\', '/').contains(testDirPattern));
filteredStream = filteredStream.filter(file -> !SourceFileGraphBuilder.isInConfiguredDirectory(
file, config.getTestSourceDirectory()));
}
List<Path> list = filteredStream.collect(Collectors.toList());
log.info("JavaSourceFileGraphBuilder: walking {} Java files under {}", list.size(), repositoryPath);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,18 +72,14 @@ public CodebaseGraphDTO buildGraph(String repositoryPath, String repositoryRoot,
new GraphMetricsCollector(classReferencesGraph, packageReferencesGraph);
KotlinMetricsCollectingVisitor metricsVisitor = new KotlinMetricsCollectingVisitor(metricsCollector);

String testDirPattern = config.getTestSourceDirectory() != null
? config.getTestSourceDirectory().replace('\\', '/')
: "";

try (Stream<Path> pathStream = Files.walk(Path.of(srcDirectory.getAbsolutePath()))) {
Stream<Path> filteredStream = pathStream.filter(
file -> file.toString().endsWith(".kt") || file.toString().endsWith(".kts"));
if (config.isExcludeTests()
&& config.getTestSourceDirectory() != null
&& !config.getTestSourceDirectory().isEmpty()) {
filteredStream = filteredStream.filter(
file -> !file.toString().replace('\\', '/').contains(testDirPattern));
filteredStream = filteredStream.filter(file -> !SourceFileGraphBuilder.isInConfiguredDirectory(
file, config.getTestSourceDirectory()));
}
List<Path> list = filteredStream.collect(Collectors.toList());

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.hjug.graphbuilder.graphbuilder;

import java.io.IOException;
import java.nio.file.Path;
import org.hjug.graphbuilder.CodebaseGraphDTO;
import org.hjug.graphbuilder.GraphBuilderConfig;

Expand All @@ -10,6 +11,12 @@
*/
public interface SourceFileGraphBuilder {

static boolean isInConfiguredDirectory(Path file, String directory) {
String normalizedFile = "/" + file.toString().replace('\\', '/') + "/";
String normalizedDirectory = directory.replace('\\', '/').replaceAll("^/+|/+$", "");
return normalizedFile.contains("/" + normalizedDirectory + "/");
}

/**
* Build a {@link CodebaseGraphDTO} representing class/package dependency
* graphs and disharmony metrics for the given source repository.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.*;
import lombok.AccessLevel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Setter;

/**
Expand All @@ -24,6 +25,7 @@ public class MethodMetrics {
private String signature;

@Setter(AccessLevel.NONE)
@EqualsAndHashCode.Exclude
private boolean finalized;

@Setter(AccessLevel.NONE)
Expand All @@ -37,8 +39,13 @@ public class MethodMetrics {
@Setter(AccessLevel.NONE)
private Set<String> accessedVariables = new HashSet<>();

@Setter(AccessLevel.NONE)
private Set<String> accessedForeignClasses = new HashSet<>();

@Setter(AccessLevel.NONE)
private Set<String> accessedForeignAttributes = new HashSet<>();

@Setter(AccessLevel.NONE)
private Set<String> accessedOwnAttributes = new HashSet<>();

@Setter(AccessLevel.NONE)
Expand Down Expand Up @@ -204,6 +211,7 @@ public void addTypeParameterFqn(String fqn) {
// --- Collection getters: lazy-cached unmodifiable views ----------------

@Setter(AccessLevel.NONE)
@EqualsAndHashCode.Exclude
private Set<String> accessedVariablesView;

public Set<String> getAccessedVariables() {
Expand All @@ -216,6 +224,7 @@ public Set<String> getAccessedVariables() {
}

@Setter(AccessLevel.NONE)
@EqualsAndHashCode.Exclude
private Set<String> accessedForeignClassesView;

public Set<String> getAccessedForeignClasses() {
Expand All @@ -228,6 +237,7 @@ public Set<String> getAccessedForeignClasses() {
}

@Setter(AccessLevel.NONE)
@EqualsAndHashCode.Exclude
private Set<String> accessedForeignAttributesView;

public Set<String> getAccessedForeignAttributes() {
Expand All @@ -240,6 +250,7 @@ public Set<String> getAccessedForeignAttributes() {
}

@Setter(AccessLevel.NONE)
@EqualsAndHashCode.Exclude
private Set<String> accessedOwnAttributesView;

public Set<String> getAccessedOwnAttributes() {
Expand All @@ -252,6 +263,7 @@ public Set<String> getAccessedOwnAttributes() {
}

@Setter(AccessLevel.NONE)
@EqualsAndHashCode.Exclude
private Set<String> calledForeignMethodsView;

public Set<String> getCalledForeignMethods() {
Expand All @@ -264,6 +276,7 @@ public Set<String> getCalledForeignMethods() {
}

@Setter(AccessLevel.NONE)
@EqualsAndHashCode.Exclude
private Set<String> calledForeignMethodClassesView;

public Set<String> getCalledForeignMethodClasses() {
Expand All @@ -276,6 +289,7 @@ public Set<String> getCalledForeignMethodClasses() {
}

@Setter(AccessLevel.NONE)
@EqualsAndHashCode.Exclude
private Set<String> changingMethodsView;

public Set<String> getChangingMethods() {
Expand All @@ -288,6 +302,7 @@ public Set<String> getChangingMethods() {
}

@Setter(AccessLevel.NONE)
@EqualsAndHashCode.Exclude
private Set<String> changingClassesView;

public Set<String> getChangingClasses() {
Expand All @@ -300,6 +315,7 @@ public Set<String> getChangingClasses() {
}

@Setter(AccessLevel.NONE)
@EqualsAndHashCode.Exclude
private Set<String> typeParameterFqnsView;

public Set<String> getTypeParameterFqns() {
Expand All @@ -312,6 +328,7 @@ public Set<String> getTypeParameterFqns() {
}

@Setter(AccessLevel.NONE)
@EqualsAndHashCode.Exclude
private List<String> normalizedBodyLinesView;

public List<String> getNormalizedBodyLines() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@
*/
class SourceFileGraphBuilderTestPathExclusionTest {

@Test
void directoryMatchingDoesNotExcludeLongerDirectoryNames() {
assertTrue(SourceFileGraphBuilder.isInConfiguredDirectory(
Path.of("project/src/test/java/Example.java"), "src/test"));
assertFalse(SourceFileGraphBuilder.isInConfiguredDirectory(
Path.of("project/src/testFixtures/java/Example.java"), "src/test"));
}

@DisplayName("Java builder excludes test files with forward-slash testSourceDirectory on Windows-style paths")
@Test
void javaBuilder_excludesTestsWithForwardSlashPatternOnWindowsPaths(@TempDir Path tempDir) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
Expand All @@ -25,6 +26,31 @@
*/
class ClassMetricsFinalizationImmutabilityTest {

@Test
void methodMetricsDoesNotExposeBackingCollectionSetters() {
Set<String> forbiddenSetters = Set.of(
"setAccessedForeignClasses", "setAccessedForeignAttributes", "setAccessedOwnAttributes");

assertTrue(Stream.of(MethodMetrics.class.getMethods())
.map(java.lang.reflect.Method::getName)
.noneMatch(forbiddenSetters::contains));
}

@Test
void methodMetricsEqualityDoesNotChangeWhenViewsAreCachedOrMetricsAreFrozen() {
MethodMetrics first = new MethodMetrics("method", "method()V");
MethodMetrics second = new MethodMetrics("method", "method()V");
first.addAccessedForeignClass("com.example.Foreign");
second.addAccessedForeignClass("com.example.Foreign");

int initialHashCode = first.hashCode();
first.getAccessedForeignClasses();
first.freeze();

assertEquals(second, first);
assertEquals(initialHashCode, first.hashCode());
}

private static ClassMetrics newPopulated(String fqn) {
ClassMetrics m = new ClassMetrics(fqn);
m.setClassName(fqn.substring(fqn.lastIndexOf('.') + 1));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.hjug.graphbuilder.GraphDependencyCollector;
import org.jgrapht.Graph;
import org.jgrapht.graph.DefaultDirectedWeightedGraph;
Expand Down Expand Up @@ -214,35 +216,24 @@ private void compareProjectEdges(
.collect(java.util.stream.Collectors.toMap(
v -> v.substring(kotlinPkgPrefix.length()), v -> v, (a, b) -> a));

// Compare edge pairs and weights
for (var entry : javaNormalized.entrySet()) {
String normV = entry.getKey();
String javaV = entry.getValue();

for (String normT : javaNormalized.keySet()) {
String javaT = javaNormalized.get(normT);
DefaultWeightedEdge javaEdge = javaGraph.getEdge(javaV, javaT);
if (javaEdge != null) {
double javaWeight = javaGraph.getEdgeWeight(javaEdge);

// Find corresponding Kotlin vertices
String kotlinV = kotlinNormalized.get(normV);
String kotlinT = kotlinNormalized.get(normT);

assertNotNull(kotlinV, "Kotlin vertex missing for normalized: " + normV);
assertNotNull(kotlinT, "Kotlin vertex missing for normalized: " + normT);

DefaultWeightedEdge kotlinEdge = kotlinGraph.getEdge(kotlinV, kotlinT);
assertNotNull(kotlinEdge, "Kotlin edge missing for: " + normV + " -> " + normT);

double kotlinWeight = kotlinGraph.getEdgeWeight(kotlinEdge);
assertEquals(
javaWeight,
kotlinWeight,
"Edge weight mismatch for " + normV + " -> " + normT + ": Java=" + javaWeight + " Kotlin="
+ kotlinWeight);
}
assertEquals(
normalizedEdges(javaGraph, javaNormalized),
normalizedEdges(kotlinGraph, kotlinNormalized),
"Java and Kotlin project edges and weights should match exactly");
}

private Map<String, Double> normalizedEdges(
Graph<String, DefaultWeightedEdge> graph, Map<String, String> normalizedVertices) {
Map<String, Double> edges = new HashMap<>();
Map<String, String> vertexNames = new HashMap<>();
normalizedVertices.forEach((normalized, vertex) -> vertexNames.put(vertex, normalized));
for (DefaultWeightedEdge edge : graph.edgeSet()) {
String source = vertexNames.get(graph.getEdgeSource(edge));
String target = vertexNames.get(graph.getEdgeTarget(edge));
if (source != null && target != null) {
edges.put(source + " -> " + target, graph.getEdgeWeight(edge));
}
}
return edges;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ public class CycleRanker {
@Getter
private CodebaseGraphDTO codebaseGraphDTO;

public CycleRanker(String repositoryPath) {
this(repositoryPath, repositoryPath);
}

/**
* Build a unified {@link CodebaseGraphDTO} from a directory that may contain
* both Java and Kotlin source files.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@
*/
class CycleRankerKotlinTest {

@Test
void singlePathConstructorRemainsAvailable() {
assertNotNull(new CycleRanker("repository"));
}

@TempDir
public File tempFolder;

Expand Down
Loading