diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..3c844565 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,45 @@ +# From https://github.com/gitattributes/gitattributes/blob/master/Java.gitattributes +# Java sources +*.java text diff=java +*.kt text diff=kotlin +*.groovy text diff=java +*.scala text diff=java +*.gradle text diff=java +*.gradle.kts text diff=kotlin + +# These files are text and should be normalized (Convert crlf => lf) +*.css text diff=css +*.scss text diff=css +*.sass text +*.df text +*.htm text diff=html +*.html text diff=html +*.js text +*.mjs text +*.cjs text +*.jsp text +*.jspf text +*.jspx text +*.properties text +*.tld text +*.tag text +*.tagx text +*.xml text +*.md text + +# These files are binary and should be left untouched +# (binary is a macro for -text -diff) +*.class binary +*.dll binary +*.ear binary +*.jar binary +*.so binary +*.war binary +*.jks binary + +# Common build-tool wrapper scripts ('.cmd' versions are handled by 'Common.gitattributes') +mvnw text eol=lf +gradlew text eol=lf + +# These are explicitly windows files and should use crlf +*.bat text eol=crlf \ No newline at end of file diff --git a/.github/workflows/maven-pr.yml b/.github/workflows/maven-pr.yml index e419bd69..223e9286 100644 --- a/.github/workflows/maven-pr.yml +++ b/.github/workflows/maven-pr.yml @@ -18,7 +18,7 @@ jobs: - name: Set up JDK 11 uses: actions/setup-java@v4 with: - java-version: 11 + java-version: 17 distribution: 'zulu' - name: Build With Maven diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 15b0023d..868833e3 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -18,7 +18,7 @@ jobs: - name: Set up JDK 11 uses: actions/setup-java@v4 with: - java-version: 11 + java-version: 17 distribution: 'zulu' - name: Build With Maven diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 01da8371..dec7def4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,7 +28,7 @@ jobs: - name: Set up JDK 11 uses: actions/setup-java@v3 with: - java-version: '11' + java-version: '17' distribution: 'temurin' cache: maven - name: Set release version diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..b8f9f3a7 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,39 @@ +# RefactorFirst Agent Guide + +## Essential Commands +- Full build: `mvn clean install` +- Skip tests: `mvn clean install -DskipTests` +- Run specific module tests: `mvn clean test -pl ` +- Run single test class: `mvn clean test -pl effort-ranker -Dtest=` +- Format code: `mvn spotless:apply` +- Check formatting: `mvn spotless:check` +- Build with OWASP dependency check: `mvn clean install -Plocal` + +## Key Architecture Points +- 11-module Maven build with data flowing left-to-right through the pipeline +- Central DTO: `CodebaseGraphDTO` (JGraphT graphs + disharmony lists + metrics) +- CLI entry point: `org.hjug.refactorfirst.Main` → `ReportCommand` +- Fat jar location: `cli/target/refactor-first-cli-*.jar` + +## Testing Notes +- JUnit 5 with parameterized tests +- Test fixtures in `test-resources/src/test/resources` +- For graph algorithm changes, check `JavaGraphBuilderTest` and `CircularReferenceCheckerTests` +- Mutation testing via PIT available but not in default build + +## Java & Toolchain +- Java 17 minimum (OpenRewrite supports 17, 21, 25) +- Lombok `@Data`/`@Builder` used extensively - avoid adding boilerplate it already removes +- SLF4J logging: `log.debug()` for verbose per-class output, `log.info()` sparingly +- Spotless enforces Palantir Java format + +## Maven Plugin Usage +Generate reports directly: +`mvn org.hjug.refactorfirst.plugin:refactor-first-maven-plugin:0.8.0:htmlReport` + +Configuration options (most important): +- `showDetails`: Shows God Class metrics in table (default: false) +- `backEdgeAnalysisCount`: 0 = analyze all back edges (default: 50) +- `analyzeCycles`: Whether to analyze cycles (default: true) +- `excludeTests`: Exclude test classes (default: true) +- `minifyHtml`: Minify HTML report (default: false) \ No newline at end of file diff --git a/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/JavaGraphBuilder.java b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/JavaGraphBuilder.java index 385858f7..6e05a32e 100644 --- a/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/JavaGraphBuilder.java +++ b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/JavaGraphBuilder.java @@ -4,7 +4,6 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -80,7 +79,7 @@ private CodebaseGraphDTO processWithOpenRewrite(String repositoryPath, GraphBuil new GraphMetricsCollector(classReferencesGraph, packageReferencesGraph); MetricsCollectingVisitor metricsVisitor = new MetricsCollectingVisitor(metricsCollector); - try (Stream pathStream = Files.walk(Paths.get(srcDirectory.getAbsolutePath()))) { + try (Stream pathStream = Files.walk(Path.of(srcDirectory.getAbsolutePath()))) { List list; if (config.isExcludeTests()) { list = pathStream @@ -90,12 +89,10 @@ private CodebaseGraphDTO processWithOpenRewrite(String repositoryPath, GraphBuil list = pathStream.collect(Collectors.toList()); } - javaParser - .parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx) - .forEach(cu -> { - javaVisitor.visit(cu, ctx); - metricsVisitor.visit(cu, ctx); - }); + javaParser.parse(list, Path.of(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { + javaVisitor.visit(cu, ctx); + metricsVisitor.visit(cu, ctx); + }); } removeClassesNotInCodebase(dependencyCollector.getPackagesInCodebase(), classReferencesGraph); diff --git a/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/metrics/MetricsCollectingVisitor.java b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/metrics/MetricsCollectingVisitor.java index 1c098958..dc56fa70 100644 --- a/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/metrics/MetricsCollectingVisitor.java +++ b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/metrics/MetricsCollectingVisitor.java @@ -60,8 +60,7 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex currentPackageName = type.getPackageName(); // Get or create metrics - this ensures it's stored in the collector - if (metricsCollector instanceof GraphMetricsCollector) { - GraphMetricsCollector gmc = (GraphMetricsCollector) metricsCollector; + if (metricsCollector instanceof GraphMetricsCollector gmc) { currentClassMetrics = gmc.getAllClassMetrics().computeIfAbsent(currentClassName, ClassMetrics::new); } else { currentClassMetrics = metricsCollector.getClassMetrics(currentClassName); @@ -85,16 +84,32 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex currentClassMetrics.setParentClass(parentType.getFullyQualifiedName()); } + // Handle record components + boolean isRecord = classDecl.getKind() == J.ClassDeclaration.Kind.Type.Record; + if (isRecord) { + // Record components are in the primary constructor + List primaryConstructor = classDecl.getPrimaryConstructor(); + if (primaryConstructor != null) { + for (Statement stmt : primaryConstructor) { + if (stmt instanceof J.VariableDeclarations varDecl) { + for (J.VariableDeclarations.NamedVariable var : varDecl.getVariables()) { + // Record components are implicitly public final fields with accessor methods + String varName = var.getSimpleName(); + currentClassMetrics.addAttribute(varName, true); // public + } + } + } + } + } + // Count protected members int protectedMembers = 0; for (Statement statement : classDecl.getBody().getStatements()) { - if (statement instanceof J.VariableDeclarations) { - J.VariableDeclarations varDecl = (J.VariableDeclarations) statement; + if (statement instanceof J.VariableDeclarations varDecl) { if (varDecl.getModifiers().stream().anyMatch(mod -> mod.getType() == J.Modifier.Type.Protected)) { protectedMembers++; } - } else if (statement instanceof J.MethodDeclaration) { - J.MethodDeclaration methodDecl = (J.MethodDeclaration) statement; + } else if (statement instanceof J.MethodDeclaration methodDecl) { if (methodDecl.getModifiers().stream().anyMatch(mod -> mod.getType() == J.Modifier.Type.Protected)) { protectedMembers++; } @@ -191,6 +206,23 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex public J.VariableDeclarations visitVariableDeclarations( J.VariableDeclarations multiVariable, ExecutionContext ctx) { if (currentClassName != null && currentMethodSignature == null) { + // Skip record components in primary constructor - they're already counted in visitClassDeclaration + J.ClassDeclaration enclosingClass = getCursor().firstEnclosing(J.ClassDeclaration.class); + if (enclosingClass != null && enclosingClass.getKind() == J.ClassDeclaration.Kind.Type.Record) { + // Check if this VariableDeclarations is in the primary constructor + // The parent is JRightPadded, grandparent is the JContainer/List + Object grandParent = getCursor().getParent().getParent().getValue(); + List primaryConstructor = enclosingClass.getPrimaryConstructor(); + if (primaryConstructor != null) { + if (grandParent == primaryConstructor + || (grandParent instanceof JContainer container + && primaryConstructor.equals(container.getElements()))) { + // This is a record component in the primary constructor, skip it + return super.visitVariableDeclarations(multiVariable, ctx); + } + } + } + for (J.VariableDeclarations.NamedVariable var : multiVariable.getVariables()) { String varName = var.getSimpleName(); boolean isPublic = multiVariable.hasModifier(J.Modifier.Type.Public); @@ -238,8 +270,8 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu JavaType.Method methodType = method.getMethodType(); if (methodType != null && !methodType.isConstructor()) { JavaType declaringType = methodType.getDeclaringType(); - if (declaringType instanceof JavaType.FullyQualified) { - String declaringFqn = ((JavaType.FullyQualified) declaringType).getFullyQualifiedName(); + if (declaringType instanceof JavaType.FullyQualified qualified) { + String declaringFqn = qualified.getFullyQualifiedName(); if (!declaringFqn.equals(currentClassName)) { StringBuilder sig = new StringBuilder(); sig.append(declaringFqn) @@ -271,8 +303,7 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu public J.FieldAccess visitFieldAccess(J.FieldAccess fieldAccess, ExecutionContext ctx) { if (currentMethodMetrics != null && fieldAccess.getType() != null) { JavaType type = fieldAccess.getType(); - if (type instanceof JavaType.Variable) { - JavaType.Variable varType = (JavaType.Variable) type; + if (type instanceof JavaType.Variable varType) { if (varType.getOwner() instanceof JavaType.FullyQualified) { JavaType.FullyQualified owner = (JavaType.FullyQualified) varType.getOwner(); String ownerFqn = owner.getFullyQualifiedName(); @@ -311,8 +342,7 @@ private String buildMethodSignature(J.MethodDeclaration method) { sig.append(method.getSimpleName()).append("("); boolean first = true; for (org.openrewrite.java.tree.Statement param : method.getParameters()) { - if (param instanceof J.VariableDeclarations) { - J.VariableDeclarations varDecl = (J.VariableDeclarations) param; + if (param instanceof J.VariableDeclarations varDecl) { if (!first) { sig.append(","); } diff --git a/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/JavaVisitor.java b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/JavaVisitor.java index b30ac9e1..7be059f4 100644 --- a/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/JavaVisitor.java +++ b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/JavaVisitor.java @@ -7,7 +7,10 @@ import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavadocVisitor; import org.openrewrite.java.tree.*; +import org.openrewrite.java.tree.J.VariableDeclarations; import org.openrewrite.java.tree.Javadoc; +import org.openrewrite.java.tree.Statement; +import org.openrewrite.java.tree.TypeTree; /** * BUG: Static method calls and definitions are not being captured, but were previously being captured. @@ -110,6 +113,25 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, P } } + // Handle record components (record header parameters) + if (classDecl.getKind() == J.ClassDeclaration.Kind.Type.Record) { + List primaryConstructor = classDecl.getPrimaryConstructor(); + if (primaryConstructor != null) { + for (Statement stmt : primaryConstructor) { + if (stmt instanceof VariableDeclarations varDecl) { + TypeTree typeExpression = varDecl.getTypeExpression(); + if (typeExpression != null) { + typeProcessor.processType(owningFqn, typeExpression.getType()); + } + // Also process annotations on record components + for (J.Annotation annotation : varDecl.getLeadingAnnotations()) { + typeProcessor.processAnnotation(owningFqn, annotation, getCursor()); + } + } + } + } + } + return super.visitClassDeclaration(classDecl, p); } finally { currentOwnerFqn = previousOwner; diff --git a/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/TypeDependencyExtractor.java b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/TypeDependencyExtractor.java index 2b55f45f..81a60a5e 100644 --- a/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/TypeDependencyExtractor.java +++ b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/TypeDependencyExtractor.java @@ -25,14 +25,14 @@ public Set extractDependencies(JavaType javaType) { } private void extractDependenciesRecursive(JavaType javaType, Set dependencies) { - if (javaType instanceof JavaType.Class) { - extractFromClass((JavaType.Class) javaType, dependencies); - } else if (javaType instanceof JavaType.Parameterized) { - extractFromParameterized((JavaType.Parameterized) javaType, dependencies); - } else if (javaType instanceof JavaType.GenericTypeVariable) { - extractFromGenericTypeVariable((JavaType.GenericTypeVariable) javaType, dependencies); - } else if (javaType instanceof JavaType.Array) { - extractFromArray((JavaType.Array) javaType, dependencies); + if (javaType instanceof JavaType.Class clazz) { + extractFromClass(clazz, dependencies); + } else if (javaType instanceof JavaType.Parameterized parameterized) { + extractFromParameterized(parameterized, dependencies); + } else if (javaType instanceof JavaType.GenericTypeVariable variable) { + extractFromGenericTypeVariable(variable, dependencies); + } else if (javaType instanceof JavaType.Array array) { + extractFromArray(array, dependencies); } } @@ -62,10 +62,10 @@ private void extractFromGenericTypeVariable(JavaType.GenericTypeVariable typeVar log.debug("Type parameter type name: {}", typeVariable.getName()); for (JavaType bound : typeVariable.getBounds()) { - if (bound instanceof JavaType.Class) { - dependencies.add(((JavaType.Class) bound).getFullyQualifiedName()); - } else if (bound instanceof JavaType.Parameterized) { - dependencies.add(((JavaType.Parameterized) bound).getFullyQualifiedName()); + if (bound instanceof JavaType.Class clazz) { + dependencies.add(clazz.getFullyQualifiedName()); + } else if (bound instanceof JavaType.Parameterized parameterized) { + dependencies.add(parameterized.getFullyQualifiedName()); } else { log.debug("Unknown type bound: {}", bound); } diff --git a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/metrics/MetricsCollectionTest.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/metrics/MetricsCollectionTest.java index 99163802..88539156 100644 --- a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/metrics/MetricsCollectionTest.java +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/metrics/MetricsCollectionTest.java @@ -4,7 +4,6 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.List; import java.util.stream.Collectors; import org.jgrapht.graph.DefaultDirectedWeightedGraph; @@ -33,8 +32,8 @@ void collectClassMetrics() throws IOException { MetricsCollectingVisitor metricsVisitor = new MetricsCollectingVisitor(metricsCollector); - List list = Files.walk(Paths.get(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); - javaParser.parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { + List list = Files.walk(Path.of(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); + javaParser.parse(list, Path.of(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { metricsVisitor.visit(cu, ctx); }); @@ -80,8 +79,8 @@ void collectMethodMetrics() throws IOException { MetricsCollectingVisitor metricsVisitor = new MetricsCollectingVisitor(metricsCollector); - List list = Files.walk(Paths.get(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); - javaParser.parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { + List list = Files.walk(Path.of(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); + javaParser.parse(list, Path.of(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { metricsVisitor.visit(cu, ctx); }); @@ -127,8 +126,8 @@ void detectGodClass() throws IOException { MetricsCollectingVisitor metricsVisitor = new MetricsCollectingVisitor(metricsCollector); - List list = Files.walk(Paths.get(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); - javaParser.parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { + List list = Files.walk(Path.of(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); + javaParser.parse(list, Path.of(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { metricsVisitor.visit(cu, ctx); }); @@ -187,8 +186,8 @@ void detectDataClass() throws IOException { MetricsCollectingVisitor metricsVisitor = new MetricsCollectingVisitor(metricsCollector); - List list = Files.walk(Paths.get(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); - javaParser.parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { + List list = Files.walk(Path.of(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); + javaParser.parse(list, Path.of(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { metricsVisitor.visit(cu, ctx); }); @@ -240,8 +239,8 @@ void detectBrainMethod() throws IOException { MetricsCollectingVisitor metricsVisitor = new MetricsCollectingVisitor(metricsCollector); - List list = Files.walk(Paths.get(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); - javaParser.parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { + List list = Files.walk(Path.of(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); + javaParser.parse(list, Path.of(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { metricsVisitor.visit(cu, ctx); }); @@ -276,8 +275,8 @@ void detectBrainClass() throws IOException { MetricsCollectingVisitor metricsVisitor = new MetricsCollectingVisitor(metricsCollector); - List list = Files.walk(Paths.get(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); - javaParser.parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { + List list = Files.walk(Path.of(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); + javaParser.parse(list, Path.of(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { metricsVisitor.visit(cu, ctx); }); @@ -351,8 +350,8 @@ void detectFeatureEnvy() throws IOException { MetricsCollectingVisitor metricsVisitor = new MetricsCollectingVisitor(metricsCollector); - List list = Files.walk(Paths.get(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); - javaParser.parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { + List list = Files.walk(Path.of(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); + javaParser.parse(list, Path.of(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { metricsVisitor.visit(cu, ctx); }); @@ -433,8 +432,8 @@ void detectIntensiveCoupling() throws IOException { MetricsCollectingVisitor metricsVisitor = new MetricsCollectingVisitor(metricsCollector); - List list = Files.walk(Paths.get(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); - javaParser.parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { + List list = Files.walk(Path.of(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); + javaParser.parse(list, Path.of(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { metricsVisitor.visit(cu, ctx); }); @@ -509,8 +508,8 @@ void detectDispersedCoupling() throws IOException { MetricsCollectingVisitor metricsVisitor = new MetricsCollectingVisitor(metricsCollector); - List list = Files.walk(Paths.get(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); - javaParser.parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { + List list = Files.walk(Path.of(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); + javaParser.parse(list, Path.of(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { metricsVisitor.visit(cu, ctx); }); @@ -585,8 +584,8 @@ void detectShotgunSurgery() throws IOException { MetricsCollectingVisitor metricsVisitor = new MetricsCollectingVisitor(metricsCollector); - List list = Files.walk(Paths.get(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); - javaParser.parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { + List list = Files.walk(Path.of(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); + javaParser.parse(list, Path.of(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { metricsVisitor.visit(cu, ctx); }); @@ -657,8 +656,8 @@ void detectRefusedParentBequest() throws IOException { MetricsCollectingVisitor metricsVisitor = new MetricsCollectingVisitor(metricsCollector); - List list = Files.walk(Paths.get(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); - javaParser.parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { + List list = Files.walk(Path.of(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); + javaParser.parse(list, Path.of(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { metricsVisitor.visit(cu, ctx); }); @@ -747,8 +746,8 @@ void detectTraditionBreaker() throws IOException { MetricsCollectingVisitor metricsVisitor = new MetricsCollectingVisitor(metricsCollector); - List list = Files.walk(Paths.get(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); - javaParser.parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { + List list = Files.walk(Path.of(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); + javaParser.parse(list, Path.of(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { metricsVisitor.visit(cu, ctx); }); @@ -830,8 +829,8 @@ void sourceFilePathCapturedForAllClasses() throws IOException { MetricsCollectingVisitor metricsVisitor = new MetricsCollectingVisitor(metricsCollector); - List list = Files.walk(Paths.get(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); - javaParser.parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { + List list = Files.walk(Path.of(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); + javaParser.parse(list, Path.of(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { metricsVisitor.visit(cu, ctx); }); @@ -867,9 +866,9 @@ void javadocMethodReferenceIsNotCountedAsForeignMethodCall() throws IOException GraphMetricsCollector metricsCollector = new GraphMetricsCollector(classGraph, packageGraph); MetricsCollectingVisitor metricsVisitor = new MetricsCollectingVisitor(metricsCollector); - List list = Files.walk(Paths.get(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); + List list = Files.walk(Path.of(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); javaParser - .parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx) + .parse(list, Path.of(srcDirectory.getAbsolutePath()), ctx) .forEach(cu -> metricsVisitor.visit(cu, ctx)); metricsCollector.finalizeMetrics(); @@ -884,4 +883,49 @@ void javadocMethodReferenceIsNotCountedAsForeignMethodCall() throws IOException "CBO must be 0: Semaphore.acquire() is Javadoc-only and must not be counted as a foreign dependency. Got: " + metrics.getCouplingBetweenObjects()); } + + @Test + void collectRecordClassMetrics() throws IOException { + File srcDirectory = new File("src/test/java/org/hjug/graphbuilder/metrics/testclasses"); + + JavaParser javaParser = JavaParser.fromJavaVersion().build(); + ExecutionContext ctx = new InMemoryExecutionContext(Throwable::printStackTrace); + + DefaultDirectedWeightedGraph classGraph = + new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); + DefaultDirectedWeightedGraph packageGraph = + new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); + + GraphMetricsCollector metricsCollector = new GraphMetricsCollector(classGraph, packageGraph); + + MetricsCollectingVisitor metricsVisitor = new MetricsCollectingVisitor(metricsCollector); + + List list = Files.walk(Path.of(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); + javaParser.parse(list, Path.of(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { + metricsVisitor.visit(cu, ctx); + }); + + metricsCollector.finalizeMetrics(); + + ClassMetrics recordMetrics = + metricsCollector.getClassMetrics("org.hjug.graphbuilder.metrics.testclasses.RecordMetricsExample"); + Assertions.assertNotNull(recordMetrics, "RecordMetricsExample metrics should be collected"); + + Assertions.assertTrue(recordMetrics.getLinesOfCode() > 0, "LOC should be greater than 0"); + Assertions.assertEquals( + 3, recordMetrics.getNumberOfAttributes(), "Record should have 3 components as attributes"); + Assertions.assertEquals(3, recordMetrics.getNumberOfPublicAttributes(), "Record components should be public"); + // Record accessor methods (name(), value(), tags()) are implicit and not explicitly declared in source + // Only explicitly declared methods like getDisplayName() are counted as accessors + Assertions.assertEquals( + 1, + recordMetrics.getNumberOfAccessorMethods(), + "Record should have 1 accessor method (getDisplayName())"); + + System.out.println("\nRecordMetricsExample Metrics:"); + System.out.println(" LOC: " + recordMetrics.getLinesOfCode()); + System.out.println(" Number of Attributes: " + recordMetrics.getNumberOfAttributes()); + System.out.println(" Number of Public Attributes: " + recordMetrics.getNumberOfPublicAttributes()); + System.out.println(" Number of Accessor Methods: " + recordMetrics.getNumberOfAccessorMethods()); + } } diff --git a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/metrics/SignificantDuplicationTest.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/metrics/SignificantDuplicationTest.java index 2ee1be91..50ae7363 100644 --- a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/metrics/SignificantDuplicationTest.java +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/metrics/SignificantDuplicationTest.java @@ -4,7 +4,6 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.List; import java.util.stream.Collectors; import org.jgrapht.graph.DefaultDirectedWeightedGraph; @@ -47,8 +46,8 @@ void setup() throws IOException { metricsCollector = new GraphMetricsCollector(classGraph, packageGraph); MetricsCollectingVisitor metricsVisitor = new MetricsCollectingVisitor(metricsCollector); - List list = Files.walk(Paths.get(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); - javaParser.parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { + List list = Files.walk(Path.of(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); + javaParser.parse(list, Path.of(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { metricsVisitor.visit(cu, ctx); }); diff --git a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/metrics/testclasses/RecordMetricsExample.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/metrics/testclasses/RecordMetricsExample.java new file mode 100644 index 00000000..044e2988 --- /dev/null +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/metrics/testclasses/RecordMetricsExample.java @@ -0,0 +1,11 @@ +package org.hjug.graphbuilder.metrics.testclasses; + +public record RecordMetricsExample(String name, int value, java.util.List tags) { + public String getDisplayName() { + return name + " - " + value; + } + + public static RecordMetricsExample create() { + return new RecordMetricsExample("test", 42, java.util.List.of("a", "b")); + } +} diff --git a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaVisitorTest.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaVisitorTest.java index e05df532..c5ca16d6 100644 --- a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaVisitorTest.java +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaVisitorTest.java @@ -10,7 +10,6 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.List; import java.util.stream.Collectors; import org.hjug.graphbuilder.DependencyCollector; @@ -34,6 +33,7 @@ class JavaVisitorTest { private static final String VARIABLE_INITIALIZERS = TESTCLASSES + "/variableInitializers"; private static final String TRY_CATCH = TESTCLASSES + "/tryCatch"; private static final String JAVADOC_TESTCLASSES = TESTCLASSES + "/javadoc"; + private static final String RECORD = TESTCLASSES + "/record"; private static String repoFrom(String pathString) { return new File(pathString).toURI().toString().replace("/" + pathString, ""); @@ -43,8 +43,8 @@ private static void visitAll(JavaVisitor visitor, String pathS File srcDirectory = new File(pathString); JavaParser javaParser = JavaParser.fromJavaVersion().build(); ExecutionContext ctx = new InMemoryExecutionContext(Throwable::printStackTrace); - List list = Files.walk(Paths.get(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); - javaParser.parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> visitor.visit(cu, ctx)); + List list = Files.walk(Path.of(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); + javaParser.parse(list, Path.of(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> visitor.visit(cu, ctx)); } private static Graph buildAndVisit(String pathString) throws IOException { @@ -81,11 +81,9 @@ void visitClasses_registersExpectedPackageCount() throws IOException { new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class), new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class)); JavaVisitor javaVisitor = new JavaVisitor<>(repoFrom(TESTCLASSES), dependencyCollector); - List list = Files.walk(Paths.get(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); - javaParser - .parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx) - .forEach(cu -> javaVisitor.visit(cu, ctx)); - assertEquals(8, dependencyCollector.getPackagesInCodebase().size()); + List list = Files.walk(Path.of(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); + javaParser.parse(list, Path.of(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> javaVisitor.visit(cu, ctx)); + assertEquals(9, dependencyCollector.getPackagesInCodebase().size()); } @Test @@ -372,6 +370,85 @@ void javadocReferencedClassDoesNotCreateSpuriousDependencyEdge() throws IOExcept "JavaDocSibling is referenced only in Javadoc {@link} and must not create a dependency edge from JavaDocOwner"); } + @Test + void visitRecordDeclaration_capturesRecordAsVertex() throws IOException { + Graph graph = buildAndVisit(RECORD); + assertTrue( + graph.containsVertex("org.hjug.graphbuilder.visitor.testclasses.record.SimpleRecord"), + "SimpleRecord should be in the graph"); + } + + @Test + void visitRecordDeclaration_capturesRecordComponentsAsDependencies() throws IOException { + Graph graph = buildAndVisit(RECORD); + + // ComplexRecord has components: String, List, Map, NestedRecord, SimpleRecord[] + assertTrue( + graph.containsVertex("org.hjug.graphbuilder.visitor.testclasses.record.ComplexRecord"), + "ComplexRecord should be in the graph"); + assertTrue( + graph.containsVertex("org.hjug.graphbuilder.visitor.testclasses.record.ComplexRecord$NestedRecord"), + "NestedRecord should be in the graph"); + assertTrue( + graph.containsVertex("org.hjug.graphbuilder.visitor.testclasses.record.SimpleRecord"), + "SimpleRecord should be in the graph"); + + // Check dependencies from ComplexRecord to its component types + assertTrue( + graph.containsEdge( + "org.hjug.graphbuilder.visitor.testclasses.record.ComplexRecord", "java.lang.String"), + "Should have edge from ComplexRecord to String"); + assertTrue( + graph.containsEdge("org.hjug.graphbuilder.visitor.testclasses.record.ComplexRecord", "java.util.List"), + "Should have edge from ComplexRecord to List"); + assertTrue( + graph.containsEdge("org.hjug.graphbuilder.visitor.testclasses.record.ComplexRecord", "java.util.Map"), + "Should have edge from ComplexRecord to Map"); + } + + @Test + void visitGenericRecordDeclaration_capturesTypeParameters() throws IOException { + Graph graph = buildAndVisit(RECORD); + + assertTrue( + graph.containsVertex("org.hjug.graphbuilder.visitor.testclasses.record.GenericRecord"), + "GenericRecord should be in the graph"); + assertTrue( + graph.containsVertex("java.lang.Comparable"), + "Should have edge from GenericRecord to Comparable (type bound)"); + } + + @Test + void visitAnnotatedRecordDeclaration_capturesAnnotations() throws IOException { + Graph graph = buildAndVisit(RECORD); + + assertTrue( + graph.containsVertex("org.hjug.graphbuilder.visitor.testclasses.record.AnnotatedRecord"), + "AnnotatedRecord should be in the graph"); + assertTrue( + graph.containsVertex("java.io.Serializable"), + "Should have edge from AnnotatedRecord to Serializable (implements)"); + // Deprecated annotation should also be captured + assertTrue( + graph.containsVertex("java.lang.Deprecated"), + "Should have edge from AnnotatedRecord to Deprecated annotation"); + } + + @Test + void visitRecordDeclaration_recordsSourceFileMapping() throws IOException { + JavaVisitor visitor = buildVisitor(repoFrom(RECORD)); + visitAll(visitor, RECORD); + + assertTrue( + visitor.getClassToSourceFilePathMapping() + .containsKey("org.hjug.graphbuilder.visitor.testclasses.record.SimpleRecord"), + "SimpleRecord should have source file mapping"); + assertTrue( + visitor.getClassToSourceFilePathMapping() + .containsKey("org.hjug.graphbuilder.visitor.testclasses.record.ComplexRecord$NestedRecord"), + "NestedRecord should have source file mapping"); + } + private static double getEdgeWeight( Graph classReferencesGraph, String sourceVertex, String targetVertex) { return classReferencesGraph.getEdgeWeight(classReferencesGraph.getEdge(sourceVertex, targetVertex)); diff --git a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/record/AnnotatedRecord.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/record/AnnotatedRecord.java new file mode 100644 index 00000000..bccdd328 --- /dev/null +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/record/AnnotatedRecord.java @@ -0,0 +1,20 @@ +package org.hjug.graphbuilder.visitor.testclasses.record; + +import java.io.Serializable; + +public record AnnotatedRecord(@Deprecated String deprecatedField, String normalField) implements Serializable { + + public AnnotatedRecord { + if (normalField == null) { + throw new IllegalArgumentException("normalField cannot be null"); + } + } + + public String getNormalField() { + return normalField.toUpperCase(); + } + + public static AnnotatedRecord create(String deprecatedField, String normalField) { + return new AnnotatedRecord(deprecatedField, normalField); + } +} diff --git a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/record/ComplexRecord.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/record/ComplexRecord.java new file mode 100644 index 00000000..ac866cbd --- /dev/null +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/record/ComplexRecord.java @@ -0,0 +1,9 @@ +package org.hjug.graphbuilder.visitor.testclasses.record; + +import java.util.List; +import java.util.Map; + +public record ComplexRecord( + String name, List tags, Map counts, NestedRecord nested, SimpleRecord[] simpleArray) { + public record NestedRecord(int id, String data) {} +} diff --git a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/record/GenericRecord.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/record/GenericRecord.java new file mode 100644 index 00000000..3e2cbc1c --- /dev/null +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/record/GenericRecord.java @@ -0,0 +1,5 @@ +package org.hjug.graphbuilder.visitor.testclasses.record; + +import java.util.List; + +public record GenericRecord>(T value, List values) {} diff --git a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/record/SimpleRecord.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/record/SimpleRecord.java new file mode 100644 index 00000000..3d7709e6 --- /dev/null +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/record/SimpleRecord.java @@ -0,0 +1,3 @@ +package org.hjug.graphbuilder.visitor.testclasses.record; + +public record SimpleRecord(String name, int value) {} diff --git a/cost-benefit-calculator/src/main/java/org/hjug/cbc/CostBenefitCalculator.java b/cost-benefit-calculator/src/main/java/org/hjug/cbc/CostBenefitCalculator.java index eefa08f6..d51f96ec 100644 --- a/cost-benefit-calculator/src/main/java/org/hjug/cbc/CostBenefitCalculator.java +++ b/cost-benefit-calculator/src/main/java/org/hjug/cbc/CostBenefitCalculator.java @@ -7,7 +7,6 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -66,7 +65,7 @@ public void runPmdAnalysis() throws IOException { try (PmdAnalysis pmd = PmdAnalysis.create(configuration)) { loadRules(pmd); - try (Stream files = Files.walk(Paths.get(repositoryPath))) { + try (Stream files = Files.walk(Path.of(repositoryPath))) { files.filter(Files::isRegularFile).forEach(file -> pmd.files().addFile(file)); } @@ -82,7 +81,7 @@ private void loadRules(PmdAnalysis pmd) { cboClassRule.setLanguage(LanguageRegistry.PMD.getLanguageByFullName("Java")); pmd.addRuleSet(RuleSet.forSingleRule(cboClassRule)); - log.info("files to be scanned: " + Paths.get(repositoryPath)); + log.info("files to be scanned: " + Path.of(repositoryPath)); } public List calculateGodClassCostBenefitValues(List godClasses) { @@ -252,8 +251,11 @@ public List getRankedChangeProneness(List } catch (NullPointerException e) { // Should not be reached log.error( - "Error looking up class SCM info. If this error is encountered, " - + "please log a bug on the RefactorFirst project and describe if the class is a nested class, lambda, etc. \nClass: {}, Path: {}", + """ + Error looking up class SCM info. If this error is encountered, \ + please log a bug on the RefactorFirst project and describe if the class is a nested class, lambda, etc.\s + Class: {}, Path: {}\ + """, className, path, e); @@ -267,10 +269,8 @@ public List getRankedChangeProneness(List }) .collect(Collectors.toList()); - List sortedScmInfos = scmLogInfos.stream() - .filter(Optional::isPresent) - .map(Optional::get) - .collect(Collectors.toList()); + List sortedScmInfos = + scmLogInfos.stream().flatMap(Optional::stream).collect(Collectors.toList()); changePronenessRanker.rankChangeProneness(sortedScmInfos); return sortedScmInfos; diff --git a/cost-benefit-calculator/src/main/java/org/hjug/cbc/RankedDisharmony.java b/cost-benefit-calculator/src/main/java/org/hjug/cbc/RankedDisharmony.java index 38547f41..4995c015 100644 --- a/cost-benefit-calculator/src/main/java/org/hjug/cbc/RankedDisharmony.java +++ b/cost-benefit-calculator/src/main/java/org/hjug/cbc/RankedDisharmony.java @@ -1,6 +1,6 @@ package org.hjug.cbc; -import java.nio.file.Paths; +import java.nio.file.Path; import java.time.Instant; import java.util.List; import lombok.Data; @@ -50,7 +50,7 @@ public class RankedDisharmony { public RankedDisharmony(GodClass godClass, ScmLogInfo scmLogInfo) { path = scmLogInfo.getPath(); // from https://stackoverflow.com/questions/1011287/get-file-name-from-a-file-location-in-java - fileName = Paths.get(path).getFileName().toString(); + fileName = Path.of(path).getFileName().toString(); className = godClass.getClassName(); changePronenessRank = scmLogInfo.getChangePronenessRank(); effortRank = godClass.getOverallRank(); @@ -71,7 +71,7 @@ public RankedDisharmony(GodClass godClass, ScmLogInfo scmLogInfo) { public RankedDisharmony(CBOClass cboClass, ScmLogInfo scmLogInfo) { path = scmLogInfo.getPath(); // from https://stackoverflow.com/questions/1011287/get-file-name-from-a-file-location-in-java - fileName = Paths.get(path).getFileName().toString(); + fileName = Path.of(path).getFileName().toString(); className = cboClass.getClassName(); changePronenessRank = scmLogInfo.getChangePronenessRank(); effortRank = cboClass.getCouplingCount(); @@ -84,7 +84,7 @@ public RankedDisharmony(CBOClass cboClass, ScmLogInfo scmLogInfo) { public RankedDisharmony(DisharmonyInstance instance, ScmLogInfo scmLogInfo) { path = scmLogInfo.getPath(); - fileName = Paths.get(path).getFileName().toString(); + fileName = Path.of(path).getFileName().toString(); className = instance.getClassName(); changePronenessRank = scmLogInfo.getChangePronenessRank(); effortRank = instance.getOverallRank(); diff --git a/coverage/pom.xml b/coverage/pom.xml index 34f4ef79..2a6a141e 100644 --- a/coverage/pom.xml +++ b/coverage/pom.xml @@ -60,7 +60,7 @@ org.jacoco jacoco-maven-plugin - 0.8.8 + 0.8.15 report-aggregate diff --git a/graph-algorithms/src/main/java/org/hjug/feedback/SuperTypeToken.java b/graph-algorithms/src/main/java/org/hjug/feedback/SuperTypeToken.java index 285f9589..2642caf4 100644 --- a/graph-algorithms/src/main/java/org/hjug/feedback/SuperTypeToken.java +++ b/graph-algorithms/src/main/java/org/hjug/feedback/SuperTypeToken.java @@ -7,8 +7,8 @@ public abstract class SuperTypeToken { protected SuperTypeToken() { Type superclass = getClass().getGenericSuperclass(); - if (superclass instanceof ParameterizedType) { - this.type = ((ParameterizedType) superclass).getActualTypeArguments()[0]; + if (superclass instanceof ParameterizedType parameterizedType) { + this.type = parameterizedType.getActualTypeArguments()[0]; } else { throw new RuntimeException("Missing type parameter."); } @@ -24,19 +24,19 @@ public Class getClassFromTypeToken() { // ((ParameterizedType) type).getActualTypeArguments()[0] - returns String in List static Class getClassFromTypeToken(Type type) { - if (type instanceof Class) { - return (Class) type; - } else if (type instanceof ParameterizedType) { - return (Class) ((ParameterizedType) type).getRawType(); - } else if (type instanceof GenericArrayType) { - Type componentType = ((GenericArrayType) type).getGenericComponentType(); + if (type instanceof Class clazz) { + return clazz; + } else if (type instanceof ParameterizedType parameterizedType) { + return (Class) parameterizedType.getRawType(); + } else if (type instanceof GenericArrayType arrayType) { + Type componentType = arrayType.getGenericComponentType(); return java.lang.reflect.Array.newInstance(getClassFromTypeToken(componentType), 0) .getClass(); } else if (type instanceof TypeVariable) { // Type variables don't have a direct class representation return Object.class; // Fallback - } else if (type instanceof WildcardType) { - Type[] upperBounds = ((WildcardType) type).getUpperBounds(); + } else if (type instanceof WildcardType wildcardType) { + Type[] upperBounds = wildcardType.getUpperBounds(); return getClassFromTypeToken(upperBounds[0]); // Use the first upper bound } throw new IllegalArgumentException("Unsupported Type: " + type); diff --git a/graph-algorithms/src/main/java/org/hjug/feedback/vertex/approximate/FeedbackVertexSetSolver.java b/graph-algorithms/src/main/java/org/hjug/feedback/vertex/approximate/FeedbackVertexSetSolver.java index 71fc9940..417b1332 100644 --- a/graph-algorithms/src/main/java/org/hjug/feedback/vertex/approximate/FeedbackVertexSetSolver.java +++ b/graph-algorithms/src/main/java/org/hjug/feedback/vertex/approximate/FeedbackVertexSetSolver.java @@ -66,7 +66,7 @@ private Map computeFractionalSolution() { .filter(v -> cycleCounts.getOrDefault(v, 0L) > 0) .min(Comparator.comparingDouble(v -> vertexWeights.get(v) / cycleCounts.get(v))); - if (!minVertex.isPresent()) break; + if (minVertex.isEmpty()) break; V vertex = minVertex.get(); double increment = vertexWeights.get(vertex) / cycleCounts.get(vertex); diff --git a/graph-algorithms/src/main/java/org/hjug/feedback/vertex/kernelized/TreewidthComputer.java b/graph-algorithms/src/main/java/org/hjug/feedback/vertex/kernelized/TreewidthComputer.java index 589e23a7..7acd098e 100644 --- a/graph-algorithms/src/main/java/org/hjug/feedback/vertex/kernelized/TreewidthComputer.java +++ b/graph-algorithms/src/main/java/org/hjug/feedback/vertex/kernelized/TreewidthComputer.java @@ -215,7 +215,7 @@ public int fillInHeuristicTreewidth(Graph graph) { .min(Map.Entry.comparingByValue())) .get(); - if (!bestVertexEntry.isPresent()) { + if (bestVertexEntry.isEmpty()) { // Fallback: choose any remaining vertex V fallbackVertex = remainingVertices.keys().nextElement(); eliminateVertexParallel(fallbackVertex, adjacencyMap, remainingVertices, maxCliqueSize); diff --git a/graph-algorithms/src/test/java/org/hjug/dsm/CircularReferenceCheckerTests.java b/graph-algorithms/src/test/java/org/hjug/dsm/CircularReferenceCheckerTests.java index b20a8276..f6d4d4c8 100644 --- a/graph-algorithms/src/test/java/org/hjug/dsm/CircularReferenceCheckerTests.java +++ b/graph-algorithms/src/test/java/org/hjug/dsm/CircularReferenceCheckerTests.java @@ -33,7 +33,7 @@ void detectCyclesTest() { cyclesForEveryVertexMap = sutCircularReferenceChecker.getCycles(classReferencesGraph); assertEquals(1, cyclesForEveryVertexMap.size(), "Now we expect one circular reference"); assertEquals( - "([A, B, C], [(A,B), (B,C), (C,A)])", + "([A, B, C], [(C,A), (A,B), (B,C)])", cyclesForEveryVertexMap.get("A").toString(), "Expected a different circular reference"); } diff --git a/pom.xml b/pom.xml index 8fe258f8..6486b311 100644 --- a/pom.xml +++ b/pom.xml @@ -50,13 +50,13 @@ + 17 + 17 UTF-8 - 11 - 11 - 1.18.36 + 1.18.46 jimbethancourt_RefactorFirst ${project.artifactId} jimbethancourt-github @@ -140,19 +140,19 @@ org.eclipse.jgit org.eclipse.jgit - 6.10.0.202406032230-r + 7.7.0.202606012155-r compile org.jgrapht jgrapht-core - 1.5.2 + 1.5.3 org.jgrapht jgrapht-opt - 1.5.2 + 1.5.3 @@ -273,13 +273,26 @@ maven-deploy-plugin 3.1.4 + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.projectlombok + lombok + 1.18.40 + + + + org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.15.0 -XDcompilePolicy=simple @@ -297,14 +310,14 @@ ${errorprone.version} --> - 11 + 17 org.jacoco jacoco-maven-plugin - 0.8.14 + 0.8.15 @@ -364,7 +377,7 @@ com.github.spotbugs spotbugs-maven-plugin - 4.9.2.0 + 4.9.8.5 com.github.spotbugs diff --git a/report/src/main/java/org/hjug/refactorfirst/report/CsvReport.java b/report/src/main/java/org/hjug/refactorfirst/report/CsvReport.java index 8c13a121..aa6d692c 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/CsvReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/CsvReport.java @@ -3,7 +3,7 @@ import static org.hjug.refactorfirst.report.ReportWriter.writeReportToDisk; import java.io.File; -import java.nio.file.Paths; +import java.nio.file.Path; import java.time.Instant; import java.time.ZoneId; import java.time.format.DateTimeFormatter; @@ -33,7 +33,7 @@ public void execute( String filename = fileNameSB.toString(); if (Objects.equals(projectName, "Maven Stub Project (No POM)")) { - projectName = new File(Paths.get("").toAbsolutePath().toString()).getName(); + projectName = new File(Path.of("").toAbsolutePath().toString()).getName(); } log.info("Generating {} for {} - {} date: {}", filename, projectName, projectVersion, publishedDate); @@ -49,7 +49,7 @@ public void execute( projectBaseDir = baseDir.getPath(); optionalGitDir = Optional.ofNullable(gitLogReader.getGitDir(baseDir)); } else { - projectBaseDir = Paths.get("").toAbsolutePath().toString(); + projectBaseDir = Path.of("").toAbsolutePath().toString(); optionalGitDir = Optional.ofNullable(gitLogReader.getGitDir(new File(projectBaseDir))); } diff --git a/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java b/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java index 7b33619b..6cb2135f 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java @@ -16,366 +16,376 @@ public class HtmlReport extends SimpleHtmlReport { // use Files.readString(Path.of(file)) // Created by generative AI and modified slightly - public static final String SUGIYAMA_SIGMA_GRAPH = ""; + public static final String SUGIYAMA_SIGMA_GRAPH = + """ + """; public static final String FORCE_3D_GRAPH = - ""; + """ + """; // Created by generative AI and modified - public static final String POPUP_STYLE = ""; + public static final String POPUP_STYLE = + """ + """; // Created by generative AI and modified - public static final String POPUP_FUNCTIONS = ""; + public static final String POPUP_FUNCTIONS = + """ + """; @Override public String printHead() { @@ -423,13 +433,15 @@ void renderPackageMapMenu(StringBuilder stringBuilder) { @Override String renderGithubButtons() { - return "
\n" + "

Show RefactorFirst some ❤️

\n" - + "Star\n" - + "Fork\n" - + "Watch\n" - + "Issue\n" - + "Sponsor\n" - + "
"; + return """ +
+

Show RefactorFirst some ❤️

+ Star + Fork + Watch + Issue + Sponsor +
"""; } public String getName(Locale locale) { diff --git a/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java b/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java index c068162f..535c1c11 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java @@ -5,7 +5,7 @@ import in.wilsonl.minifyhtml.Configuration; import in.wilsonl.minifyhtml.MinifyHtml; import java.io.File; -import java.nio.file.Paths; +import java.nio.file.Path; import java.time.Instant; import java.time.ZoneId; import java.time.format.DateTimeFormatter; @@ -35,7 +35,10 @@ public class SimpleHtmlReport { public static final String THE_BEGINNING = - "\n" + "\n"; + """ + + + """; public static final String THE_END = "\n" + " \n" + " \n" + "\n"; @@ -132,7 +135,7 @@ public StringBuilder generateReport( projectBaseDir = baseDir.getPath(); optionalGitDir = Optional.ofNullable(GitLogReader.getGitDir(baseDir)); } else { - projectBaseDir = Paths.get("").toAbsolutePath().toString(); + projectBaseDir = Path.of("").toAbsolutePath().toString(); optionalGitDir = Optional.ofNullable(GitLogReader.getGitDir(new File(projectBaseDir))); } @@ -873,10 +876,12 @@ public String printOpenBodyTag() { } public String printBreadcrumbs() { - return "
\n" - + "
\n" - + "
\n" - + "
\n"; + return """ + +